Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 68 additions & 20 deletions fn.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest)
func buildRequirements(in *v1beta1.Input, xr *resource.Composite) (*fnv1.Requirements, error) { //nolint:gocyclo // Adding non-nil validations increases function complexity.
extraResources := make(map[string]*fnv1.ResourceSelector, len(in.Spec.ExtraResources))
for _, extraResource := range in.Spec.ExtraResources {
namespace, err := selectValue(extraResource.Namespace, xr)
if err != nil {
return nil, err
}
extraResName := extraResource.Into
switch extraResource.Type {
case v1beta1.ResourceSourceTypeReference, "":
Expand All @@ -128,29 +132,17 @@ func buildRequirements(in *v1beta1.Input, xr *resource.Composite) (*fnv1.Require
Match: &fnv1.ResourceSelector_MatchName{
MatchName: extraResource.Ref.Name,
},
Namespace: extraResource.Namespace,
Namespace: namespace,
}
case v1beta1.ResourceSourceTypeSelector:
matchLabels := map[string]string{}
for _, selector := range extraResource.Selector.MatchLabels {
switch selector.GetType() {
case v1beta1.ResourceSourceSelectorLabelMatcherTypeValue:
if selector.Value == nil {
return nil, errors.New("Value cannot be nil for type 'Value'")
}
matchLabels[selector.Key] = *selector.Value
case v1beta1.ResourceSourceSelectorLabelMatcherTypeFromCompositeFieldPath:
if selector.ValueFromFieldPath == nil {
return nil, errors.New("ValueFromFieldPath cannot be nil for type 'FromCompositeFieldPath'")
}
value, err := fieldpath.Pave(xr.Resource.Object).GetString(*selector.ValueFromFieldPath)
if err != nil {
if !selector.FromFieldPathIsOptional() {
return nil, errors.Wrapf(err, "cannot get value from field path %q", *selector.ValueFromFieldPath)
}
continue
}
matchLabels[selector.Key] = value
value, err := selectValueWithPolicy(&selector.ValueSelectorWithPolicy, xr)
if err != nil {
return nil, err
}
if value != nil {
matchLabels[selector.Key] = *value
}
}
if len(matchLabels) == 0 {
Expand All @@ -162,7 +154,7 @@ func buildRequirements(in *v1beta1.Input, xr *resource.Composite) (*fnv1.Require
Match: &fnv1.ResourceSelector_MatchLabels{
MatchLabels: &fnv1.MatchLabels{Labels: matchLabels},
},
Namespace: extraResource.Namespace,
Namespace: namespace,
}
}
}
Expand Down Expand Up @@ -286,3 +278,59 @@ func sortExtrasByFieldPath(extras []resource.Required, path string) error { //no
}
return nil
}

func selectValue(selector *v1beta1.ValueSelector, xr *resource.Composite) (*string, error) {
if selector == nil {
return nil, nil
}

t := selector.GetType()
switch t {
case v1beta1.ValueSelectorTypeValue:
return selectValueFromValue(selector.ValueFromValue)
case v1beta1.ValueSelectorTypeFromCompositeFieldPath:
return selectValueFromFieldPath(selector.ValueFromCompositeFieldPath, xr)
}
return nil, errors.Errorf("unknown value selector type %q", t)
}

func selectValueWithPolicy(selector *v1beta1.ValueSelectorWithPolicy, xr *resource.Composite) (*string, error) {
if selector == nil {
return nil, nil
}

t := selector.GetType()
switch t {
case v1beta1.ValueSelectorTypeValue:
return selectValueFromValue(selector.ValueFromValue)
case v1beta1.ValueSelectorTypeFromCompositeFieldPath:
return selectValueFromFieldPathWithPolicy(selector.ValueFromCompositeFieldPathWithPolicy, xr)
}
return nil, errors.Errorf("unknown value selector type %q", t)
}

func selectValueFromFieldPathWithPolicy(selector v1beta1.ValueFromCompositeFieldPathWithPolicy, xr *resource.Composite) (*string, error) {
value, err := selectValueFromFieldPath(selector.ValueFromCompositeFieldPath, xr)
if err != nil && !selector.FromFieldPathIsOptional() {
return nil, err
}
return value, nil
}

func selectValueFromFieldPath(selector v1beta1.ValueFromCompositeFieldPath, xr *resource.Composite) (*string, error) {
if selector.ValueFromFieldPath == nil {
return nil, errors.New("ValueFromFieldPath cannot be nil for type 'FromCompositeFieldPath'")
}
value, err := fieldpath.Pave(xr.Resource.Object).GetString(*selector.ValueFromFieldPath)
if err != nil {
return nil, errors.Wrapf(err, "cannot get value from field path %q", *selector.ValueFromFieldPath)
}
return &value, nil
}

func selectValueFromValue(selector v1beta1.ValueFromValue) (*string, error) {
if selector.Value == nil {
return nil, errors.New("Value cannot be nil for type 'Value'")
}
return selector.Value, nil
}
66 changes: 62 additions & 4 deletions fn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ func TestRunFunction(t *testing.T) {
"apiVersion": "test.crossplane.io/v1alpha1",
"kind": "XR",
"metadata": {
"name": "my-xr"
"name": "my-xr",
"namespace": "xr-namespace"
},
"spec": {
"existingEnvSelectorLabel": "someMoreBar",
Expand Down Expand Up @@ -126,7 +127,10 @@ func TestRunFunction(t *testing.T) {
"type": "Reference",
"kind": "Foo",
"apiVersion": "test.crossplane.io/v1alpha1",
"namespace": "my-namespace",
"namespace": {
"type": "Value",
"value": "my-namespace"
},
"into": "obj-5",
"ref": {
"name": "my-foo"
Expand All @@ -136,7 +140,10 @@ func TestRunFunction(t *testing.T) {
"type": "Selector",
"kind": "Bar",
"apiVersion": "test.crossplane.io/v1alpha1",
"namespace": "my-namespace",
"namespace": {
"type": "FromCompositeFieldPath",
"valueFromFieldPath": "metadata.namespace"
},
"into": "obj-6",
"selector": {
"matchLabels": [
Expand Down Expand Up @@ -216,7 +223,7 @@ func TestRunFunction(t *testing.T) {
},
},
},
Namespace: ptr.To("my-namespace"),
Namespace: ptr.To("xr-namespace"),
},
},
},
Expand Down Expand Up @@ -600,6 +607,57 @@ func TestRunFunction(t *testing.T) {
},
},
},
"RequestNamespaceNotFoundRequired": {
reason: "The Function should return fatal if a required namespace is not found in composite",
args: args{
req: &fnv1.RunFunctionRequest{
Meta: &fnv1.RequestMeta{Tag: "hello"},
Observed: &fnv1.State{
Composite: &fnv1.Resource{
Resource: resource.MustStructJSON(`{
"apiVersion": "test.crossplane.io/v1alpha1",
"kind": "XR",
"metadata": {
"name": "my-xr"
}
}`),
},
},
Input: resource.MustStructJSON(`{
"apiVersion": "extra-resources.fn.crossplane.io/v1beta1",
"kind": "Input",
"spec": {
"extraResources": [
{
"type": "Reference",
"into": "obj-0",
"kind": "EnvironmentConfig",
"apiVersion": "apiextensions.crossplane.io/v1beta1",
"ref": {
"name": "my-env-config"
},
"namespace": {
"type": "FromCompositeFieldPath",
"valueFromFieldPath": "spec.namespace"
}
}
]
}
}`),
},
},
want: want{
rsp: &fnv1.RunFunctionResponse{
Meta: &fnv1.ResponseMeta{Tag: "hello", Ttl: durationpb.New(response.DefaultTTL)},
Results: []*fnv1.Result{
{
Severity: fnv1.Severity_SEVERITY_FATAL,
Target: fnv1.Target_TARGET_COMPOSITE.Enum(),
},
},
},
},
},
}

for name, tc := range cases {
Expand Down
88 changes: 59 additions & 29 deletions input/v1beta1/resource_select.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ type ResourceSource struct {
// APIVersion is the kubernetes API Version of the target extra resource(s).
APIVersion string `json:"apiVersion,omitempty"`

// Namespace is the namespace in which to look for the ExtraResource.
// Namespace selects the namespace in which to look for the ExtraResource.
// If not set, the resource is assumed to be cluster-scoped.
// +optional
Namespace *string `json:"namespace,omitempty"`
Namespace *ValueSelector `json:"namespace,omitempty"`

// Into is the key into which extra resources for this selector will be placed.
Into string `json:"into"`
Expand Down Expand Up @@ -136,58 +136,88 @@ func (e *ResourceSourceSelector) GetSortByFieldPath() string {
return e.SortByFieldPath
}

// ResourceSourceSelectorLabelMatcherType specifies where the value for a label comes from.
type ResourceSourceSelectorLabelMatcherType string
// ValueSelectorType specifies where a value comes from.
type ValueSelectorType string

const (
// ResourceSourceSelectorLabelMatcherTypeFromCompositeFieldPath extracts
// the label value from a composite fieldpath.
ResourceSourceSelectorLabelMatcherTypeFromCompositeFieldPath ResourceSourceSelectorLabelMatcherType = "FromCompositeFieldPath"
// ResourceSourceSelectorLabelMatcherTypeValue uses a literal as label
// value.
ResourceSourceSelectorLabelMatcherTypeValue ResourceSourceSelectorLabelMatcherType = "Value"
// ValueSelectorTypeFromCompositeFieldPath extracts the value from a composite
// fieldpath.
ValueSelectorTypeFromCompositeFieldPath ValueSelectorType = "FromCompositeFieldPath"
// ValueSelectorTypeValue uses a literal as value.
ValueSelectorTypeValue ValueSelectorType = "Value"
)

// An ResourceSourceSelectorLabelMatcher acts like a k8s label selector but
// can draw the label value from a different path.
// can draw the label value from a different path. When using
// FromCompositeFieldPath with fromFieldPathPolicy Optional, and a field is not
// found in the composite resource, that label pair will just be skipped. N.B.
// other specified label matchers will still be used to retrieve the desired
// resource config, if any.
type ResourceSourceSelectorLabelMatcher struct {
// Type specifies where the value for a label comes from.
// Key of the label to match.
Key string `json:"key"`

ValueSelectorWithPolicy `json:",inline"`
}

// ValueSelectorBase is the base of ValueSelector and ValueSelectorWithPolicy,
// containing the Type field and all value sources that do not have a
// with-policy variant.
type ValueSelectorBase struct {
// Type specifies where the value comes from.
// +optional
// +kubebuilder:validation:Enum=FromCompositeFieldPath;Value
// +kubebuilder:default=FromCompositeFieldPath
Type ResourceSourceSelectorLabelMatcherType `json:"type,omitempty"`
Type ValueSelectorType `json:"type,omitempty"`

// Key of the label to match.
Key string `json:"key"`
ValueFromValue `json:",inline"`
}

// ValueFromFieldPath specifies the field path to look for the label value.
// ValueSelector is a value selector that must return a value.
type ValueSelector struct {
ValueSelectorBase `json:",inline"`
ValueFromCompositeFieldPath `json:",inline"`
}

// ValueSelectorWithPolicy is a value selector that may optionally return a
// value when the selection policy is Optional.
type ValueSelectorWithPolicy struct {
ValueSelectorBase `json:",inline"`
ValueFromCompositeFieldPathWithPolicy `json:",inline"`
}

// A ValueFromCompositeFieldPath draws a value from a field path.
type ValueFromCompositeFieldPath struct {
// ValueFromFieldPath specifies the field path to look for the value.
ValueFromFieldPath *string `json:"valueFromFieldPath,omitempty"`
}

// A ValueFromCompositeFieldPathWithPolicy draws a value from a field path.
type ValueFromCompositeFieldPathWithPolicy struct {
ValueFromCompositeFieldPath `json:",inline"`

// FromFieldPathPolicy specifies the policy for the valueFromFieldPath.
// The default is Required, meaning that an error will be returned if the
// field is not found in the composite resource.
// Optional means that if the field is not found in the composite resource,
// that label pair will just be skipped. N.B. other specified label
// matchers will still be used to retrieve the desired
// resource config, if any.
// The default is Required.
// +kubebuilder:validation:Enum=Optional;Required
// +kubebuilder:default=Required
FromFieldPathPolicy *FromFieldPathPolicy `json:"fromFieldPathPolicy,omitempty"`
}

// Value specifies a literal label value.
// A ValueFromValue is a literal value.
type ValueFromValue struct {
// Value specifies a literal value.
Value *string `json:"value,omitempty"`
}

// FromFieldPathIsOptional returns true if the FromFieldPathPolicy is set to
// +optional
func (e *ResourceSourceSelectorLabelMatcher) FromFieldPathIsOptional() bool {
// FromFieldPathIsOptional returns true if the FromFieldPathPolicy is set to Optional.
func (e *ValueFromCompositeFieldPathWithPolicy) FromFieldPathIsOptional() bool {
return e.FromFieldPathPolicy != nil && *e.FromFieldPathPolicy == FromFieldPathPolicyOptional
}

// GetType returns the type of the label matcher, returning the default if not set.
func (e *ResourceSourceSelectorLabelMatcher) GetType() ResourceSourceSelectorLabelMatcherType {
// GetType returns the type of the value selector, returning the default if not set.
func (e *ValueSelectorBase) GetType() ValueSelectorType {
if e == nil || e.Type == "" {
return ResourceSourceSelectorLabelMatcherTypeFromCompositeFieldPath
return ValueSelectorTypeFromCompositeFieldPath
}
return e.Type
}
Expand Down
Loading