|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "encoding/json" |
| 6 | + |
| 7 | + "dev.upbound.io/models/com/example/platform/v1alpha1" |
| 8 | + "dev.upbound.io/models/io/upbound/aws/s3/v1beta1" |
| 9 | + "k8s.io/utils/ptr" |
| 10 | + |
| 11 | + "github.com/crossplane/crossplane-runtime/pkg/logging" |
| 12 | + "github.com/crossplane/function-sdk-go/errors" |
| 13 | + fnv1 "github.com/crossplane/function-sdk-go/proto/v1" |
| 14 | + "github.com/crossplane/function-sdk-go/request" |
| 15 | + "github.com/crossplane/function-sdk-go/resource" |
| 16 | + "github.com/crossplane/function-sdk-go/resource/composed" |
| 17 | + "github.com/crossplane/function-sdk-go/response" |
| 18 | +) |
| 19 | + |
| 20 | +// Function is your composition function. |
| 21 | +type Function struct { |
| 22 | + fnv1.UnimplementedFunctionRunnerServiceServer |
| 23 | + |
| 24 | + log logging.Logger |
| 25 | +} |
| 26 | + |
| 27 | +// RunFunction runs the Function. |
| 28 | +func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest) (*fnv1.RunFunctionResponse, error) { |
| 29 | + f.log.Info("Running function", "tag", req.GetMeta().GetTag()) |
| 30 | + rsp := response.To(req, response.DefaultTTL) |
| 31 | + |
| 32 | + observedComposite, err := request.GetObservedCompositeResource(req) |
| 33 | + if err != nil { |
| 34 | + response.Fatal(rsp, errors.Wrap(err, "cannot get xr")) |
| 35 | + return rsp, nil |
| 36 | + } |
| 37 | + |
| 38 | + observedComposed, err := request.GetObservedComposedResources(req) |
| 39 | + if err != nil { |
| 40 | + response.Fatal(rsp, errors.Wrap(err, "cannot get observed resources")) |
| 41 | + return rsp, nil |
| 42 | + } |
| 43 | + |
| 44 | + var xr v1alpha1.XStorageBucket |
| 45 | + if err := convertViaJSON(&xr, observedComposite.Resource); err != nil { |
| 46 | + response.Fatal(rsp, errors.Wrap(err, "cannot convert xr")) |
| 47 | + return rsp, nil |
| 48 | + } |
| 49 | + |
| 50 | + params := xr.Spec.Parameters |
| 51 | + if params.Region == nil || *params.Region == "" { |
| 52 | + response.Fatal(rsp, errors.Wrap(err, "missing region")) |
| 53 | + return rsp, nil |
| 54 | + } |
| 55 | + |
| 56 | + // We'll collect our desired composed resources into this map, then convert |
| 57 | + // them to the SDK's types and set them in the response when we return. |
| 58 | + desiredComposed := make(map[resource.Name]any) |
| 59 | + defer func() { |
| 60 | + desiredComposedResources, err := request.GetDesiredComposedResources(req) |
| 61 | + if err != nil { |
| 62 | + response.Fatal(rsp, errors.Wrap(err, "cannot get desired resources")) |
| 63 | + return |
| 64 | + } |
| 65 | + |
| 66 | + for name, obj := range desiredComposed { |
| 67 | + c := composed.New() |
| 68 | + if err := convertViaJSON(c, obj); err != nil { |
| 69 | + response.Fatal(rsp, errors.Wrapf(err, "cannot convert %s to unstructured", name)) |
| 70 | + return |
| 71 | + } |
| 72 | + desiredComposedResources[name] = &resource.DesiredComposed{Resource: c} |
| 73 | + } |
| 74 | + |
| 75 | + if err := response.SetDesiredComposedResources(rsp, desiredComposedResources); err != nil { |
| 76 | + response.Fatal(rsp, errors.Wrap(err, "cannot set desired resources")) |
| 77 | + return |
| 78 | + } |
| 79 | + }() |
| 80 | + |
| 81 | + bucket := &v1beta1.Bucket{ |
| 82 | + APIVersion: ptr.To("s3.aws.upbound.io/v1beta1"), |
| 83 | + Kind: ptr.To("Bucket"), |
| 84 | + Spec: &v1beta1.BucketSpec{ |
| 85 | + ForProvider: &v1beta1.BucketSpecForProvider{ |
| 86 | + Region: params.Region, |
| 87 | + }, |
| 88 | + }, |
| 89 | + } |
| 90 | + desiredComposed["bucket"] = bucket |
| 91 | + |
| 92 | + // Return early if Crossplane hasn't observed the bucket yet. This means it |
| 93 | + // hasn't been created yet. This function will be called again after it is. |
| 94 | + observedBucket, ok := observedComposed["bucket"] |
| 95 | + if !ok { |
| 96 | + response.Normal(rsp, "waiting for bucket to be created").TargetCompositeAndClaim() |
| 97 | + return rsp, nil |
| 98 | + } |
| 99 | + |
| 100 | + // The desired ACL, encryption, and versioning resources all need to refer |
| 101 | + // to the bucket by its external name, which is stored in its external name |
| 102 | + // annotation. Return early if the Bucket's external-name annotation isn't |
| 103 | + // set yet. |
| 104 | + bucketExternalName := observedBucket.Resource.GetAnnotations()["crossplane.io/external-name"] |
| 105 | + if bucketExternalName == "" { |
| 106 | + response.Normal(rsp, "waiting for bucket to be created").TargetCompositeAndClaim() |
| 107 | + return rsp, nil |
| 108 | + } |
| 109 | + |
| 110 | + acl := &v1beta1.BucketACL{ |
| 111 | + APIVersion: ptr.To("s3.aws.upbound.io/v1beta1"), |
| 112 | + Kind: ptr.To("BucketACL"), |
| 113 | + Spec: &v1beta1.BucketACLSpec{ |
| 114 | + ForProvider: &v1beta1.BucketACLSpecForProvider{ |
| 115 | + Bucket: &bucketExternalName, |
| 116 | + Region: params.Region, |
| 117 | + ACL: params.ACL, |
| 118 | + }, |
| 119 | + }, |
| 120 | + } |
| 121 | + desiredComposed["acl"] = acl |
| 122 | + |
| 123 | + boc := &v1beta1.BucketOwnershipControls{ |
| 124 | + APIVersion: ptr.To("s3.aws.upbound.io/v1beta1"), |
| 125 | + Kind: ptr.To("BucketOwnershipControls"), |
| 126 | + Spec: &v1beta1.BucketOwnershipControlsSpec{ |
| 127 | + ForProvider: &v1beta1.BucketOwnershipControlsSpecForProvider{ |
| 128 | + Bucket: &bucketExternalName, |
| 129 | + Region: params.Region, |
| 130 | + Rule: &[]v1beta1.BucketOwnershipControlsSpecForProviderRule{{ |
| 131 | + ObjectOwnership: ptr.To("BucketOwnerPreferred"), |
| 132 | + }}, |
| 133 | + }, |
| 134 | + }, |
| 135 | + } |
| 136 | + desiredComposed["boc"] = boc |
| 137 | + |
| 138 | + pab := &v1beta1.BucketPublicAccessBlock{ |
| 139 | + APIVersion: ptr.To("s3.aws.upbound.io/v1beta1"), |
| 140 | + Kind: ptr.To("BucketPublicAccessBlock"), |
| 141 | + Spec: &v1beta1.BucketPublicAccessBlockSpec{ |
| 142 | + ForProvider: &v1beta1.BucketPublicAccessBlockSpecForProvider{ |
| 143 | + Bucket: &bucketExternalName, |
| 144 | + Region: params.Region, |
| 145 | + BlockPublicAcls: ptr.To(false), |
| 146 | + RestrictPublicBuckets: ptr.To(false), |
| 147 | + IgnorePublicAcls: ptr.To(false), |
| 148 | + BlockPublicPolicy: ptr.To(false), |
| 149 | + }, |
| 150 | + }, |
| 151 | + } |
| 152 | + desiredComposed["pab"] = pab |
| 153 | + |
| 154 | + sse := &v1beta1.BucketServerSideEncryptionConfiguration{ |
| 155 | + APIVersion: ptr.To("s3.aws.upbound.io/v1beta1"), |
| 156 | + Kind: ptr.To("BucketServerSideEncryptionConfiguration"), |
| 157 | + Spec: &v1beta1.BucketServerSideEncryptionConfigurationSpec{ |
| 158 | + ForProvider: &v1beta1.BucketServerSideEncryptionConfigurationSpecForProvider{ |
| 159 | + Bucket: &bucketExternalName, |
| 160 | + Region: params.Region, |
| 161 | + Rule: &[]v1beta1.BucketServerSideEncryptionConfigurationSpecForProviderRule{{ |
| 162 | + ApplyServerSideEncryptionByDefault: &[]v1beta1.BucketServerSideEncryptionConfigurationSpecForProviderRuleApplyServerSideEncryptionByDefault{{ |
| 163 | + SseAlgorithm: ptr.To("AES256"), |
| 164 | + }}, |
| 165 | + BucketKeyEnabled: ptr.To(true), |
| 166 | + }}, |
| 167 | + }, |
| 168 | + }, |
| 169 | + } |
| 170 | + desiredComposed["sse"] = sse |
| 171 | + |
| 172 | + if params.Versioning != nil && *params.Versioning { |
| 173 | + versioning := &v1beta1.BucketVersioning{ |
| 174 | + APIVersion: ptr.To("s3.aws.upbound.io/v1beta1"), |
| 175 | + Kind: ptr.To("BucketVersioning"), |
| 176 | + Spec: &v1beta1.BucketVersioningSpec{ |
| 177 | + ForProvider: &v1beta1.BucketVersioningSpecForProvider{ |
| 178 | + Bucket: &bucketExternalName, |
| 179 | + Region: params.Region, |
| 180 | + VersioningConfiguration: &[]v1beta1.BucketVersioningSpecForProviderVersioningConfiguration{{ |
| 181 | + Status: ptr.To("Enabled"), |
| 182 | + }}, |
| 183 | + }, |
| 184 | + }, |
| 185 | + } |
| 186 | + desiredComposed["versioning"] = versioning |
| 187 | + } |
| 188 | + |
| 189 | + return rsp, nil |
| 190 | +} |
| 191 | + |
| 192 | +func convertViaJSON(to, from any) error { |
| 193 | + bs, err := json.Marshal(from) |
| 194 | + if err != nil { |
| 195 | + return err |
| 196 | + } |
| 197 | + return json.Unmarshal(bs, to) |
| 198 | +} |
0 commit comments