diff --git a/example/minimal/composition.yaml b/example/minimal/composition.yaml new file mode 100644 index 0000000..930cb09 --- /dev/null +++ b/example/minimal/composition.yaml @@ -0,0 +1,20 @@ +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: function-pkl +spec: + compositeTypeRef: + apiVersion: example.crossplane.io/v1 + kind: XR + mode: Pipeline + pipeline: + - step: run-the-template + functionRef: + name: function-pkl + input: + apiVersion: template.fn.crossplane.io/v1beta1 + kind: Pkl + spec: + type: uri + # This pkl file is at `pkl/crossplane-example/minimal.pkl` in this repo + uri: "package://pkg.pkl-lang.org/github.com/crossplane-contrib/function-pkl/crossplane-example@1.0.0#/minimal.pkl" diff --git a/example/minimal/functions.yaml b/example/minimal/functions.yaml new file mode 100644 index 0000000..95fac53 --- /dev/null +++ b/example/minimal/functions.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: pkg.crossplane.io/v1beta1 +kind: Function +metadata: + name: function-pkl + annotations: + # This tells crossplane beta render to connect to the function locally. + render.crossplane.io/runtime: Development +spec: + # This is ignored when using the Development runtime. + package: function-pkl diff --git a/example/minimal/xr.yaml b/example/minimal/xr.yaml new file mode 100644 index 0000000..25472b9 --- /dev/null +++ b/example/minimal/xr.yaml @@ -0,0 +1,6 @@ +# Replace this with your XR! +apiVersion: example.crossplane.io/v1 +kind: XR +metadata: + name: example-xr +spec: {} diff --git a/go.sum b/go.sum index 89fe6b2..ff69477 100644 --- a/go.sum +++ b/go.sum @@ -261,8 +261,6 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -282,8 +280,6 @@ k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= -k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= -k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= diff --git a/internal/function/fn.go b/internal/function/fn.go index 9a99906..3ad1be0 100644 --- a/internal/function/fn.go +++ b/internal/function/fn.go @@ -87,7 +87,6 @@ func (f *Function) RunFunction(ctx context.Context, req *fnv1beta1.RunFunctionRe Ctx: ctx, })) } - if err != nil { response.Fatal(rsp, errors.Wrap(err, "could not create Pkl Evaluator")) return rsp, nil @@ -104,34 +103,43 @@ func (f *Function) RunFunction(ctx context.Context, req *fnv1beta1.RunFunctionRe return rsp, nil } - helperResponse := &helper.CompositionResponse{} - err = yaml.Unmarshal([]byte(renderedManifest), helperResponse) + rsp, err = toResponse(renderedManifest, req.GetMeta().GetTag()) if err != nil { - return nil, errors.Wrapf(err, "rendered Pkl file was not in expected format. did you amend @crossplane/CompositionResponse.pkl?") + response.Fatal(rsp, errors.Wrap(err, "error while converting Pkl file")) + return rsp, nil } - fixedRequirements := &fnv1beta1.Requirements{ - ExtraResources: convertExtraResources(helperResponse.Requirements.ExtraResources), + return rsp, nil +} + +func toResponse(renderedManifest, tag string) (*fnv1beta1.RunFunctionResponse, error) { + rspHelper := &helper.CompositionResponse{} + err := yaml.Unmarshal([]byte(renderedManifest), rspHelper) + if err != nil { + return nil, errors.Wrapf(err, "rendered Pkl file was not in expected format. did you amend @crossplane/CompositionResponse.pkl?") } responseMeta := &fnv1beta1.ResponseMeta{ - Tag: req.GetMeta().GetTag(), + Tag: tag, Ttl: durationpb.New(response.DefaultTTL), } + if ttl := rspHelper.GetMeta().GetTtl(); ttl != nil { + responseMeta.Ttl = ttl + } // Note: consider not overwriting rsp and whether it makes a difference. - rsp = &fnv1beta1.RunFunctionResponse{ - Meta: responseMeta, - Desired: helperResponse.Desired, - Results: helperResponse.Results, - Context: helperResponse.Context, - Requirements: fixedRequirements, + rsp := &fnv1beta1.RunFunctionResponse{ + Meta: responseMeta, + Desired: rspHelper.Desired, + Results: rspHelper.Results, + Context: rspHelper.Context, } - if ttl := helperResponse.GetMeta().GetTtl(); ttl != nil { - rsp.Meta.Ttl = ttl + if rspHelper.Requirements != nil && rspHelper.Requirements.ExtraResources != nil { + rsp.Requirements = &fnv1beta1.Requirements{ + ExtraResources: convertExtraResources(rspHelper.Requirements.ExtraResources), + } } - return rsp, nil } diff --git a/internal/function/fn_test.go b/internal/function/fn_test.go index 7dcb3d3..fdd121b 100644 --- a/internal/function/fn_test.go +++ b/internal/function/fn_test.go @@ -29,6 +29,7 @@ import ( "github.com/crossplane/crossplane-runtime/pkg/logging" fnv1beta1 "github.com/crossplane/function-sdk-go/proto/v1beta1" "github.com/crossplane/function-sdk-go/resource" + "github.com/crossplane/function-sdk-go/response" "github.com/crossplane-contrib/function-pkl/input/v1beta1" ) @@ -54,6 +55,71 @@ func TestRunFunction(t *testing.T) { args args want want }{ + "Minimal": { + reason: "The Function should create a single resource", + args: args{ + ctx: context.TODO(), + req: &fnv1beta1.RunFunctionRequest{ + Input: resource.MustStructObject(&v1beta1.Pkl{ + Spec: v1beta1.PklSpec{ + Type: "local", + Local: &v1beta1.Local{ + ProjectDir: pklPackage, + File: pklPackage + "/minimal.pkl", + }, + }, + }), + Observed: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{ + Resource: resource.MustStructJSON(`{ + "apiVersion": "example.crossplane.io/v1", + "kind": "XR", + "metadata": { + "name": "example-xr" + }, + "spec": {} + }`), + }, + }, + }, + }, + want: want{ + rsp: &fnv1beta1.RunFunctionResponse{ + Meta: &fnv1beta1.ResponseMeta{ + Ttl: durationpb.New(response.DefaultTTL), + }, + Desired: &fnv1beta1.State{ + Composite: &fnv1beta1.Resource{}, + Resources: map[string]*fnv1beta1.Resource{ + "cm-minimal": { + Resource: resource.MustStructJSON(`{ + "apiVersion": "kubernetes.crossplane.io/v1alpha2", + "kind": "Object", + "metadata": { + "name": "cm-one" + }, + "spec": { + "forProvider": { + "manifest": { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "name": "cm-minimal", + "namespace": "crossplane-system" + }, + "data": { + "foo": "bar" + } + } + } + } + }`), + }, + }, + }, + }, + }, + }, "Full": { reason: "The Function should create a full functionResult", args: args{ diff --git a/pkl/crossplane.contrib.example/full.pkl b/pkl/crossplane.contrib.example/full.pkl index 9e7ec50..401bba2 100644 --- a/pkl/crossplane.contrib.example/full.pkl +++ b/pkl/crossplane.contrib.example/full.pkl @@ -1,5 +1,4 @@ amends "@crossplane.contrib/CompositionResponse.pkl" -import "@crossplane.contrib/Resource.pkl" import "@crossplane.contrib/crossplane.pkl" import "crds/XR.pkl" @@ -42,7 +41,7 @@ desired { } } resources { - ["cm-one"] = new Resource { + ["cm-one"] = new { resource = new Object { metadata { name = "cm-one" diff --git a/pkl/crossplane.contrib.example/minimal.pkl b/pkl/crossplane.contrib.example/minimal.pkl new file mode 100644 index 0000000..2416d17 --- /dev/null +++ b/pkl/crossplane.contrib.example/minimal.pkl @@ -0,0 +1,28 @@ +amends "@crossplane.contrib/CompositionResponse.pkl" +import "crds/Object.pkl" +import "@k8s/api/core/v1/ConfigMap.pkl" + +desired { + resources { + ["cm-minimal"] = new { + resource = new Object { + metadata { + name = "cm-one" + } + spec { + forProvider { + manifest = new ConfigMap { + metadata { + name = "cm-minimal" + namespace = "crossplane-system" + } + data { + ["foo"] = "bar" + } + } + } + } + } + } + } +} \ No newline at end of file