Skip to content

Commit

Permalink
fix nested status update
Browse files Browse the repository at this point in the history
Signed-off-by: ezgidemirel <[email protected]>
  • Loading branch information
ezgidemirel committed Oct 30, 2023
1 parent a618a92 commit fecf0fe
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 47 deletions.
3 changes: 3 additions & 0 deletions examples/composition-fs.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# This composition expects to find the templates in filesystem.
# You can create a configmap and mount it using DeploymentRuntimeConfig.
# Please check the examples/functions.yaml file for an example.
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
Expand Down
29 changes: 15 additions & 14 deletions examples/functions.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
name: function-go-templating
spec:
package: xpkg.upbound.io/ezgi/function-go-templating:v0.0.1 # TODO(ezgidemirel): Update package
packagePullPolicy: Always
# Please uncomment the following lines if you want to mount the templates from a config map with the following DeploymentRuntimeConfig resource.
# runtimeConfigRef:
# name: mount-templates

---

apiVersion: pkg.crossplane.io/v1beta1
kind: DeploymentRuntimeConfig
metadata:
Expand All @@ -16,23 +28,12 @@ spec:
name: templates
readOnly: true
volumes:
- name: templates
configMap:
name: templates
- name: templates
configMap:
name: templates

---

apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
name: function-go-templating
spec:
package: xpkg.upbound.io/ezgi/function-go-templating:v0.0.1 # TODO(ezgidemirel): Update package
packagePullPolicy: Always
runtimeConfigRef:
name: mount-templates

---
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
Expand Down
54 changes: 25 additions & 29 deletions fn.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"bytes"
"context"
"dario.cat/mergo"
"encoding/base64"
"fmt"
"io"
Expand Down Expand Up @@ -108,14 +109,14 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ
}

// Get the desired composite resource from the request.
dxr, err := request.GetDesiredCompositeResource(req)
desiredComposite, err := request.GetDesiredCompositeResource(req)
if err != nil {
response.Fatal(rsp, errors.Wrap(err, "cannot get desired composite resource"))
return rsp, nil
}

// Get the desired composed resources from the request.
dcd, err := request.GetDesiredComposedResources(req)
desiredComposed, err := request.GetDesiredComposedResources(req)
if err != nil {
response.Fatal(rsp, errors.Wrap(err, "cannot get desired composed resources"))
return rsp, nil
Expand All @@ -126,10 +127,11 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ
cd := resource.NewDesiredComposed()
cd.Resource.Unstructured = *obj.DeepCopy()

// TODO(ezgidemirel): Refactor to reduce cyclomatic complexity.
// Update only the status of the desired composite resource.
if cd.Resource.GetAPIVersion() == dxr.Resource.GetAPIVersion() && cd.Resource.GetKind() == dxr.Resource.GetKind() {
if cd.Resource.GetAPIVersion() == desiredComposite.Resource.GetAPIVersion() && cd.Resource.GetKind() == desiredComposite.Resource.GetKind() {
dst := make(map[string]any)
if err := dxr.Resource.GetValueInto("status", &dst); err != nil && !fieldpath.IsNotFound(err) {
if err := desiredComposite.Resource.GetValueInto("status", &dst); err != nil && !fieldpath.IsNotFound(err) {
response.Fatal(rsp, errors.Wrap(err, "cannot get existing composite status"))
return rsp, nil
}
Expand All @@ -140,27 +142,28 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ
return rsp, nil
}

for k, v := range src {
fmt.Println(k, v)
dst[k] = v
if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
response.Fatal(rsp, errors.Wrap(err, "cannot merge desired composite status"))
return rsp, nil
}

if err := fieldpath.Pave(dxr.Resource.Object).SetValue("status", dst); err != nil {
if err := fieldpath.Pave(desiredComposite.Resource.Object).SetValue("status", dst); err != nil {
response.Fatal(rsp, errors.Wrap(err, "cannot set desired composite status"))
return rsp, nil
}

continue
}

// TODO(ezgidemirel): Refactor to reduce cyclomatic complexity.
// Set composite resource's connection details.
if cd.Resource.GetAPIVersion() == metaApiVersion {
switch obj.GetKind() {
case "CompositeConnectionDetails":
con, _ := cd.Resource.GetStringObject("data")
for k, v := range con {
d, _ := base64.StdEncoding.DecodeString(v) //nolint:errcheck // k8s returns secret values encoded
dxr.ConnectionDetails[k] = d
desiredComposite.ConnectionDetails[k] = d
}
default:
response.Fatal(rsp, fmt.Errorf(errFmtInvalidMetaType, obj.GetKind()))
Expand All @@ -170,6 +173,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ
continue
}

// TODO(ezgidemirel): Refactor to reduce cyclomatic complexity.
// Set ready state.
if v, found := cd.Resource.GetAnnotations()[annotationKeyReady]; found {
if v != string(resource.ReadyTrue) && v != string(resource.ReadyUnspecified) && v != string(resource.ReadyFalse) {
Expand All @@ -179,32 +183,32 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ

cd.Ready = fn.Ready(v)

// remove meta annotation
// Remove meta annotation.
meta.RemoveAnnotations(cd.Resource, annotationKeyReady)
}

// Remove resource name annotation.
meta.RemoveAnnotations(cd.Resource, annotationKeyCompositionResourceName)

// Add resource to the desired composed resources map.
name, err := getCompositionResourceName(obj)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot get composition resource name of %s", obj.GetName()))
name, found := obj.GetAnnotations()[annotationKeyCompositionResourceName]
if !found {
response.Fatal(rsp, errors.Errorf("cannot get composition resource name of %s", obj.GetName()))
return rsp, nil
}

// remove resource name annotation
meta.RemoveAnnotations(cd.Resource, annotationKeyCompositionResourceName)

dcd[name] = cd
desiredComposed[resource.Name(name)] = cd
}

f.log.Debug("desired composite resource", "desiredComposite:", dxr)
f.log.Debug("constructed desired composed resources", "desiredComposed:", dcd)
f.log.Debug("desired composite resource", "desiredComposite:", desiredComposite)
f.log.Debug("constructed desired composed resources", "desiredComposed:", desiredComposed)

if err := response.SetDesiredComposedResources(rsp, dcd); err != nil {
if err := response.SetDesiredComposedResources(rsp, desiredComposed); err != nil {
response.Fatal(rsp, errors.Wrap(err, "cannot desired composed resources"))
return rsp, nil
}

if err := response.SetDesiredCompositeResource(rsp, dxr); err != nil {
if err := response.SetDesiredCompositeResource(rsp, desiredComposite); err != nil {
response.Fatal(rsp, errors.Wrap(err, "cannot set desired composite resource"))
return rsp, nil
}
Expand All @@ -227,11 +231,3 @@ func convertToMap(req *fnv1beta1.RunFunctionRequest) (map[string]any, error) {

return mReq, nil
}

func getCompositionResourceName(obj *unstructured.Unstructured) (resource.Name, error) {
if v, found := obj.GetAnnotations()[annotationKeyCompositionResourceName]; found {
return resource.Name(v), nil
}

return "", errors.Errorf("%s annotation not found", annotationKeyCompositionResourceName)
}
47 changes: 44 additions & 3 deletions fn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ var (
metaResourceInvalid = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"InvalidMeta"}`
metaResourceConDet = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"CompositeConnectionDetails","data":{"key":"dmFsdWU="}}` // encoded string "value"

xr = `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"name":"cool-xr"},"spec":{"count":2}}`
xrWithStatus = `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"name":"cool-xr"},"spec":{"count":2},"status":{"ready":"true"}}`
xr = `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"name":"cool-xr"},"spec":{"count":2}}`
xrWithStatus = `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"name":"cool-xr"},"spec":{"count":2},"status":{"ready":"true"}}`
xrWithNestedStatusFoo = `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"name":"cool-xr"},"spec":{"count":2},"status":{"state":{"foo":"bar"}}}`
xrWithNestedStatusBaz = `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"name":"cool-xr"},"spec":{"count":2},"status":{"state":{"baz":"qux"}}}`

path = "testdata/templates"
wrongPath = "testdata/wrong"
Expand Down Expand Up @@ -109,7 +111,7 @@ func TestRunFunction(t *testing.T) {
Results: []*fnv1beta1.Result{
{
Severity: fnv1beta1.Severity_SEVERITY_FATAL,
Message: fmt.Sprintf("cannot get composition resource name of cool-cd: %s annotation not found", annotationKeyCompositionResourceName),
Message: "cannot get composition resource name of cool-cd",
},
},
},
Expand Down Expand Up @@ -309,6 +311,45 @@ func TestRunFunction(t *testing.T) {
},
},
},
"UpdateDesiredCompositeNestedStatus": {
reason: "The Function should update the desired composite resource nested status.",
args: args{
req: &fnv1beta1.RunFunctionRequest{
Meta: &fnv1beta1.RequestMeta{Tag: "status"},
Input: resource.MustStructObject(
&v1beta1.Input{
Source: v1beta1.InlineSource,
Inline: &v1beta1.InputSourceInline{Template: xrWithNestedStatusBaz},
}),
Observed: &fnv1beta1.State{
Composite: &fnv1beta1.Resource{
Resource: resource.MustStructJSON(xrWithNestedStatusFoo),
},
},
Desired: &fnv1beta1.State{
Composite: &fnv1beta1.Resource{
Resource: resource.MustStructJSON(xrWithNestedStatusFoo),
},
},
},
},
want: want{
rsp: &fnv1beta1.RunFunctionResponse{
Meta: &fnv1beta1.ResponseMeta{Tag: "status", Ttl: durationpb.New(response.DefaultTTL)},
Results: []*fnv1beta1.Result{
{
Severity: fnv1beta1.Severity_SEVERITY_NORMAL,
Message: fmt.Sprintf("Successful run with %q source", v1beta1.InlineSource),
},
},
Desired: &fnv1beta1.State{
Composite: &fnv1beta1.Resource{
Resource: resource.MustStructJSON(`{"apiVersion":"example.org/v1","kind":"XR","metadata":{"name":"cool-xr"},"spec":{"count":2},"status":{"state":{"foo":"bar","baz":"qux"}}}`),
},
},
},
},
},
"ResponseIsReturnedWithTemplatingFS": {
reason: "The Function should return the desired composite resource and the templated composed resources with FileSystem cd.",
args: args{
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/crossplane-contrib/function-go-templating
go 1.20

require (
dario.cat/mergo v1.0.0
github.com/alecthomas/kong v0.8.1
github.com/crossplane/crossplane-runtime v1.14.0-rc.1
github.com/crossplane/function-sdk-go v0.0.0-20231025230156-142a5c0bc9cc
Expand All @@ -14,7 +15,6 @@ require (
)

require (
dario.cat/mergo v1.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
Expand Down

0 comments on commit fecf0fe

Please sign in to comment.