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  }