Adding ManagedResolver
Signed-off-by: soorena776 <javad@upbound.io>
This commit is contained in:
parent
1c6cccad93
commit
ee01116db1
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
Copyright 2019 The Crossplane Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
const (
|
||||
attributeReferencerTagName = "attributereferencer"
|
||||
errPanicedResolving = "paniced while resolving references: %v"
|
||||
)
|
||||
|
||||
// Error strings
|
||||
const (
|
||||
errTaggedFieldlNotImplemented = "ManagedReferenceResolver: the field has the %v tag, but has not implemented AttributeReferencer interface"
|
||||
errBuildAttribute = "ManagedReferenceResolver: could not build the attribute"
|
||||
errAssignAttribute = "ManagedReferenceResolver: could not assign the attribute"
|
||||
)
|
||||
|
||||
// fieldHasTagPair is used in findAttributeReferencerFields
|
||||
type fieldHasTagPair struct {
|
||||
fieldValue reflect.Value
|
||||
hasTheTag bool
|
||||
}
|
||||
|
||||
// NotReadyError is a custom error interface, that indicates the resource is not ready
|
||||
type NotReadyError interface {
|
||||
error
|
||||
|
||||
resources() []string
|
||||
}
|
||||
|
||||
type resourceNotReadyErr struct {
|
||||
items []string
|
||||
}
|
||||
|
||||
// NewNotReadyErr returns a new NotReadyError
|
||||
func NewNotReadyErr(items []string) NotReadyError {
|
||||
return &resourceNotReadyErr{items}
|
||||
}
|
||||
|
||||
func (r *resourceNotReadyErr) Error() string {
|
||||
return fmt.Sprintf("These resources are not ready to be referenced: %s", r.items)
|
||||
}
|
||||
|
||||
func (r *resourceNotReadyErr) resources() []string {
|
||||
return r.items
|
||||
}
|
||||
|
||||
// AttributeReferencer is an interface for referencing and resolving
|
||||
// cross-resource attribute references. See
|
||||
// https://github.com/crossplaneio/crossplane/blob/master/design/one-pager-cross-resource-referencing.md
|
||||
// for more information
|
||||
type AttributeReferencer interface {
|
||||
|
||||
// ValidateReady validates that the referenced managed resource type has the
|
||||
// 'Ready' condition type and it is 'True'. If the returned error is nil,
|
||||
// the resource is interpretted to be `Ready`. Otherwise, if the error type
|
||||
// is `NotReadyError`, then the reference resolution status is updated as
|
||||
// `ReferenceResolutionBlocked`, otherwise it updates the status as
|
||||
// `ReconcileError`
|
||||
ValidateReady(context.Context, Managed, client.Reader) error
|
||||
|
||||
// Build retrieves referenced resource, as well as other non-managed
|
||||
// resources (like a `Provider`), and builds the referenced attribute
|
||||
Build(context.Context, Managed, client.Reader) (string, error)
|
||||
|
||||
// Assign accepts a managed resource object, and assigns the given value to the
|
||||
// corresponding property
|
||||
Assign(Managed, string) error
|
||||
}
|
||||
|
||||
// A ManagedReferenceResolver resolves the references to other managed
|
||||
// resources, by looking them up in the Kubernetes API server. The references
|
||||
// are the fields in the managed resource that implement AttributeReferencer
|
||||
// interface and have
|
||||
// `attributeReferencerTagName:"managedResourceStructTagPackageName"` tag
|
||||
type ManagedReferenceResolver interface {
|
||||
ResolveReferences(context.Context, Managed) error
|
||||
}
|
||||
|
||||
// APIManagedReferenceResolver resolves implements ManagedReferenceResolver interface
|
||||
type APIManagedReferenceResolver struct {
|
||||
reader client.Reader
|
||||
}
|
||||
|
||||
// NewReferenceResolver returns a new APIManagedReferenceResolver
|
||||
func NewReferenceResolver(c client.Reader) *APIManagedReferenceResolver {
|
||||
return &APIManagedReferenceResolver{c}
|
||||
}
|
||||
|
||||
// ResolveReferences resolves references made to other managed resources
|
||||
func (r *APIManagedReferenceResolver) ResolveReferences(ctx context.Context, mg Managed) (err error) {
|
||||
// retrieve all the referencer fields from the managed resource
|
||||
referencers, err := findAttributeReferencerFields(mg, false)
|
||||
if err != nil {
|
||||
// if there is an error it should immediately panic, since this means an
|
||||
// attribute is tagged but doesn't implement AttributeReferencer
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// this recovers from potential panics during execution of
|
||||
// AttributeReferencer methods
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.Errorf(errPanicedResolving, r)
|
||||
}
|
||||
}()
|
||||
|
||||
// make sure that all the references are ready
|
||||
for _, referencer := range referencers {
|
||||
if err := referencer.ValidateReady(ctx, mg, r.reader); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// build and assign the attributes
|
||||
for _, referencer := range referencers {
|
||||
val, err := referencer.Build(ctx, mg, r.reader)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, errBuildAttribute)
|
||||
}
|
||||
|
||||
if err := referencer.Assign(mg, val); err != nil {
|
||||
return errors.WithMessage(err, errAssignAttribute)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// findAttributeReferencerFields recursively finds all non-nil fields in a struct and its sub types
|
||||
// that implement AttributeReferencer and have `attributeReferencerTagName:"managedResourceStructTagPackageName"` tag
|
||||
func findAttributeReferencerFields(obj interface{}, objHasTheRightTag bool) ([]AttributeReferencer, error) {
|
||||
pairs := []fieldHasTagPair{}
|
||||
v := reflect.ValueOf(obj)
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
pairs = append(pairs, fieldHasTagPair{v.Elem(), objHasTheRightTag})
|
||||
|
||||
case reflect.Struct:
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
pairs = append(pairs, fieldHasTagPair{v.Field(i), hasAttrRefTag(reflect.TypeOf(obj).Field(i))})
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
pairs = append(pairs, fieldHasTagPair{v.Index(i), objHasTheRightTag})
|
||||
}
|
||||
}
|
||||
|
||||
return inspectFields(pairs)
|
||||
}
|
||||
|
||||
// inspectFields along with findAttributeReferencerFields it recursively
|
||||
// inspects the extracted struct fields, and returns the ones that are of type
|
||||
// `AttributeReferencer`.
|
||||
func inspectFields(pairs []fieldHasTagPair) ([]AttributeReferencer, error) {
|
||||
result := []AttributeReferencer{}
|
||||
for _, pair := range pairs {
|
||||
if !pair.fieldValue.CanInterface() {
|
||||
if pair.hasTheTag {
|
||||
// if the field has the tag but its value cannot be converted to
|
||||
// an `Interface{}` (like a struct with private fields) it can't
|
||||
// possibly implement the method sets. returning error here
|
||||
// since, there won't be a recursive call for this value
|
||||
return nil, errors.Errorf(errTaggedFieldlNotImplemented, attributeReferencerTagName)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if pair.hasTheTag {
|
||||
if ar, implements := pair.fieldValue.Interface().(AttributeReferencer); implements {
|
||||
if !pair.fieldValue.IsNil() {
|
||||
result = append(result, ar)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// this is for the case where a tag is assigned to a struct, but it
|
||||
// doesn't implement the interface
|
||||
if pair.fieldValue.Kind() == reflect.Struct {
|
||||
return nil, errors.Errorf(errTaggedFieldlNotImplemented, attributeReferencerTagName)
|
||||
}
|
||||
}
|
||||
|
||||
resolvers, err := findAttributeReferencerFields(pair.fieldValue.Interface(), pair.hasTheTag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, resolvers...)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// hasAttrRefTag returns true if the given struct field has the
|
||||
// AttributeReference tag
|
||||
func hasAttrRefTag(field reflect.StructField) bool {
|
||||
val, ok := field.Tag.Lookup(managedResourceStructTagPackageName)
|
||||
return ok && (val == attributeReferencerTagName)
|
||||
}
|
||||
Loading…
Reference in New Issue