Skip to content
Merged
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
36 changes: 26 additions & 10 deletions fn.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,22 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest)
return rsp, nil
}

func getSelectedEnvConfigs(in *v1beta1.Input, requiredResources map[string][]resource.Required) (envConfigs []unstructured.Unstructured, err error) {
func getSelectedEnvConfigs(in *v1beta1.Input, requiredResources map[string][]resource.Required) (map[string][]unstructured.Unstructured, error) {
envConfigs := make(map[string][]unstructured.Unstructured)

for i, config := range in.Spec.EnvironmentConfigs {
extraResName := fmt.Sprintf("environment-config-%d", i)
resources, ok := requiredResources[extraResName]
if !ok {
// Skip if the required resource was not requested (e.g., optional selector with no matchLabels)
continue
}

toFieldPath := ""
if config.ToFieldPath != nil {
toFieldPath = *config.ToFieldPath
}

switch config.GetType() {
case v1beta1.EnvironmentSourceTypeReference:
out, err := processSourceByReference(in, config, resources)
Expand All @@ -152,15 +160,15 @@ func getSelectedEnvConfigs(in *v1beta1.Input, requiredResources map[string][]res
if out == nil {
continue
}
envConfigs = append(envConfigs, *out)
envConfigs[toFieldPath] = append(envConfigs[toFieldPath], *out)

case v1beta1.EnvironmentSourceTypeSelector:
out, err := processEnvironmentSource(config, resources)
if err != nil {
return nil, errors.Wrapf(err, "cannot process environment config %q by selector", extraResName)
}
if len(out) > 0 {
envConfigs = append(envConfigs, out...)
envConfigs[toFieldPath] = append(envConfigs[toFieldPath], out...)
}
}
}
Expand Down Expand Up @@ -352,15 +360,23 @@ func buildRequirements(in *v1beta1.Input, xr *resource.Composite) (*fnv1.Require
return &fnv1.Requirements{Resources: resources}, nil
}

func mergeEnvConfigsData(configs []unstructured.Unstructured) (map[string]any, error) {
func mergeEnvConfigsData(configsByField map[string][]unstructured.Unstructured) (map[string]any, error) {
merged := map[string]any{}
for _, c := range configs {
data := map[string]any{}
if err := fieldpath.Pave(c.Object).GetValueInto("data", &data); err != nil {
return nil, errors.Wrapf(err, "cannot get data from environment config %q", c.GetName())
}
for fieldPath, configs := range configsByField {
for _, c := range configs {
data := map[string]any{}
if fieldPath != "" {
if err := fieldpath.Pave(data).SetValue(fieldPath, c.Object["data"]); err != nil {
return nil, errors.Errorf("cannot get data from environment config %s into path %q", c.GetName(), fieldPath)
}
} else {
if err := fieldpath.Pave(c.Object).GetValueInto("data", &data); err != nil {
return nil, errors.Wrapf(err, "cannot get data from environment config %q", c.GetName())
}
}

merged = mergeMaps(merged, data)
merged = mergeMaps(merged, data)
}
}
return merged, nil
}
Expand Down
130 changes: 130 additions & 0 deletions fn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,136 @@ func TestRunFunction(t *testing.T) {
},
},
},
"ToFieldPath": {
reason: "The Function should load into the specified toFieldPath",
args: args{
req: &fnv1.RunFunctionRequest{
Meta: &fnv1.RequestMeta{Tag: "hello"},
Input: resource.MustStructJSON(`{
"apiVersion": "template.fn.crossplane.io/v1beta1",
"kind": "Input",
"spec": {
"defaultData": {
"a": "from-default"
},
"environmentConfigs": [
{
"type": "Reference",
"ref": {
"name": "foo"
},
"toFieldPath": "foo"
},
{
"type": "Selector",
"selector": {
"mode": "Multiple",
"matchLabels": [
{
"type": "Value",
"key": "foo",
"value": "bar"
}
]
},
"toFieldPath": "foo.bar"
}
]
}
}`),
RequiredResources: map[string]*fnv1.Resources{
"environment-config-0": {
Items: []*fnv1.Resource{
{
Resource: resource.MustStructJSON(`{
"apiVersion": "apiextensions.crossplane.io/v1beta1",
"kind": "EnvironmentConfig",
"metadata": {
"name": "foo"
},
"data": {
"a": "from-foo"
}
}`),
},
},
},
"environment-config-1": {
Items: []*fnv1.Resource{
{
Resource: resource.MustStructJSON(`{
"apiVersion": "apiextensions.crossplane.io/v1beta1",
"kind": "EnvironmentConfig",
"metadata": {
"name": "first"
},
"data": {
"a": "from-label-select-first"
}
}`),
},
{
Resource: resource.MustStructJSON(`{
"apiVersion": "apiextensions.crossplane.io/v1beta1",
"kind": "EnvironmentConfig",
"metadata": {
"name": "second"
},
"data": {
"b": "from-label-select-second"
}
}`),
},
},
},
},
},
},
want: want{
rsp: &fnv1.RunFunctionResponse{
Meta: &fnv1.ResponseMeta{Tag: "hello", Ttl: durationpb.New(response.DefaultTTL)},
Results: []*fnv1.Result{},
Requirements: &fnv1.Requirements{
Resources: map[string]*fnv1.ResourceSelector{
"environment-config-0": {
ApiVersion: "apiextensions.crossplane.io/v1beta1",
Kind: "EnvironmentConfig",
Match: &fnv1.ResourceSelector_MatchName{
MatchName: "foo",
},
},
"environment-config-1": {
ApiVersion: "apiextensions.crossplane.io/v1beta1",
Kind: "EnvironmentConfig",
Match: &fnv1.ResourceSelector_MatchLabels{
MatchLabels: &fnv1.MatchLabels{
Labels: map[string]string{
"foo": "bar",
},
},
},
},
},
},
Context: &structpb.Struct{
Fields: map[string]*structpb.Value{
FunctionContextKeyEnvironment: structpb.NewStructValue(resource.MustStructJSON(`{
"apiVersion": "internal.crossplane.io/v1alpha1",
"kind": "Environment",
"a": "from-default",
"foo": {
"a": "from-foo",
"bar": {
"a": "from-label-select-first",
"b": "from-label-select-second"
}
}
}`)),
},
},
},
},
},
}

for name, tc := range cases {
Expand Down
4 changes: 4 additions & 0 deletions input/v1beta1/composition_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ type EnvironmentSource struct {
// Selector selects EnvironmentConfig(s) via labels.
// +optional
Selector *EnvironmentSourceSelector `json:"selector,omitempty"`

// ToFieldPath specifies where in the environment to load the EnvironmentConfig(s).
// +optional
ToFieldPath *string `json:"toFieldPath,omitempty"`
}

// GetType returns the type of the environment source, returning the default if not set.
Expand Down
Loading