From b38124398d936109b541df5afd31709e2700ce78 Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Fri, 22 Nov 2024 17:58:58 -0600 Subject: [PATCH 1/9] wip Signed-off-by: Steven Borrelli --- claimconditions.go | 43 +++++++++++ example/conditions/README.md | 84 ++++++++++++++++++++++ example/conditions/composition.yaml | 49 +++++++++++++ example/conditions/environmentConfigs.yaml | 10 +++ example/conditions/functions.yaml | 25 +++++++ example/conditions/xr.yaml | 5 ++ example/conditions/xrd.yaml | 25 +++++++ fn.go | 12 ++++ go.mod | 4 +- go.sum | 21 +++--- 10 files changed, 265 insertions(+), 13 deletions(-) create mode 100644 claimconditions.go create mode 100644 example/conditions/README.md create mode 100644 example/conditions/composition.yaml create mode 100644 example/conditions/environmentConfigs.yaml create mode 100644 example/conditions/functions.yaml create mode 100644 example/conditions/xr.yaml create mode 100644 example/conditions/xrd.yaml diff --git a/claimconditions.go b/claimconditions.go new file mode 100644 index 0000000..b5b5789 --- /dev/null +++ b/claimconditions.go @@ -0,0 +1,43 @@ +package main + +import ( + xpruntimev1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/function-sdk-go/errors" + fnv1 "github.com/crossplane/function-sdk-go/proto/v1" + "github.com/crossplane/function-sdk-go/response" + "github.com/davecgh/go-spew/spew" + corev1 "k8s.io/api/core/v1" +) + +// A CompositionTarget is the target of a composition event or condition. +type CompositionTarget string + +// Composition event and condition targets. +const ( + CompositionTargetComposite CompositionTarget = "Composite" + CompositionTargetCompositeAndClaim CompositionTarget = "CompositeAndClaim" +) + +func UpdateClaimConditions(rsp *fnv1.RunFunctionResponse, conditions ...xpruntimev1.Condition) (*fnv1.RunFunctionResponse, error) { + for _, c := range conditions { + if xpruntimev1.IsSystemConditionType(c.Type) { + response.Fatal(rsp, errors.Errorf("cannot set ClaimCondition type: %s is a reserved Crossplane Condition", c.Type)) + return rsp, nil + } + var co *response.ConditionOption + switch c.Status { + case corev1.ConditionTrue: + co = response.ConditionTrue(rsp, string(c.Type), string(c.Reason)).WithMessage(c.Message) + case corev1.ConditionFalse: + co = response.ConditionFalse(rsp, string(c.Type), string(c.Reason)) + case corev1.ConditionUnknown: + co = response.ConditionFalse(rsp, string(c.Type), string(c.Reason)) + } + if c.Message != "" { + co.WithMessage(c.Message) + } + + spew.Dump(co) + } + return rsp, nil +} diff --git a/example/conditions/README.md b/example/conditions/README.md new file mode 100644 index 0000000..21587cd --- /dev/null +++ b/example/conditions/README.md @@ -0,0 +1,84 @@ +# Writing to the Function Context + +function-go-templating can write to the Function Context + +## Testing This Function Locally + +You can run your function locally and test it using [`crossplane render`](https://docs.crossplane.io/latest/cli/command-reference/#render) +with these example manifests. + +```shell +crossplane render \ + --extra-resources environmentConfigs.yaml \ + --include-context \ + xr.yaml composition.yaml functions.yaml +``` + +Will produce an output like: + +```shell +--- +apiVersion: example.crossplane.io/v1 +kind: XR +metadata: + name: example-xr +status: + conditions: + - lastTransitionTime: "2024-01-01T00:00:00Z" + reason: Available + status: "True" + type: Ready + fromEnv: e +--- +apiVersion: render.crossplane.io/v1beta1 +fields: + apiextensions.crossplane.io/environment: + apiVersion: internal.crossplane.io/v1alpha1 + array: + - "1" + - "2" + complex: + a: b + c: + d: e + f: "1" + kind: Environment + nestedEnvUpdate: + hello: world + update: environment + newkey: + hello: world + other-context-key: + complex: + a: b + c: + d: e + f: "1" +kind: Context +``` + +## Debugging This Function + +First we need to run the command in debug mode. In a terminal Window Run: + +```shell +# Run the function locally +$ go run . --insecure --debug +``` + +Next, set the go-templating function `render.crossplane.io/runtime: Development` annotation so that +`crossplane render` communicates with the local process instead of downloading an image: + +```yaml +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: crossplane-contrib-function-go-templating + annotations: + render.crossplane.io/runtime: Development +spec: + package: xpkg.upbound.io/crossplane-contrib/function-go-templating:v0.6.0 +``` + +While the function is running in one terminal, open another terminal window and run `crossplane render`. +The function should output debug-level logs in the terminal. diff --git a/example/conditions/composition.yaml b/example/conditions/composition.yaml new file mode 100644 index 0000000..7f59d3f --- /dev/null +++ b/example/conditions/composition.yaml @@ -0,0 +1,49 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: go-template-context.example.crossplane.io +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: XR + mode: Pipeline + pipeline: + - step: go-templating-update-conditions + functionRef: + name: crossplane-contrib-function-go-templating + input: + apiVersion: gotemplating.fn.crossplane.io/v1beta1 + kind: GoTemplate + source: Inline + inline: + template: | + --- + apiVersion: meta.gotemplating.fn.crossplane.io/v1alpha1 + kind: ClaimConditions + conditions: + # Type of the condition, e.g. DatabaseReady. + # 'Healthy', 'Ready' and 'Synced' are reserved for use by Crossplane and this function will raise an error if used + # - type: + # Status of the condition. + # status: + # Machine-readable PascalCase reason. + # reason: + # Optional Target. Publish Condition only to the Composite, or the Composite and the Claim. + # Defaults to CompositeAndClaim + # target: + - type: TestCondition + status: "False" + reason: InstallFail + message: "failed to install" + - type: ConditionTrue + status: "True" + reason: this condition is true + message: we are true + target: Composite + - type: DatabaseReady + status: "True" + reason: Ready + message: Database is ready + - step: automatically-detect-ready-composed-resources + functionRef: + name: crossplane-contrib-function-auto-ready diff --git a/example/conditions/environmentConfigs.yaml b/example/conditions/environmentConfigs.yaml new file mode 100644 index 0000000..46dc62b --- /dev/null +++ b/example/conditions/environmentConfigs.yaml @@ -0,0 +1,10 @@ +apiVersion: apiextensions.crossplane.io/v1alpha1 +kind: EnvironmentConfig +metadata: + name: example-config +data: + complex: + a: b + c: + d: e + f: "1" \ No newline at end of file diff --git a/example/conditions/functions.yaml b/example/conditions/functions.yaml new file mode 100644 index 0000000..82ebe3b --- /dev/null +++ b/example/conditions/functions.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: crossplane-contrib-function-environment-configs +spec: + # This is ignored when using the Development runtime. + package: xpkg.upbound.io/crossplane-contrib/function-environment-configs:v0.0.7 +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: crossplane-contrib-function-go-templating + annotations: + # This tells crossplane beta render to connect to the function locally. + render.crossplane.io/runtime: Development +spec: + package: xpkg.upbound.io/crossplane-contrib/function-go-templating:v0.7.0 +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: crossplane-contrib-function-auto-ready +spec: + package: xpkg.upbound.io/crossplane-contrib/function-auto-ready:v0.3.0 diff --git a/example/conditions/xr.yaml b/example/conditions/xr.yaml new file mode 100644 index 0000000..35a7feb --- /dev/null +++ b/example/conditions/xr.yaml @@ -0,0 +1,5 @@ +apiVersion: example.crossplane.io/v1 +kind: XR +metadata: + name: example-xr +spec: {} \ No newline at end of file diff --git a/example/conditions/xrd.yaml b/example/conditions/xrd.yaml new file mode 100644 index 0000000..8955af2 --- /dev/null +++ b/example/conditions/xrd.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: apiextensions.crossplane.io/v1 +kind: CompositeResourceDefinition +metadata: + name: xrs.example.crossplane.io +spec: + group: example.crossplane.io + names: + kind: XR + plural: xrs + connectionSecretKeys: + - test + versions: + - name: v1 + served: true + referenceable: true + schema: + openAPIV3Schema: + type: object + properties: + status: + type: object + properties: + fromEnv: + type: string \ No newline at end of file diff --git a/fn.go b/fn.go index 7d05468..9e81075 100644 --- a/fn.go +++ b/fn.go @@ -15,6 +15,7 @@ import ( "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/yaml" + xpruntimev1 "github.com/crossplane/crossplane-runtime/apis/common/v1" "github.com/crossplane/crossplane-runtime/pkg/fieldpath" "github.com/crossplane/crossplane-runtime/pkg/meta" @@ -192,6 +193,17 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest) d, _ := base64.StdEncoding.DecodeString(v) //nolint:errcheck // k8s returns secret values encoded desiredComposite.ConnectionDetails[k] = d } + case "ClaimConditions": + var conditions []xpruntimev1.Condition + if err = cd.Resource.GetValueInto("conditions", &conditions); err != nil { + response.Fatal(rsp, errors.Wrap(err, "cannot get Conditions from input")) + return rsp, nil + } + rsp, err := UpdateClaimConditions(rsp, conditions...) + if err != nil { + response.Fatal(rsp, errors.Wrap(err, "cannot set ClaimCondition")) + return rsp, nil + } case "Context": contextData := make(map[string]interface{}) if err = cd.Resource.GetValueInto("data", &contextData); err != nil { diff --git a/go.mod b/go.mod index e65951c..1359f43 100644 --- a/go.mod +++ b/go.mod @@ -10,9 +10,11 @@ require ( github.com/alecthomas/kong v0.9.0 github.com/crossplane/crossplane-runtime v1.17.0 github.com/crossplane/function-sdk-go v0.3.0 + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/google/go-cmp v0.6.0 google.golang.org/protobuf v1.34.3-0.20240816073751-94ecbc261689 gopkg.in/yaml.v3 v3.0.1 + k8s.io/api v0.30.0 k8s.io/apimachinery v0.30.0 sigs.k8s.io/controller-tools v0.14.0 ) @@ -20,7 +22,6 @@ require ( require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fatih/color v1.17.0 // indirect @@ -70,7 +71,6 @@ require ( google.golang.org/grpc v1.66.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/api v0.30.0 // indirect k8s.io/apiextensions-apiserver v0.30.0 // indirect k8s.io/client-go v0.30.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect diff --git a/go.sum b/go.sum index 69e3aa3..9720234 100644 --- a/go.sum +++ b/go.sum @@ -2,16 +2,16 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= -github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU= -github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA= -github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/kong v1.4.0 h1:UL7tzGMnnY0YRMMvJyITIRX1EpO6RbBRZDNcCevy3HA= +github.com/alecthomas/kong v1.4.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/antchfx/htmlquery v1.2.4 h1:qLteofCMe/KGovBI6SQgmou2QNyedFUW+pE+BpeZ494= @@ -126,11 +126,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -167,7 +164,6 @@ github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk= github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= @@ -209,6 +205,8 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= @@ -283,11 +281,12 @@ google.golang.org/protobuf v1.34.3-0.20240816073751-94ecbc261689/go.mod h1:qYOHt gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From aef7a5d2425b2b303bc2e300795a192540417688 Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Fri, 22 Nov 2024 21:07:02 -0600 Subject: [PATCH 2/9] initial working example Signed-off-by: Steven Borrelli --- claimconditions.go | 73 ++++++++++++++++++++++++++++++++++------------ fn.go | 4 +-- go.mod | 8 +++-- go.sum | 21 ++++++------- 4 files changed, 73 insertions(+), 33 deletions(-) diff --git a/claimconditions.go b/claimconditions.go index b5b5789..5df2140 100644 --- a/claimconditions.go +++ b/claimconditions.go @@ -1,43 +1,80 @@ package main import ( - xpruntimev1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + v1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" "github.com/crossplane/function-sdk-go/errors" fnv1 "github.com/crossplane/function-sdk-go/proto/v1" "github.com/crossplane/function-sdk-go/response" - "github.com/davecgh/go-spew/spew" corev1 "k8s.io/api/core/v1" ) // A CompositionTarget is the target of a composition event or condition. type CompositionTarget string +// A TargetedCondition represents a condition produced by the composition +// process. It can target either the XR only, or both the XR and the claim. +type TargetedCondition struct { + xpv1.Condition `json:",inline"` + Target CompositionTarget `json:"target"` +} + // Composition event and condition targets. const ( CompositionTargetComposite CompositionTarget = "Composite" CompositionTargetCompositeAndClaim CompositionTarget = "CompositeAndClaim" ) -func UpdateClaimConditions(rsp *fnv1.RunFunctionResponse, conditions ...xpruntimev1.Condition) (*fnv1.RunFunctionResponse, error) { +// UpdateClaimConditions updates Conditions in the Claim and Composite +func UpdateClaimConditions(rsp *fnv1.RunFunctionResponse, conditions ...TargetedCondition) (*fnv1.RunFunctionResponse, error) { for _, c := range conditions { - if xpruntimev1.IsSystemConditionType(c.Type) { + if xpv1.IsSystemConditionType(v1.ConditionType(c.Type)) { response.Fatal(rsp, errors.Errorf("cannot set ClaimCondition type: %s is a reserved Crossplane Condition", c.Type)) return rsp, nil } - var co *response.ConditionOption - switch c.Status { - case corev1.ConditionTrue: - co = response.ConditionTrue(rsp, string(c.Type), string(c.Reason)).WithMessage(c.Message) - case corev1.ConditionFalse: - co = response.ConditionFalse(rsp, string(c.Type), string(c.Reason)) - case corev1.ConditionUnknown: - co = response.ConditionFalse(rsp, string(c.Type), string(c.Reason)) - } - if c.Message != "" { - co.WithMessage(c.Message) - } - - spew.Dump(co) + co := transformCondition(c) + UpdateResponseWithCondition(rsp, co) } return rsp, nil } + +// transformCondition converts a TargetedCondition to be compatible with the Protobuf SDK +func transformCondition(tc TargetedCondition) *fnv1.Condition { + c := &fnv1.Condition{ + Type: string(tc.Condition.Type), + Reason: string(tc.Condition.Reason), + Target: transformTarget(tc.Target), + } + + switch tc.Condition.Status { + case corev1.ConditionTrue: + c.Status = fnv1.Status_STATUS_CONDITION_TRUE + case corev1.ConditionFalse: + c.Status = fnv1.Status_STATUS_CONDITION_FALSE + case corev1.ConditionUnknown: + fallthrough + default: + c.Status = fnv1.Status_STATUS_CONDITION_UNKNOWN + } + + if tc.Message != "" { + c.Message = &tc.Message + } + return c +} + +// transformTarget converts the input into a target Go SDK Enum +// Default to TARGET_COMPOSITE_AND_CLAIM +func transformTarget(ct CompositionTarget) *fnv1.Target { + if ct == CompositionTargetComposite { + return fnv1.Target_TARGET_COMPOSITE.Enum() + } + return fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum() +} + +func UpdateResponseWithCondition(rsp *fnv1.RunFunctionResponse, c *fnv1.Condition) { + if rsp.GetConditions() == nil { + rsp.Conditions = make([]*fnv1.Condition, 0, 1) + } + rsp.Conditions = append(rsp.GetConditions(), c) +} diff --git a/fn.go b/fn.go index 9e81075..835f1fd 100644 --- a/fn.go +++ b/fn.go @@ -15,7 +15,6 @@ import ( "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/yaml" - xpruntimev1 "github.com/crossplane/crossplane-runtime/apis/common/v1" "github.com/crossplane/crossplane-runtime/pkg/fieldpath" "github.com/crossplane/crossplane-runtime/pkg/meta" @@ -194,7 +193,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest) desiredComposite.ConnectionDetails[k] = d } case "ClaimConditions": - var conditions []xpruntimev1.Condition + var conditions []TargetedCondition if err = cd.Resource.GetValueInto("conditions", &conditions); err != nil { response.Fatal(rsp, errors.Wrap(err, "cannot get Conditions from input")) return rsp, nil @@ -204,6 +203,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest) response.Fatal(rsp, errors.Wrap(err, "cannot set ClaimCondition")) return rsp, nil } + f.log.Debug("updating ClaimConditions", "conditions", rsp.Conditions) case "Context": contextData := make(map[string]interface{}) if err = cd.Resource.GetValueInto("data", &contextData); err != nil { diff --git a/go.mod b/go.mod index 1359f43..884676b 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/crossplane-contrib/function-go-templating go 1.23 -toolchain go1.23.2 +toolchain go1.23.3 require ( dario.cat/mergo v1.0.1 @@ -21,7 +21,8 @@ require ( require ( github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/Masterminds/semver/v3 v3.3.1 // indirect + github.com/alecthomas/assert/v2 v2.11.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fatih/color v1.17.0 // indirect @@ -50,6 +51,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.7.0 // indirect @@ -62,7 +64,7 @@ require ( golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect + golang.org/x/sys v0.27.0 // indirect golang.org/x/term v0.24.0 // indirect golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect diff --git a/go.sum b/go.sum index 9720234..0a58f2b 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7l github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/kong v1.4.0 h1:UL7tzGMnnY0YRMMvJyITIRX1EpO6RbBRZDNcCevy3HA= -github.com/alecthomas/kong v1.4.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU= +github.com/alecthomas/kong v0.9.0 h1:G5diXxc85KvoV2f0ZRVuMsi45IrBgx9zDNGNj165aPA= +github.com/alecthomas/kong v0.9.0/go.mod h1:Y47y5gKfHp1hDc7CH7OeXgLIpp+Q2m1Ni0L5s3bI8Os= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/antchfx/htmlquery v1.2.4 h1:qLteofCMe/KGovBI6SQgmou2QNyedFUW+pE+BpeZ494= @@ -126,8 +126,11 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -164,6 +167,7 @@ github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk= github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= @@ -173,8 +177,8 @@ github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSz github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= @@ -205,8 +209,6 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= @@ -248,8 +250,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -281,12 +283,11 @@ google.golang.org/protobuf v1.34.3-0.20240816073751-94ecbc261689/go.mod h1:qYOHt gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From b440da52ba34ff542b768843d781d5799ed96d2c Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Fri, 22 Nov 2024 21:12:28 -0600 Subject: [PATCH 3/9] update example Signed-off-by: Steven Borrelli --- claimconditions.go | 3 +- example/conditions/README.md | 51 ++-------------------- example/conditions/composition.yaml | 1 + example/conditions/environmentConfigs.yaml | 10 ----- 4 files changed, 5 insertions(+), 60 deletions(-) delete mode 100644 example/conditions/environmentConfigs.yaml diff --git a/claimconditions.go b/claimconditions.go index 5df2140..c4581b6 100644 --- a/claimconditions.go +++ b/claimconditions.go @@ -1,7 +1,6 @@ package main import ( - v1 "github.com/crossplane/crossplane-runtime/apis/common/v1" xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" "github.com/crossplane/function-sdk-go/errors" fnv1 "github.com/crossplane/function-sdk-go/proto/v1" @@ -28,7 +27,7 @@ const ( // UpdateClaimConditions updates Conditions in the Claim and Composite func UpdateClaimConditions(rsp *fnv1.RunFunctionResponse, conditions ...TargetedCondition) (*fnv1.RunFunctionResponse, error) { for _, c := range conditions { - if xpv1.IsSystemConditionType(v1.ConditionType(c.Type)) { + if xpv1.IsSystemConditionType(xpv1.ConditionType(c.Type)) { response.Fatal(rsp, errors.Errorf("cannot set ClaimCondition type: %s is a reserved Crossplane Condition", c.Type)) return rsp, nil } diff --git a/example/conditions/README.md b/example/conditions/README.md index 21587cd..60de2cc 100644 --- a/example/conditions/README.md +++ b/example/conditions/README.md @@ -1,6 +1,6 @@ -# Writing to the Function Context +# Writing to the Composite or Claim Status -function-go-templating can write to the Function Context +function-go-templating can write to the Composite or Claim Status. See [Communication Between Composition Functions and the Claim](https://github.com/crossplane/crossplane/blob/main/design/one-pager-fn-claim-conditions.md) for more information. ## Testing This Function Locally @@ -9,54 +9,9 @@ with these example manifests. ```shell crossplane render \ - --extra-resources environmentConfigs.yaml \ - --include-context \ xr.yaml composition.yaml functions.yaml ``` -Will produce an output like: - -```shell ---- -apiVersion: example.crossplane.io/v1 -kind: XR -metadata: - name: example-xr -status: - conditions: - - lastTransitionTime: "2024-01-01T00:00:00Z" - reason: Available - status: "True" - type: Ready - fromEnv: e ---- -apiVersion: render.crossplane.io/v1beta1 -fields: - apiextensions.crossplane.io/environment: - apiVersion: internal.crossplane.io/v1alpha1 - array: - - "1" - - "2" - complex: - a: b - c: - d: e - f: "1" - kind: Environment - nestedEnvUpdate: - hello: world - update: environment - newkey: - hello: world - other-context-key: - complex: - a: b - c: - d: e - f: "1" -kind: Context -``` - ## Debugging This Function First we need to run the command in debug mode. In a terminal Window Run: @@ -77,7 +32,7 @@ metadata: annotations: render.crossplane.io/runtime: Development spec: - package: xpkg.upbound.io/crossplane-contrib/function-go-templating:v0.6.0 + package: xpkg.upbound.io/crossplane-contrib/function-go-templating:v0.9.0 ``` While the function is running in one terminal, open another terminal window and run `crossplane render`. diff --git a/example/conditions/composition.yaml b/example/conditions/composition.yaml index 7f59d3f..04c0af1 100644 --- a/example/conditions/composition.yaml +++ b/example/conditions/composition.yaml @@ -35,6 +35,7 @@ spec: status: "False" reason: InstallFail message: "failed to install" + target: ClaimAndComposite - type: ConditionTrue status: "True" reason: this condition is true diff --git a/example/conditions/environmentConfigs.yaml b/example/conditions/environmentConfigs.yaml deleted file mode 100644 index 46dc62b..0000000 --- a/example/conditions/environmentConfigs.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: apiextensions.crossplane.io/v1alpha1 -kind: EnvironmentConfig -metadata: - name: example-config -data: - complex: - a: b - c: - d: e - f: "1" \ No newline at end of file From 0c46a6982670fb2b1506530eb649b8abbc35be5a Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Fri, 22 Nov 2024 21:17:22 -0600 Subject: [PATCH 4/9] go.mod tidy Signed-off-by: Steven Borrelli --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 884676b..91035a7 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/alecthomas/kong v0.9.0 github.com/crossplane/crossplane-runtime v1.17.0 github.com/crossplane/function-sdk-go v0.3.0 - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/google/go-cmp v0.6.0 google.golang.org/protobuf v1.34.3-0.20240816073751-94ecbc261689 gopkg.in/yaml.v3 v3.0.1 @@ -23,6 +22,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.3.1 // indirect github.com/alecthomas/assert/v2 v2.11.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fatih/color v1.17.0 // indirect From 62a60f1d339af52fd8f2fa46353287a8daa89e62 Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Fri, 22 Nov 2024 22:37:59 -0600 Subject: [PATCH 5/9] update fn_test.go with ClaimConditions Signed-off-by: Steven Borrelli --- fn_test.go | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/fn_test.go b/fn_test.go index 1c56ddb..a0331f4 100644 --- a/fn_test.go +++ b/fn_test.go @@ -10,6 +10,7 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/durationpb" + "k8s.io/utils/ptr" "github.com/crossplane/crossplane-runtime/pkg/logging" @@ -40,6 +41,9 @@ var ( xrWithNestedStatusBaz = `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"name":"cool-xr"},"spec":{"count":2},"status":{"state":{"baz":"qux"}}}` xrRecursiveTmpl = `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"annotations":{"gotemplating.fn.crossplane.io/composition-resource-name":"recursive-xr"},"name":"recursive-xr","labels":{"belongsTo":{{.observed.composite.resource.metadata.name|quote}}}},"spec":{"count":2}}` + claimConditions = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"ClaimConditions","conditions":[{"type":"TestCondition","status":"False","reason":"InstallFail","message":"failed to install","target":"ClaimAndComposite"},{"type":"ConditionTrue","status":"True","reason":"this condition is true","message":"we are true","target":"Composite"},{"type":"DatabaseReady","status":"True","reason":"Ready","message":"Database is ready"}]}` + claimConditionsReservedKey = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"ClaimConditions","conditions":[{"type":"Ready","tatus":"False","reason":"InstallFail","message":"I am using a reserved Condition","target":"ClaimAndComposite"}]}` + extraResources = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"ExtraResources","requirements":{"cool-extra-resource":{"apiVersion":"example.org/v1","kind":"CoolExtraResource","matchName":"cool-extra-resource"}}} {"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"ExtraResources","requirements":{"another-cool-extra-resource":{"apiVersion":"example.org/v1","kind":"CoolExtraResource","matchLabels":{"key": "value"}},"yet-another-cool-extra-resource":{"apiVersion":"example.org/v1","kind":"CoolExtraResource","matchName":"foo"}}} {"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"ExtraResources","requirements":{"all-cool-resources":{"apiVersion":"example.org/v1","kind":"CoolExtraResource","matchLabels":{}}}}` @@ -611,6 +615,100 @@ func TestRunFunction(t *testing.T) { }, }, }, + "ClaimConditionsError": { + reason: "The Function should return a fatal result if a reserved Condition is set.", + args: args{ + req: &fnv1.RunFunctionRequest{ + Input: resource.MustStructObject( + &v1beta1.GoTemplate{ + Source: v1beta1.InlineSource, + Inline: &v1beta1.TemplateSourceInline{Template: claimConditionsReservedKey}, + }), + Observed: &fnv1.State{ + Composite: &fnv1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + }, + Desired: &fnv1.State{ + Composite: &fnv1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + }, + }, + }, + want: want{ + rsp: &fnv1.RunFunctionResponse{ + Meta: &fnv1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)}, + Results: []*fnv1.Result{ + { + Severity: fnv1.Severity_SEVERITY_FATAL, + Message: "cannot set ClaimCondition type: Ready is a reserved Crossplane Condition", + Target: fnv1.Target_TARGET_COMPOSITE.Enum(), + }, + }, + Desired: &fnv1.State{ + Composite: &fnv1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + }, + }, + }, + }, + "ClaimConditions": { + reason: "The Function should correctly set ClaimConditions.", + args: args{ + req: &fnv1.RunFunctionRequest{ + Input: resource.MustStructObject( + &v1beta1.GoTemplate{ + Source: v1beta1.InlineSource, + Inline: &v1beta1.TemplateSourceInline{Template: claimConditions}, + }), + Observed: &fnv1.State{ + Composite: &fnv1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + }, + Desired: &fnv1.State{ + Composite: &fnv1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + }, + }, + }, + want: want{ + rsp: &fnv1.RunFunctionResponse{ + Conditions: []*fnv1.Condition{ + { + Type: "TestCondition", + Status: fnv1.Status_STATUS_CONDITION_FALSE, + Reason: "InstallFail", + Message: ptr.To("failed to install"), + Target: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + }, + { + Type: "ConditionTrue", + Status: fnv1.Status_STATUS_CONDITION_TRUE, + Reason: "this condition is true", + Message: ptr.To("we are true"), + Target: fnv1.Target_TARGET_COMPOSITE.Enum(), + }, + { + Type: "DatabaseReady", + Status: fnv1.Status_STATUS_CONDITION_TRUE, + Reason: "Ready", + Message: ptr.To("Database is ready"), + Target: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + }, + }, + Meta: &fnv1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)}, + Desired: &fnv1.State{ + Composite: &fnv1.Resource{ + Resource: resource.MustStructJSON(xr), + }, + }, + }, + }, + }, "CompositeConnectionDetails": { reason: "The Function should return the desired composite with CompositeConnectionDetails.", args: args{ From 4c256db706dbb6521d27a7ab81d5ededa83bc727 Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Fri, 22 Nov 2024 22:43:00 -0600 Subject: [PATCH 6/9] go mod tidy Signed-off-by: Steven Borrelli --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 91035a7..358aee8 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.30.0 k8s.io/apimachinery v0.30.0 + k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3 sigs.k8s.io/controller-tools v0.14.0 ) @@ -77,7 +78,6 @@ require ( k8s.io/client-go v0.30.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3 // indirect sigs.k8s.io/controller-runtime v0.18.2 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect From f9edb3ff601ca4d1b3be18b84cb36a647454886c Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Sun, 24 Nov 2024 17:58:51 -0600 Subject: [PATCH 7/9] add testing Signed-off-by: Steven Borrelli --- claimconditions.go | 17 +- claimconditions_test.go | 333 ++++++++++++++++++++++++++++ example/conditions/composition.yaml | 2 +- fn.go | 3 +- fn_test.go | 2 +- 5 files changed, 349 insertions(+), 8 deletions(-) create mode 100644 claimconditions_test.go diff --git a/claimconditions.go b/claimconditions.go index c4581b6..6e8537b 100644 --- a/claimconditions.go +++ b/claimconditions.go @@ -25,16 +25,19 @@ const ( ) // UpdateClaimConditions updates Conditions in the Claim and Composite -func UpdateClaimConditions(rsp *fnv1.RunFunctionResponse, conditions ...TargetedCondition) (*fnv1.RunFunctionResponse, error) { +func UpdateClaimConditions(rsp *fnv1.RunFunctionResponse, conditions ...TargetedCondition) error { + if rsp == nil { + return nil + } for _, c := range conditions { if xpv1.IsSystemConditionType(xpv1.ConditionType(c.Type)) { response.Fatal(rsp, errors.Errorf("cannot set ClaimCondition type: %s is a reserved Crossplane Condition", c.Type)) - return rsp, nil + return errors.New("error updating response") } co := transformCondition(c) UpdateResponseWithCondition(rsp, co) } - return rsp, nil + return nil } // transformCondition converts a TargetedCondition to be compatible with the Protobuf SDK @@ -71,9 +74,15 @@ func transformTarget(ct CompositionTarget) *fnv1.Target { return fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum() } +// UpdateResponseWithCondition updates the RunFunctionResponse with a Condition func UpdateResponseWithCondition(rsp *fnv1.RunFunctionResponse, c *fnv1.Condition) { + if rsp == nil { + return + } if rsp.GetConditions() == nil { rsp.Conditions = make([]*fnv1.Condition, 0, 1) } - rsp.Conditions = append(rsp.GetConditions(), c) + if c != nil { + rsp.Conditions = append(rsp.GetConditions(), c) + } } diff --git a/claimconditions_test.go b/claimconditions_test.go new file mode 100644 index 0000000..f933f6e --- /dev/null +++ b/claimconditions_test.go @@ -0,0 +1,333 @@ +package main + +import ( + "reflect" + "testing" + + xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1" + "github.com/crossplane/crossplane-runtime/pkg/test" + "github.com/crossplane/function-sdk-go/errors" + fnv1 "github.com/crossplane/function-sdk-go/proto/v1" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + v1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" +) + +func Test_UpdateClaimConditions(t *testing.T) { + type args struct { + rsp *fnv1.RunFunctionResponse + c []TargetedCondition + } + type want struct { + rsp *fnv1.RunFunctionResponse + err error + } + cases := map[string]struct { + reason string + args args + want want + }{ + "EmptyResponseNoConditions": { + reason: "When No Response or Conditions are provided, return a nil response", + args: args{}, + want: want{}, + }, + "ErrorOnReadyReservedType": { + reason: "Return an error if a Reserved Condition Type is being used", + args: args{ + rsp: &fnv1.RunFunctionResponse{}, + c: []TargetedCondition{ + { + Condition: xpv1.Condition{ + Message: "Ready Message", + Status: v1.ConditionTrue, + Type: "Ready", + }, + Target: CompositionTargetComposite, + }, + }, + }, + want: want{ + rsp: &fnv1.RunFunctionResponse{ + Results: []*fnv1.Result{ + { + Severity: fnv1.Severity_SEVERITY_FATAL, + Message: "cannot set ClaimCondition type: Ready is a reserved Crossplane Condition", + Target: fnv1.Target_TARGET_COMPOSITE.Enum(), + }, + }, + }, + err: errors.New("error updating response"), + }, + }, + "SuccessfullyAddConditions": { + reason: "Add Conditions Successfully", + args: args{ + rsp: &fnv1.RunFunctionResponse{}, + c: []TargetedCondition{ + { + Condition: xpv1.Condition{ + Message: "Creating Resource", + Status: v1.ConditionFalse, + Type: "NetworkReady", + }, + Target: CompositionTargetCompositeAndClaim, + }, + { + Condition: xpv1.Condition{ + Message: "Ready Message", + Status: v1.ConditionTrue, + Type: "DatabaseReady", + }, + Target: CompositionTargetComposite, + }, + { + Condition: xpv1.Condition{ + Message: "No Target should add CompositeAndClaim", + Status: v1.ConditionTrue, + Type: "NoTarget", + }, + }, + }, + }, + want: want{ + rsp: &fnv1.RunFunctionResponse{ + Conditions: []*fnv1.Condition{ + { + Message: ptr.To("Creating Resource"), + Status: fnv1.Status_STATUS_CONDITION_FALSE, + Target: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + Type: "NetworkReady", + }, + { + Message: ptr.To("Ready Message"), + Status: fnv1.Status_STATUS_CONDITION_TRUE, + Target: fnv1.Target_TARGET_COMPOSITE.Enum(), + Type: "DatabaseReady", + }, + { + Message: ptr.To("No Target should add CompositeAndClaim"), + Status: fnv1.Status_STATUS_CONDITION_TRUE, + Target: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + Type: "NoTarget", + }, + }, + }, + }, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + err := UpdateClaimConditions(tc.args.rsp, tc.args.c...) + if diff := cmp.Diff(tc.args.rsp, tc.want.rsp, cmpopts.IgnoreUnexported(fnv1.RunFunctionResponse{}, fnv1.Result{}, fnv1.Condition{})); diff != "" { + t.Errorf("%s\nUpdateClaimConditions(...): -want rsp, +got rsp:\n%s", tc.reason, diff) + } + if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { + t.Errorf("%s\nUpdateClaimConditions(...): -want err, +got err:\n%s", tc.reason, diff) + } + + }) + } +} + +func Test_transformCondition(t *testing.T) { + type args struct { + tc TargetedCondition + } + cases := map[string]struct { + reason string + args args + want *fnv1.Condition + }{ + "Basic": { + reason: "Basic Target", + args: args{ + tc: TargetedCondition{ + Condition: xpv1.Condition{ + Message: "Basic Message", + Status: v1.ConditionTrue, + Type: "TestType", + }, + Target: CompositionTargetComposite, + }, + }, + want: &fnv1.Condition{ + Message: ptr.To("Basic Message"), + Status: fnv1.Status_STATUS_CONDITION_TRUE, + Target: fnv1.Target_TARGET_COMPOSITE.Enum(), + Type: "TestType", + }, + }, + "Defaults": { + reason: "Default Settings", + args: args{ + tc: TargetedCondition{ + Condition: xpv1.Condition{}, + }, + }, + want: &fnv1.Condition{ + Status: fnv1.Status_STATUS_CONDITION_UNKNOWN, + Target: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + }, + }, + "StatusFalseNoTarget": { + reason: "When Status is false and no target set", + args: args{ + tc: TargetedCondition{ + Condition: xpv1.Condition{ + Message: "Basic Message", + Status: v1.ConditionFalse, + Type: "TestType", + }, + }, + }, + want: &fnv1.Condition{ + Message: ptr.To("Basic Message"), + Status: fnv1.Status_STATUS_CONDITION_FALSE, + Target: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + Type: "TestType", + }, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + if got := transformCondition(tc.args.tc); !reflect.DeepEqual(got, tc.want) { + t.Errorf("transformCondition() = %v, want %v", got, tc.want) + } + }) + } +} + +func Test_transformTarget(t *testing.T) { + type args struct { + ct CompositionTarget + } + cases := map[string]struct { + reason string + args args + want *fnv1.Target + }{ + "DefaultToCompositeAndClaim": { + reason: "unknown target will default to CompositeAndClaim", + args: args{ + ct: "COMPOSE", + }, + want: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + }, + "Composite": { + reason: "Composite target correctly set", + args: args{ + ct: "Composite", + }, + want: fnv1.Target_TARGET_COMPOSITE.Enum(), + }, + "CompositeAndClaim": { + reason: "CompositeAndClaim target correctly set", + args: args{ + ct: "CompositeAndClaim", + }, + want: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + if got := transformTarget(tc.args.ct); !reflect.DeepEqual(got, tc.want) { + t.Errorf("transformTarget() = %v, want %v", got, tc.want) + } + }) + } +} + +func Test_UpdateResponseWithCondition(t *testing.T) { + type args struct { + rsp *fnv1.RunFunctionResponse + c *fnv1.Condition + } + cases := map[string]struct { + reason string + args args + want *fnv1.RunFunctionResponse + }{ + "EmptyResponseNoConditions": { + reason: "When No Response or Conditions are provided, return a nil response", + args: args{}, + }, + "ResponseWithNoConditions": { + reason: "A response with no conditions should initialize an array before adding the condition", + args: args{ + rsp: &fnv1.RunFunctionResponse{}, + }, + want: &fnv1.RunFunctionResponse{ + Conditions: []*fnv1.Condition{}, + }, + }, + "ResponseAddCondition": { + reason: "A response with no conditions should initialize an array before adding the condition", + args: args{ + rsp: &fnv1.RunFunctionResponse{}, + c: &fnv1.Condition{ + Message: ptr.To("Basic Message"), + Status: fnv1.Status_STATUS_CONDITION_FALSE, + Target: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + Type: "TestType", + }, + }, + want: &fnv1.RunFunctionResponse{ + Conditions: []*fnv1.Condition{ + { + Message: ptr.To("Basic Message"), + Status: fnv1.Status_STATUS_CONDITION_FALSE, + Target: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + Type: "TestType", + }, + }, + }, + }, + "ResponseAppCondition": { + reason: "A response with existing conditions should append the condition", + args: args{ + rsp: &fnv1.RunFunctionResponse{ + Conditions: []*fnv1.Condition{ + { + Message: ptr.To("Existing Message"), + Status: fnv1.Status_STATUS_CONDITION_TRUE, + Target: fnv1.Target_TARGET_COMPOSITE.Enum(), + Type: "ExistingTestType", + }, + }, + }, + c: &fnv1.Condition{ + Message: ptr.To("Basic Message"), + Status: fnv1.Status_STATUS_CONDITION_FALSE, + Target: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + Type: "TestType", + }, + }, + want: &fnv1.RunFunctionResponse{ + Conditions: []*fnv1.Condition{ + { + Message: ptr.To("Existing Message"), + Status: fnv1.Status_STATUS_CONDITION_TRUE, + Target: fnv1.Target_TARGET_COMPOSITE.Enum(), + Type: "ExistingTestType", + }, + { + Message: ptr.To("Basic Message"), + Status: fnv1.Status_STATUS_CONDITION_FALSE, + Target: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + Type: "TestType", + }, + }, + }, + }, + } + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + UpdateResponseWithCondition(tc.args.rsp, tc.args.c) + if diff := cmp.Diff(tc.args.rsp, tc.want, cmpopts.IgnoreUnexported(fnv1.RunFunctionResponse{}, fnv1.Condition{})); diff != "" { + t.Errorf("%s\nUpdateResponseWithCondition(...): -want rsp, +got rsp:\n%s", tc.reason, diff) + } + }) + } +} diff --git a/example/conditions/composition.yaml b/example/conditions/composition.yaml index 04c0af1..26f9da3 100644 --- a/example/conditions/composition.yaml +++ b/example/conditions/composition.yaml @@ -35,7 +35,7 @@ spec: status: "False" reason: InstallFail message: "failed to install" - target: ClaimAndComposite + target: CompositeAndClaim - type: ConditionTrue status: "True" reason: this condition is true diff --git a/fn.go b/fn.go index 835f1fd..fd1376d 100644 --- a/fn.go +++ b/fn.go @@ -198,9 +198,8 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest) response.Fatal(rsp, errors.Wrap(err, "cannot get Conditions from input")) return rsp, nil } - rsp, err := UpdateClaimConditions(rsp, conditions...) + err := UpdateClaimConditions(rsp, conditions...) if err != nil { - response.Fatal(rsp, errors.Wrap(err, "cannot set ClaimCondition")) return rsp, nil } f.log.Debug("updating ClaimConditions", "conditions", rsp.Conditions) diff --git a/fn_test.go b/fn_test.go index a0331f4..11f69c4 100644 --- a/fn_test.go +++ b/fn_test.go @@ -42,7 +42,7 @@ var ( xrRecursiveTmpl = `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"annotations":{"gotemplating.fn.crossplane.io/composition-resource-name":"recursive-xr"},"name":"recursive-xr","labels":{"belongsTo":{{.observed.composite.resource.metadata.name|quote}}}},"spec":{"count":2}}` claimConditions = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"ClaimConditions","conditions":[{"type":"TestCondition","status":"False","reason":"InstallFail","message":"failed to install","target":"ClaimAndComposite"},{"type":"ConditionTrue","status":"True","reason":"this condition is true","message":"we are true","target":"Composite"},{"type":"DatabaseReady","status":"True","reason":"Ready","message":"Database is ready"}]}` - claimConditionsReservedKey = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"ClaimConditions","conditions":[{"type":"Ready","tatus":"False","reason":"InstallFail","message":"I am using a reserved Condition","target":"ClaimAndComposite"}]}` + claimConditionsReservedKey = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"ClaimConditions","conditions":[{"type":"Ready","status":"False","reason":"InstallFail","message":"I am using a reserved Condition","target":"ClaimAndComposite"}]}` extraResources = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"ExtraResources","requirements":{"cool-extra-resource":{"apiVersion":"example.org/v1","kind":"CoolExtraResource","matchName":"cool-extra-resource"}}} {"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"ExtraResources","requirements":{"another-cool-extra-resource":{"apiVersion":"example.org/v1","kind":"CoolExtraResource","matchLabels":{"key": "value"}},"yet-another-cool-extra-resource":{"apiVersion":"example.org/v1","kind":"CoolExtraResource","matchName":"foo"}}} From 54705fe87b5b8c6059a314cf068bf28a79c37ca1 Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Mon, 25 Nov 2024 09:53:23 -0600 Subject: [PATCH 8/9] default to Composite claimcondition Signed-off-by: Steven Borrelli --- claimconditions.go | 8 ++++---- claimconditions_test.go | 12 ++++++------ example/conditions/composition.yaml | 11 +++++++---- fn_test.go | 4 ++-- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/claimconditions.go b/claimconditions.go index 6e8537b..3788e28 100644 --- a/claimconditions.go +++ b/claimconditions.go @@ -66,12 +66,12 @@ func transformCondition(tc TargetedCondition) *fnv1.Condition { } // transformTarget converts the input into a target Go SDK Enum -// Default to TARGET_COMPOSITE_AND_CLAIM +// Default to TARGET_COMPOSITE func transformTarget(ct CompositionTarget) *fnv1.Target { - if ct == CompositionTargetComposite { - return fnv1.Target_TARGET_COMPOSITE.Enum() + if ct == CompositionTargetCompositeAndClaim { + return fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum().Enum() } - return fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum() + return fnv1.Target_TARGET_COMPOSITE.Enum() } // UpdateResponseWithCondition updates the RunFunctionResponse with a Condition diff --git a/claimconditions_test.go b/claimconditions_test.go index f933f6e..d948d90 100644 --- a/claimconditions_test.go +++ b/claimconditions_test.go @@ -109,7 +109,7 @@ func Test_UpdateClaimConditions(t *testing.T) { { Message: ptr.To("No Target should add CompositeAndClaim"), Status: fnv1.Status_STATUS_CONDITION_TRUE, - Target: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + Target: fnv1.Target_TARGET_COMPOSITE.Enum(), Type: "NoTarget", }, }, @@ -168,7 +168,7 @@ func Test_transformCondition(t *testing.T) { }, want: &fnv1.Condition{ Status: fnv1.Status_STATUS_CONDITION_UNKNOWN, - Target: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + Target: fnv1.Target_TARGET_COMPOSITE.Enum(), }, }, "StatusFalseNoTarget": { @@ -185,7 +185,7 @@ func Test_transformCondition(t *testing.T) { want: &fnv1.Condition{ Message: ptr.To("Basic Message"), Status: fnv1.Status_STATUS_CONDITION_FALSE, - Target: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + Target: fnv1.Target_TARGET_COMPOSITE.Enum(), Type: "TestType", }, }, @@ -208,12 +208,12 @@ func Test_transformTarget(t *testing.T) { args args want *fnv1.Target }{ - "DefaultToCompositeAndClaim": { - reason: "unknown target will default to CompositeAndClaim", + "DefaultToComposite": { + reason: "unknown target will default to Composite", args: args{ ct: "COMPOSE", }, - want: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + want: fnv1.Target_TARGET_COMPOSITE.Enum(), }, "Composite": { reason: "Composite target correctly set", diff --git a/example/conditions/composition.yaml b/example/conditions/composition.yaml index 26f9da3..7c23e6c 100644 --- a/example/conditions/composition.yaml +++ b/example/conditions/composition.yaml @@ -21,16 +21,19 @@ spec: apiVersion: meta.gotemplating.fn.crossplane.io/v1alpha1 kind: ClaimConditions conditions: + # Guide to ClaimConditions fields: # Type of the condition, e.g. DatabaseReady. # 'Healthy', 'Ready' and 'Synced' are reserved for use by Crossplane and this function will raise an error if used # - type: - # Status of the condition. + # Status of the condition. String of "True"/"False"/"Unknown" # status: - # Machine-readable PascalCase reason. + # Machine-readable PascalCase reason, for example "DatabaseReady" # reason: - # Optional Target. Publish Condition only to the Composite, or the Composite and the Claim. - # Defaults to CompositeAndClaim + # Optional Target. Publish Condition only to the Composite, or the Composite and the Claim (CompositeAndClaim). + # Defaults to Composite # target: + # Optional message: + # message: - type: TestCondition status: "False" reason: InstallFail diff --git a/fn_test.go b/fn_test.go index 11f69c4..227d208 100644 --- a/fn_test.go +++ b/fn_test.go @@ -683,7 +683,7 @@ func TestRunFunction(t *testing.T) { Status: fnv1.Status_STATUS_CONDITION_FALSE, Reason: "InstallFail", Message: ptr.To("failed to install"), - Target: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + Target: fnv1.Target_TARGET_COMPOSITE.Enum(), }, { Type: "ConditionTrue", @@ -697,7 +697,7 @@ func TestRunFunction(t *testing.T) { Status: fnv1.Status_STATUS_CONDITION_TRUE, Reason: "Ready", Message: ptr.To("Database is ready"), - Target: fnv1.Target_TARGET_COMPOSITE_AND_CLAIM.Enum(), + Target: fnv1.Target_TARGET_COMPOSITE.Enum(), }, }, Meta: &fnv1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)}, From 56034b634cdc491a4637191afa4c1155ef69c310 Mon Sep 17 00:00:00 2001 From: Steven Borrelli Date: Mon, 25 Nov 2024 10:48:44 -0600 Subject: [PATCH 9/9] update docs Signed-off-by: Steven Borrelli --- README.md | 41 +++++++++++++++++++++++++++++ example/conditions/composition.yaml | 5 ++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 410a05e..595a4c4 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,47 @@ spec: For more information, see the example in [recursive](example/recursive). +## Setting Conditions on the Claim and Composite + +Starting with Crossplane 1.17, Composition authors can set custom Conditions on the +Composite and the Claim. + +Add a `ClaimConditions` to your template to set Conditions: + +```yaml +apiVersion: meta.gotemplating.fn.crossplane.io/v1alpha1 +kind: ClaimConditions +conditions: +# Guide to ClaimConditions fields: +# Type of the condition, e.g. DatabaseReady. +# 'Healthy', 'Ready' and 'Synced' are reserved for use by Crossplane and this function will raise an error if used +# - type: +# Status of the condition. String of "True"/"False"/"Unknown" +# status: +# Machine-readable PascalCase reason, for example "ErrorProvisioning" +# reason: +# Optional Target. Publish Condition only to the Composite, or the Composite and the Claim (CompositeAndClaim). +# Defaults to Composite +# target: +# Optional message: +# message: +- type: TestCondition + status: "False" + reason: InstallFail + message: "failed to install" + target: CompositeAndClaim +- type: ConditionTrue + status: "True" + reason: TrueCondition + message: we are true + target: Composite +- type: DatabaseReady + status: "True" + reason: Ready + message: Database is ready + target: CompositeAndClaim +``` + ## Additional functions | Name | Description | diff --git a/example/conditions/composition.yaml b/example/conditions/composition.yaml index 7c23e6c..d034db6 100644 --- a/example/conditions/composition.yaml +++ b/example/conditions/composition.yaml @@ -27,7 +27,7 @@ spec: # - type: # Status of the condition. String of "True"/"False"/"Unknown" # status: - # Machine-readable PascalCase reason, for example "DatabaseReady" + # Machine-readable PascalCase reason, for example "ErrorProvisioning" # reason: # Optional Target. Publish Condition only to the Composite, or the Composite and the Claim (CompositeAndClaim). # Defaults to Composite @@ -41,13 +41,14 @@ spec: target: CompositeAndClaim - type: ConditionTrue status: "True" - reason: this condition is true + reason: TrueCondition message: we are true target: Composite - type: DatabaseReady status: "True" reason: Ready message: Database is ready + target: CompositeAndClaim - step: automatically-detect-ready-composed-resources functionRef: name: crossplane-contrib-function-auto-ready