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
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,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 @@ -181,7 +190,7 @@ data:
region: {{ $spec.region }}
id: field
array:
- "1"
- "1"
- "2"
```

Expand Down Expand Up @@ -294,24 +303,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
83 changes: 55 additions & 28 deletions fn.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,38 +145,74 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest)
}

// Initialize the requirements.
requirements := &fnv1.Requirements{ExtraResources: make(map[string]*fnv1.ResourceSelector)}
requirements := &fnv1.Requirements{Resources: make(map[string]*fnv1.ResourceSelector)}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in latest function-sdk-go that this PR depend on


// Convert the rendered manifests to a list of desired composed resources.
for _, obj := range objs {
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 @@ -232,11 +268,11 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest)
return rsp, nil
}
for k, v := range ers {
if _, found := requirements.ExtraResources[k]; found {
if _, found := requirements.Resources[k]; found {
response.Fatal(rsp, errors.Errorf("duplicate extra resource key %q", k))
return rsp, nil
}
requirements.ExtraResources[k] = v.ToResourceSelector()
requirements.Resources[k] = v.ToResourceSelector()
}
default:
response.Fatal(rsp, errors.Errorf("invalid kind %q for apiVersion %q - must be one of CompositeConnectionDetails, Context or ExtraResources", obj.GetKind(), metaApiVersion))
Expand All @@ -246,18 +282,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 Expand Up @@ -285,7 +312,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest)
return rsp, nil
}

if len(requirements.ExtraResources) > 0 {
if len(requirements.Resources) > 0 {
rsp.Requirements = requirements
}

Expand Down
Loading
Loading