knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/resolver/addressable_resolver.go (about) 1 /* 2 Copyright 2019 The Knative Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package resolver 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 24 "k8s.io/apimachinery/pkg/runtime/schema" 25 "k8s.io/client-go/tools/cache" 26 27 "knative.dev/pkg/client/injection/ducks/duck/v1/addressable" 28 "knative.dev/pkg/controller" 29 "knative.dev/pkg/network" 30 31 corev1 "k8s.io/api/core/v1" 32 apierrs "k8s.io/apimachinery/pkg/api/errors" 33 "k8s.io/apimachinery/pkg/api/meta" 34 35 "knative.dev/pkg/apis" 36 pkgapisduck "knative.dev/pkg/apis/duck" 37 duckv1 "knative.dev/pkg/apis/duck/v1" 38 duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1" 39 "knative.dev/pkg/tracker" 40 ) 41 42 // RefResolverFunc resolves ObjectReferences into a URI. 43 // It returns true when it handled the reference, in which case it also returns the resolved URI or an error. 44 type RefResolverFunc func(ctx context.Context, ref *corev1.ObjectReference) (bool, *apis.URL, error) 45 46 // URIResolver resolves Destinations and ObjectReferences into a URI. 47 type URIResolver struct { 48 tracker tracker.Interface 49 listerFactory func(schema.GroupVersionResource) (cache.GenericLister, error) 50 resolvers []RefResolverFunc 51 } 52 53 // NewURIResolverFromTracker constructs a new URIResolver with context, a tracker and an optional list of custom resolvers. 54 func NewURIResolverFromTracker(ctx context.Context, t tracker.Interface, resolvers ...RefResolverFunc) *URIResolver { 55 ret := &URIResolver{ 56 tracker: t, 57 resolvers: resolvers, 58 } 59 60 informerFactory := &pkgapisduck.CachedInformerFactory{ 61 Delegate: &pkgapisduck.EnqueueInformerFactory{ 62 Delegate: addressable.Get(ctx), 63 EventHandler: controller.HandleAll(ret.tracker.OnChanged), 64 }, 65 } 66 67 ret.listerFactory = func(gvr schema.GroupVersionResource) (cache.GenericLister, error) { 68 _, l, err := informerFactory.Get(ctx, gvr) 69 return l, err 70 } 71 72 return ret 73 } 74 75 // URIFromDestination resolves a v1beta1.Destination into a URI string. 76 func (r *URIResolver) URIFromDestination(ctx context.Context, dest duckv1beta1.Destination, parent interface{}) (string, error) { 77 var deprecatedObjectReference *duckv1.KReference 78 if dest.DeprecatedAPIVersion != "" || dest.DeprecatedKind != "" || dest.DeprecatedName != "" || dest.DeprecatedNamespace != "" { 79 deprecatedObjectReference = &duckv1.KReference{ 80 Kind: dest.DeprecatedKind, 81 APIVersion: dest.DeprecatedAPIVersion, 82 Name: dest.DeprecatedName, 83 Namespace: dest.DeprecatedNamespace, 84 } 85 } 86 if dest.Ref != nil && deprecatedObjectReference != nil { 87 return "", errors.New("ref and [apiVersion, kind, name] can't be both present") 88 } 89 90 var ref *duckv1.KReference 91 if dest.Ref != nil { 92 ref = &duckv1.KReference{ 93 Kind: dest.Ref.Kind, 94 Namespace: dest.Ref.Namespace, 95 Name: dest.Ref.Name, 96 APIVersion: dest.Ref.APIVersion, 97 } 98 } else { 99 ref = deprecatedObjectReference 100 } 101 102 u, err := r.URIFromDestinationV1(ctx, duckv1.Destination{Ref: ref, URI: dest.URI, CACerts: dest.CACerts}, parent) 103 if err != nil { 104 return "", err 105 } 106 return u.String(), nil 107 } 108 109 // URIFromDestinationV1 resolves a v1.Destination into a URL. 110 func (r *URIResolver) URIFromDestinationV1(ctx context.Context, dest duckv1.Destination, parent interface{}) (*apis.URL, error) { 111 addr, err := r.AddressableFromDestinationV1(ctx, dest, parent) 112 if err != nil { 113 return nil, err 114 } 115 return addr.URL, nil 116 } 117 118 func (r *URIResolver) URIFromKReference(ctx context.Context, ref *duckv1.KReference, parent interface{}) (*apis.URL, error) { 119 dest := duckv1.Destination{ 120 Ref: ref, 121 } 122 addr, err := r.AddressableFromDestinationV1(ctx, dest, parent) 123 if err != nil { 124 return nil, err 125 } 126 return addr.URL, nil 127 } 128 129 // URIFromObjectReference resolves an ObjectReference to a URI string. 130 func (r *URIResolver) URIFromObjectReference(ctx context.Context, ref *corev1.ObjectReference, parent interface{}) (*apis.URL, error) { 131 if ref == nil { 132 return nil, apierrs.NewBadRequest("ref is nil") 133 } 134 dest := duckv1.KReference{ 135 Kind: ref.Kind, 136 Namespace: ref.Namespace, 137 Name: ref.Name, 138 APIVersion: ref.APIVersion, 139 } 140 return r.URIFromKReference(ctx, &dest, parent) 141 } 142 143 // AddressableFromDestinationV1 resolves a v1.Destination into a duckv1.Addressable. 144 func (r *URIResolver) AddressableFromDestinationV1(ctx context.Context, dest duckv1.Destination, parent interface{}) (*duckv1.Addressable, error) { 145 if dest.Ref != nil { 146 addr, err := r.addressableFromDestinationRef(ctx, dest, parent) 147 if err != nil { 148 return nil, err 149 } 150 if dest.URI != nil { 151 if dest.URI.URL().IsAbs() { 152 return nil, errors.New("absolute URI is not allowed when Ref or [apiVersion, kind, name] exists") 153 } 154 addr.URL = addr.URL.ResolveReference(dest.URI) 155 return addr, nil 156 } 157 return addr, nil 158 } 159 160 if dest.URI != nil { 161 // IsAbs check whether the URL has a non-empty scheme. Besides the non non-empty scheme, we also require dest.URI has a non-empty host 162 if !dest.URI.URL().IsAbs() || dest.URI.Host == "" { 163 return nil, fmt.Errorf("URI is not absolute (both scheme and host should be non-empty): %q", dest.URI.String()) 164 } 165 return &duckv1.Addressable{ 166 URL: dest.URI, 167 CACerts: dest.CACerts, 168 Audience: dest.Audience, 169 }, nil 170 } 171 172 return nil, errors.New("destination missing Ref and URI, expected at least one") 173 } 174 175 func (r *URIResolver) addressableFromDestinationRef(ctx context.Context, dest duckv1.Destination, parent interface{}) (*duckv1.Addressable, error) { 176 if dest.Ref == nil { 177 return nil, apierrs.NewBadRequest("ref is nil") 178 } 179 180 or := &corev1.ObjectReference{ 181 Kind: dest.Ref.Kind, 182 Namespace: dest.Ref.Namespace, 183 Name: dest.Ref.Name, 184 APIVersion: dest.Ref.APIVersion, 185 } 186 187 // try custom resolvers first 188 for _, resolver := range r.resolvers { 189 handled, url, err := resolver(ctx, or) 190 if handled { 191 return &duckv1.Addressable{ 192 Name: dest.Ref.Address, 193 URL: url, 194 CACerts: dest.CACerts, 195 Audience: dest.Audience, 196 }, err 197 } 198 199 // when handled is false, both url and err are ignored. 200 } 201 202 gvr, _ := meta.UnsafeGuessKindToResource(or.GroupVersionKind()) 203 if err := r.tracker.TrackReference(tracker.Reference{ 204 APIVersion: dest.Ref.APIVersion, 205 Kind: dest.Ref.Kind, 206 Namespace: dest.Ref.Namespace, 207 Name: dest.Ref.Name, 208 }, parent); err != nil { 209 return nil, fmt.Errorf("failed to track reference %s %s/%s: %w", gvr.String(), dest.Ref.Namespace, dest.Ref.Name, err) 210 } 211 212 lister, err := r.listerFactory(gvr) 213 if err != nil { 214 return nil, fmt.Errorf("failed to get lister for %s: %w", gvr.String(), err) 215 } 216 217 obj, err := lister.ByNamespace(dest.Ref.Namespace).Get(dest.Ref.Name) 218 if err != nil { 219 return nil, fmt.Errorf("failed to get object %s/%s: %w", dest.Ref.Namespace, dest.Ref.Name, err) 220 } 221 222 // K8s Services are special cased. They can be called, even though they do not satisfy the 223 // Callable interface. 224 if dest.Ref.APIVersion == "v1" && dest.Ref.Kind == "Service" { 225 url := &apis.URL{ 226 Scheme: "http", 227 Host: network.GetServiceHostname(dest.Ref.Name, dest.Ref.Namespace), 228 Path: "", 229 } 230 if dest.CACerts != nil && *dest.CACerts != "" { 231 url.Scheme = "https" 232 } 233 return &duckv1.Addressable{ 234 Name: dest.Ref.Address, 235 URL: url, 236 CACerts: dest.CACerts, 237 Audience: dest.Audience, 238 }, nil 239 } 240 241 addressable, ok := obj.(*duckv1.AddressableType) 242 if !ok { 243 return nil, apierrs.NewBadRequest(fmt.Sprintf("%s(%T) is not an AddressableType", dest.Ref, dest.Ref)) 244 } 245 246 addr, err := r.selectAddress(dest, addressable) 247 if err != nil { 248 return nil, err 249 } 250 // Do not modify informer copy. 251 addr = addr.DeepCopy() 252 253 if addr.URL == nil { 254 return nil, apierrs.NewBadRequest(fmt.Sprintf("URL missing in address of %s", dest.Ref)) 255 } 256 if addr.URL.Host == "" { 257 return nil, apierrs.NewBadRequest(fmt.Sprintf("hostname missing in address of %s", dest.Ref)) 258 } 259 260 if dest.CACerts != nil && *dest.CACerts != "" { 261 addr.CACerts = dest.CACerts 262 } 263 264 if dest.Audience != nil && *dest.Audience != "" { 265 // destinations audience takes preference 266 addr.Audience = dest.Audience 267 } 268 269 return addr, nil 270 } 271 272 // selectAddress selects a single address from the given AddressableType 273 func (r *URIResolver) selectAddress(dest duckv1.Destination, addressable *duckv1.AddressableType) (*duckv1.Addressable, error) { 274 if len(addressable.Status.Addresses) == 0 && addressable.Status.Address == nil { 275 return nil, apierrs.NewBadRequest(fmt.Sprintf("address not set for %s", dest.Ref)) 276 } 277 278 // if dest.ref.address is specified: 279 // - select the first (in order) address from status.addresses with name == dest.ref.address 280 // - If no address is found return an error explaining that the address with name dest.ref.address 281 // cannot be found/resolved 282 if dest.Ref.Address != nil && *dest.Ref.Address != "" { 283 for _, addr := range addressable.Status.Addresses { 284 if addr.Name != nil && *addr.Name == *dest.Ref.Address { 285 return &addr, nil 286 } 287 } 288 return nil, apierrs.NewBadRequest(fmt.Sprintf("address with name %q not found for %s", *dest.Ref.Address, dest.Ref)) 289 } 290 291 // if dest.ref.address is not specified: 292 // - select the first (in order) address from status.addresses 293 // - If no address is found in addresses use status.address 294 if len(addressable.Status.Addresses) > 0 { 295 return &addressable.Status.Addresses[0], nil 296 } 297 298 return addressable.Status.Address, nil 299 }