Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ on:

env:
# Common versions
GO_VERSION: '1.23.12'
GO_VERSION: '1.24.9'
GOLANGCI_VERSION: 'v1.62.0'
DOCKER_BUILDX_VERSION: 'v0.23.0'

Expand Down
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,26 @@ data:
server-endpoint: {{ (index $.observed.resources "my-server").resource.status.atProvider.endpoint | b64enc }}
```

To mark a desired composed resource as ready, use the
To mark a desired composed resource or composite resource as ready or not ready, use the
`gotemplating.fn.crossplane.io/ready` annotation:

```yaml
# Composed resource
apiVersion: s3.aws.upbound.io/v1beta1
kind: Bucket
metadata:
annotations:
gotemplating.fn.crossplane.io/composition-resource-name: bucket
gotemplating.fn.crossplane.io/ready: "True"
spec: {}

# Composite resource
apiVersion: example.crossplane.io/v1beta1
kind: XR
metadata:
annotations:
gotemplating.fn.crossplane.io/ready: "True"
status: {}
```

See the [example](example) directory for examples that you can run locally using
Expand Down Expand Up @@ -209,7 +218,7 @@ data:
region: {{ $spec.region }}
id: field
array:
- "1"
- "1"
- "2"
```

Expand Down Expand Up @@ -322,24 +331,24 @@ 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:
# - 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).
# Optional Target. Publish Condition only to the Composite, or the Composite and the Claim (CompositeAndClaim).
# Defaults to Composite
# target:
# target:
# Optional message:
# message:
# message:
- type: TestCondition
status: "False"
reason: InstallFail
message: "failed to install"
target: CompositeAndClaim
- type: ConditionTrue
status: "True"
reason: TrueCondition
reason: TrueCondition
message: we are true
target: Composite
- type: DatabaseReady
Expand Down
2 changes: 1 addition & 1 deletion context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package main
import (
"testing"

"github.com/crossplane/crossplane-runtime/pkg/logging"
"github.com/crossplane/crossplane-runtime/v2/pkg/logging"
fnv1 "github.com/crossplane/function-sdk-go/proto/v1"
"github.com/crossplane/function-sdk-go/resource"
"github.com/google/go-cmp/cmp"
Expand Down
75 changes: 51 additions & 24 deletions fn.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,31 +194,67 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest)
cd := resource.NewDesiredComposed()
cd.Resource.Unstructured = *obj.DeepCopy()

// TODO(ezgidemirel): Refactor to reduce cyclomatic complexity.
// Check for ready state.
var ready *resource.Ready
if cd.Resource.GetAPIVersion() != metaApiVersion {
if v, found := cd.Resource.GetAnnotations()[annotationKeyReady]; found {
if v != string(resource.ReadyTrue) && v != string(resource.ReadyUnspecified) && v != string(resource.ReadyFalse) {
response.Fatal(rsp, errors.Errorf("invalid function input: invalid %q annotation value %q: must be True, False, or Unspecified", annotationKeyReady, v))
return rsp, nil
}

r := resource.Ready(v)
ready = &r

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

// TODO(ezgidemirel): Refactor to reduce cyclomatic complexity.
// Handle if the composite resource appears in the rendered template.
// Unless resource name annotation is present, update only the status of the desired composite resource.
// Unless resource name annotation is present, update only the status and ready state of the desired composite resource.
name, nameFound := obj.GetAnnotations()[annotationKeyCompositionResourceName]
if cd.Resource.GetAPIVersion() == observedComposite.Resource.GetAPIVersion() && cd.Resource.GetKind() == observedComposite.Resource.GetKind() && !nameFound {
dst := make(map[string]any)
if err := desiredComposite.Resource.GetValueInto("status", &dst); err != nil && !fieldpath.IsNotFound(err) {
response.Fatal(rsp, errors.Wrap(err, "cannot get desired composite status"))
return rsp, nil
dstExists := true
if err := desiredComposite.Resource.GetValueInto("status", &dst); err != nil {
if fieldpath.IsNotFound(err) {
dstExists = false
} else {
response.Fatal(rsp, errors.Wrap(err, "cannot get desired composite status"))
return rsp, nil
}
}

src := make(map[string]any)
if err := cd.Resource.GetValueInto("status", &src); err != nil && !fieldpath.IsNotFound(err) {
response.Fatal(rsp, errors.Wrap(err, "cannot get templated composite status"))
return rsp, nil
srcExists := true
if err := cd.Resource.GetValueInto("status", &src); err != nil {
if fieldpath.IsNotFound(err) {
srcExists = false
} else {
response.Fatal(rsp, errors.Wrap(err, "cannot get templated composite status"))
return rsp, nil
}
}

if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
response.Fatal(rsp, errors.Wrap(err, "cannot merge desired composite status"))
return rsp, nil
// Only update status if there's either existing status or new status content.
if dstExists || srcExists {
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(desiredComposite.Resource.Object).SetValue("status", dst); err != nil {
response.Fatal(rsp, errors.Wrap(err, "cannot set desired composite status"))
return rsp, 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
// Set ready state.
if ready != nil {
desiredComposite.Ready = *ready
}

continue
Expand Down Expand Up @@ -288,18 +324,9 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest)
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) {
response.Fatal(rsp, errors.Errorf("invalid function input: invalid %q annotation value %q: must be True, False, or Unspecified", annotationKeyReady, v))
return rsp, nil
}

cd.Ready = resource.Ready(v)

// Remove meta annotation.
meta.RemoveAnnotations(cd.Resource, annotationKeyReady)
if ready != nil {
cd.Ready = *ready
}

// Remove resource name annotation.
Expand Down
Loading