istio
128 строк · 4.8 Кб
1// Copyright Istio Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// nolint: gocritic
16package ambient
17
18import (
19"net/netip"
20"strings"
21
22v1 "k8s.io/api/core/v1"
23metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24"sigs.k8s.io/gateway-api/apis/v1beta1"
25
26"istio.io/istio/pkg/config/constants"
27"istio.io/istio/pkg/config/schema/gvk"
28"istio.io/istio/pkg/kube/krt"
29"istio.io/istio/pkg/log"
30"istio.io/istio/pkg/ptr"
31)
32
33type Waypoint struct {
34krt.Named
35
36Addresses []netip.Addr
37}
38
39func fetchWaypoint(ctx krt.HandlerContext, Waypoints krt.Collection[Waypoint], Namespaces krt.Collection[*v1.Namespace], o metav1.ObjectMeta) *Waypoint {
40// namespace to be used when the annotation doesn't include a namespace
41fallbackNamespace := o.Namespace
42// try fetching the waypoint defined on the object itself
43wp, isNone := getUseWaypoint(o, fallbackNamespace)
44if isNone {
45// we've got a local override here opting out of waypoint
46return nil
47}
48if wp != nil {
49// plausible the object has a waypoint defined but that waypoint's underlying gateway is not ready, in this case we'd return nil here even if
50// the namespace-defined waypoint is ready and would not be nil... is this OK or should we handle that? Could lead to odd behavior when
51// o was reliant on the namespace waypoint and then get's a use-waypoint annotation added before that gateway is ready.
52// goes from having a waypoint to having no waypoint and then eventually gets a waypoint back
53return krt.FetchOne[Waypoint](ctx, Waypoints, krt.FilterKey(wp.ResourceName()))
54}
55
56// try fetching the namespace-defined waypoint
57namespace := ptr.OrEmpty[*v1.Namespace](krt.FetchOne[*v1.Namespace](ctx, Namespaces, krt.FilterKey(o.Namespace)))
58// this probably should never be nil. How would o exist in a namespace we know nothing about? maybe edge case of starting the controller or ns delete?
59if namespace != nil {
60// toss isNone, we don't need to know /why/ we got nil
61wpNamespace, _ := getUseWaypoint(namespace.ObjectMeta, fallbackNamespace)
62if wpNamespace != nil {
63return krt.FetchOne[Waypoint](ctx, Waypoints, krt.FilterKey(wpNamespace.ResourceName()))
64}
65}
66
67// neither o nor it's namespace has a use-waypoint annotation
68return nil
69}
70
71// getUseWaypoint takes objectMeta and a defaultNamespace
72// it looks for the istio.io/use-waypoint annotation and parses it
73// if there is no namespace provided in the annotation the default namespace will be used
74// defaultNamespace avoids the need to infer when object meta from a namespace was given
75func getUseWaypoint(meta metav1.ObjectMeta, defaultNamespace string) (named *krt.Named, isNone bool) {
76if annotationValue, ok := meta.Annotations[constants.AmbientUseWaypoint]; ok {
77if annotationValue == "#none" || annotationValue == "~" {
78return nil, true
79}
80namespacedName := strings.Split(annotationValue, "/")
81switch len(namespacedName) {
82case 1:
83return &krt.Named{
84Name: namespacedName[0],
85Namespace: defaultNamespace,
86}, false
87case 2:
88return &krt.Named{
89Name: namespacedName[1],
90Namespace: namespacedName[0],
91}, false
92default:
93// malformed annotation error
94log.Errorf("%s/%s, has a malformed %s annotation, value found: %s", meta.GetNamespace(), meta.GetName(), constants.AmbientUseWaypoint, annotationValue)
95return nil, false
96}
97
98}
99return nil, false
100}
101
102func (w Waypoint) ResourceName() string {
103return w.GetNamespace() + "/" + w.GetName()
104}
105
106func WaypointsCollection(Gateways krt.Collection[*v1beta1.Gateway]) krt.Collection[Waypoint] {
107return krt.NewCollection(Gateways, func(ctx krt.HandlerContext, gateway *v1beta1.Gateway) *Waypoint {
108if len(gateway.Status.Addresses) == 0 {
109// gateway.Status.Addresses should only be populated once the Waypoint's deployment has at least 1 ready pod, it should never be removed after going ready
110// ignore Kubernetes Gateways which aren't waypoints
111return nil
112}
113return &Waypoint{
114Named: krt.NewNamed(gateway),
115Addresses: getGatewayAddrs(gateway),
116}
117}, krt.WithName("Waypoints"))
118}
119
120func getGatewayAddrs(gw *v1beta1.Gateway) []netip.Addr {
121// Currently, we only look at one address. Probably this should be made more robust
122ip, err := netip.ParseAddr(gw.Status.Addresses[0].Value)
123if err == nil {
124return []netip.Addr{ip}
125}
126log.Errorf("Unable to parse IP address in status of %v/%v/%v", gvk.KubernetesGateway, gw.Namespace, gw.Name)
127return nil
128}
129