Skip to content
Merged
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
14 changes: 13 additions & 1 deletion fn.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"context"
"fmt"
"maps"
"time"

"github.com/crossplane-contrib/function-patch-and-transform/input/v1beta1"
"github.com/crossplane/crossplane-runtime/v2/pkg/errors"
"github.com/crossplane/crossplane-runtime/v2/pkg/fieldpath"
"github.com/crossplane/crossplane-runtime/v2/pkg/logging"
"github.com/crossplane/crossplane-runtime/v2/pkg/reconciler/managed"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/structpb"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/json"
Expand All @@ -27,6 +29,7 @@ type Function struct {
fnv1.UnimplementedFunctionRunnerServiceServer

log logging.Logger
ttl time.Duration
}

// RunFunction runs the Function.
Expand All @@ -37,14 +40,23 @@ func (f *Function) RunFunction(ctx context.Context, req *fnv1.RunFunctionRequest
log.Debug("Running Function")

// TODO(negz): We can probably use a longer TTL if all resources are ready.
rsp := response.To(req, response.DefaultTTL)
rsp := response.To(req, f.ttl)

input := &v1beta1.Resources{}
if err := request.GetInput(req, input); err != nil {
response.Fatal(rsp, errors.Wrap(err, "cannot get Function input"))
return rsp, nil
}

if input.TTL != "" {
dur, err := time.ParseDuration(input.TTL)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot set ttl"))
return rsp, nil
}
rsp.Meta.Ttl = durationpb.New(dur)
}

// Our input is an opaque object nested in a Composition, so unfortunately
// it won't handle validation for us.
if err := ValidateResources(input); err != nil {
Expand Down
36 changes: 35 additions & 1 deletion fn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"testing"
"time"

"github.com/crossplane-contrib/function-patch-and-transform/input/v1beta1"
"github.com/crossplane/crossplane-runtime/v2/pkg/logging"
Expand Down Expand Up @@ -1535,7 +1536,7 @@ func TestRunFunction(t *testing.T) {

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
f := &Function{log: logging.NewNopLogger()}
f := &Function{log: logging.NewNopLogger(), ttl: response.DefaultTTL}
rsp, err := f.RunFunction(tc.args.ctx, tc.args.req)

if diff := cmp.Diff(tc.want.rsp, rsp, protocmp.Transform()); diff != "" {
Expand All @@ -1549,6 +1550,39 @@ func TestRunFunction(t *testing.T) {
}
}

func TestRunFunctionCacheTTL(t *testing.T) {
f := &Function{log: logging.NewNopLogger(), ttl: response.DefaultTTL}
rsp, err := f.RunFunction(context.Background(), &fnv1.RunFunctionRequest{
Input: resource.MustStructObject(&v1beta1.Resources{
TTL: "5m",
Resources: []v1beta1.ComposedTemplate{
{
Name: "cool-resource",
Base: &runtime.RawExtension{Raw: []byte(`{"apiVersion":"example.org/v1","kind":"CD"}`)},
},
},
}),
Observed: &fnv1.State{
Composite: &fnv1.Resource{
Resource: resource.MustStructJSON(`{"apiVersion":"example.org/v1","kind":"XR"}`),
},
},
Desired: &fnv1.State{
Composite: &fnv1.Resource{
Resource: resource.MustStructJSON(`{"apiVersion":"example.org/v1","kind":"XR"}`),
},
},
})
if err != nil {
t.Fatalf("RunFunction(...): %v", err)
}

want := durationpb.New(5 * time.Minute)
if diff := cmp.Diff(want, rsp.GetMeta().GetTtl(), protocmp.Transform()); diff != "" {
t.Errorf("RunFunction(...): -want TTL, +got TTL:\n%s", diff)
}
}

// Crossplane sends as context a fake resource:
// { "apiVersion": "internal.crossplane.io/v1alpha1", "kind": "Environment", ... the actual environment content ... }
// See: https://github.com/crossplane/crossplane/blob/806f0d20d146f6f4f1735c5ec6a7dc78923814b3/internal/controller/apiextensions/composite/environment_fetcher.go#L85C1-L85C1
Expand Down
4 changes: 4 additions & 0 deletions input/v1beta1/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ type Resources struct {
// composite resource is created.
Resources []ComposedTemplate `json:"resources"`

// TTL for which a response can be cached in time.Duration format. Default "1m".
// +optional
TTL string `json:"ttl,omitempty"`

// WriteConnectionSecretToRef specifies the name and namespace of a Secret
// to which any connection details for this composite resource should be
// written. This field is only used for Crossplane v2 composite resources.
Expand Down
21 changes: 15 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@
package main

import (
"time"

"github.com/alecthomas/kong"

"github.com/crossplane/function-sdk-go"
"github.com/crossplane/function-sdk-go/response"
)

// CLI of this Function.
type CLI struct {
Debug bool `help:"Emit debug logs in addition to info logs." short:"d"`

Network string `default:"tcp" help:"Network on which to listen for gRPC connections."`
Address string `default:":9443" help:"Address at which to listen for gRPC connections."`
TLSCertsDir string `env:"TLS_SERVER_CERTS_DIR" help:"Directory containing server certs (tls.key, tls.crt) and the CA used to verify client certificates (ca.crt)"`
Insecure bool `help:"Run without mTLS credentials. If you supply this flag --tls-server-certs-dir will be ignored."`
MaxRecvMessageSize int `default:"4" help:"Maximum size of received messages in MB."`
Network string `default:"tcp" help:"Network on which to listen for gRPC connections."`
Address string `default:":9443" help:"Address at which to listen for gRPC connections."`
TLSCertsDir string `env:"TLS_SERVER_CERTS_DIR" help:"Directory containing server certs (tls.key, tls.crt) and the CA used to verify client certificates (ca.crt)"`
Insecure bool `help:"Run without mTLS credentials. If you supply this flag --tls-server-certs-dir will be ignored."`
MaxRecvMessageSize int `default:"4" help:"Maximum size of received messages in MB."`
TTL *time.Duration `default:"1m" help:"Time to live for function response."`
}

// Run this Function.
Expand All @@ -25,7 +29,12 @@ func (c *CLI) Run() error {
return err
}

return function.Serve(&Function{log: log},
ttl := response.DefaultTTL
if c.TTL != nil {
ttl = *c.TTL
}

return function.Serve(&Function{log: log, ttl: ttl},
function.Listen(c.Network, c.Address),
function.MTLSCertificates(c.TLSCertsDir),
function.Insecure(c.Insecure),
Expand Down
4 changes: 4 additions & 0 deletions package/input/pt.fn.crossplane.io_resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1286,6 +1286,10 @@ spec:
- name
type: object
type: array
ttl:
description: TTL for which a response can be cached in time.Duration format.
Default "1m".
type: string
writeConnectionSecretToRef:
description: |-
WriteConnectionSecretToRef specifies the name and namespace of a Secret
Expand Down
Loading