crossplane-function-inventory-sdk

module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: Apache-2.0

README

Inventory SDK for go crossplane functions

A simplified library for building Crossplane composition functions that provides type-safe resource management through an inventory-based approach.

Introduction

Crossplane inventory SDK eliminates the complexity of manually handling Crossplane function requests and responses by providing a clean, tag-driven inventory system. Instead of working directly with unstructured data and managing complex resource lifecycles, you define your resource requirements using Go struct tags and let Crossplane inventory SDK handle the rest.

Key features:

  • Type Safety: Automatic conversion between unstructured Kubernetes resources and strongly-typed Go objects
  • Tag-Based Configuration: Declarative resource binding through struct tags
  • Simplified API: Clean request → inventory → response flow

Getting Started

How your fn.go could look like

package main

import (
	"context"

	"github.com/crossplane/crossplane-runtime/pkg/logging"
	fnv1 "github.com/crossplane/function-sdk-go/proto/v1"
	"github.com/crossplane/function-sdk-go/response"
	"github.com/pkg/errors"
	"github.com/bakito/crossplane-function-inventory-sdk/inventory"
)

// Declare yor inventory
type Inventory struct {
	ObservedComposedNS      *corev1.Namespace           `crossplane:"observed-composed:ns"`
	ObservedComposedNSReady fnv1.Ready                  `crossplane:"observed-composed:ns"`
	ObservedComposedNSConn  inventory.ConnectionDetails `crossplane:"observed-composed:ns"`

	DesiredComposedNS      *akcv1alpha1.AzureKubernetesCluster `crossplane:"desired-composed:ns"`
	DesiredComposedNSReady fnv1.Ready                          `crossplane:"desired-composed:ns"`

	DesiredComposedMap      map[string]*corev1.ConfigMap           `crossplane:"desired-composed:cm-"`
	DesiredComposedMapReady map[string]fnv1.Ready                  `crossplane:"desired-composed:cm-"`
	DesiredComposedMapConn  map[string]inventory.ConnectionDetails `crossplane:"desired-composed:cm-"`

	ObservedComposite      *v1alpha1.Subscription      `crossplane:"observed-composite"`
	ObservedCompositeReady fnv1.Ready                  `crossplane:"observed-composite"`
	ObservedCompositeConn  inventory.ConnectionDetails `crossplane:"observed-composite"`
}

func CreateNS(inv &Inventory) error {
	inv.DesiredComposedNS = &corev1.Namespace{}
	return nil
}

// Function returns whatever response you ask it to.
type Function struct {
	fnv1.UnimplementedFunctionRunnerServiceServer
	log logging.Logger
}

// RunFunction runs the Function.
func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest) (*fnv1.RunFunctionResponse, error) {
	rsp := response.To(req, response.DefaultTTL)

	inv := &Inventory{}
	if err := inventory.BuildFromRequest(req, f.log, inv); err != nil {
		response.Fatal(rsp, errors.Wrap(err, "failed to build inventory"))
		return rsp, nil
	}

	if err := CreateNS(inv); err != nil {
		response.Fatal(rsp, errors.Wrap(err, "failed to create namespace"))
		return rsp, nil
	}

	if err := inventory.ConvertToResponse(rsp, inv); err != nil {
		response.Fatal(rsp, errors.Wrap(err, "failed to convert inventory to response"))
		return rsp, nil
	}
	return rsp, nil
}

Tag Combinations

The crossplane struct tag supports various combinations to handle different resource types and access patterns:

Composite Resource
// Observed Composite
ObservedComposite        *corev1.ConfigMap `crossplane:"observed-composite"`
ObservedCompositeConn    inventory.ConnectionDetails `crossplane:"observed-composite"`

// Desired Composite

// The desired composite resource can only modify the status of the resource
DesiredComposite        *corev1.ConfigMap `crossplane:"desired-composite"`
DesiredCompositeConn    inventory.ConnectionDetails `crossplane:"desired-composite"`
Single Managed Resource
// Access individual resources by exact name
ObservedNamespace        *corev1.Namespace                `crossplane:"observed-composed:my-namespace"`
ObservedNamespaceConn    inventory.ConnectionDetails    `crossplane:"observed-composed:my-namespace"`
// Create individual resource by exact name
DesiredNamespace        *corev1.Namespace                `crossplane:"desired-composed:my-namespace"`
DesiredNamespaceReady    fnv1.Ready                        `crossplane:"desired-composed:my-namespace"`
Map-Based Managed Resource (Prefix Matching)
// Access multiple resources matching a prefix
ObservedNamespace        map[string]*corev1.Namespace            `crossplane:"observed-composed:ns-"`
ObservedNamespaceConn    map[string]inventory.ConnectionDetails    `crossplane:"observed-composed:ns-"`
// Creating multiple resources matching a prefix
DesiredNamespace        map[string]*corev1.Namespace            `crossplane:"desired-composed:ns-"`
DesiredNamespaceReady    map[string]fnv1.Ready                    `crossplane:"desired-composed:ns-"`
Function Input
// Access function input
FunctionInput    *myapi.MyInput                `crossplane:"input"`
Required Resources (Extra Resources)

Requesting extra resources:

req, err := require.Requires(
require.As(ExtraResourcesServer).WithRO(&v1beta1.MSSQLServer{}).MatchName(sqlNaming.ServerName()),
require.As(ExtraResourcesElasticPool).WithRO(&v1beta1.MSSQLElasticPool{}).MatchName(sqlNaming.ElasticPoolName()),
)
if err != nil {
return err
}

provided, err := req.Assure(req)
if !provided {
m.Log.Debug("No extra resources present, exiting", "requirements", rsp.GetRequirements())
return rsp, nil
}
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "requirements not fulfilled"))
return rsp, nil
}

req.Register(rsp)


Accessing extra resources

// Access a single extra resource
ExtraPod      *corev1.Pod `crossplane:"extra:pod-extra"`
ExtraPodReady fnv1.Ready  `crossplane:"extra:pod-extra"`
// Accessing a list of extra resources
ExtraSvc      []*corev1.Service `crossplane:"extra:svc-extra"`
ExtraSvcReady []fnv1.Ready      `crossplane:"extra:svc-extra"`

Generating Constants

The generator scans your struct tags and creates typed constants for all resource names, helping prevent typos and making refactoring easier.

It's recommended to setup the genearation within a generate.go file:

//go:build generate
// +build generate

//go:generate go run -tags github.com/bakito/crossplane-function-inventory-sdk/cmd/tag-const-gen -i inventory.go

Testing

Golden File Testing allows verifying the resources generated by the function against golden files.

The golden files are named as follows: <composite-name>-<function-resource-key>.yaml. The file for the desired composite is named <composite-name>-composite.yaml.

This test case will start the function and compare the desired output against all the golden files.

package main

import (
	"testing"

	ft "github.com/bakito/crossplane-function-inventory-sdk/testing"

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

func TestRunFunctionGolden(t *testing.T) {
	cases := map[string]ft.GoldenFileCase{
		"My Function Test": {
			// Reason for the test case
			Reason: "Test case description",
			// Composite resource input file
			Composite: "testdata/xr.yaml",
			// Function input file
			Input: "testdata/input.yaml",
			// Location of the golden files
			DesiredLocation: "testdata/golden/",
		},
	}

	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			tc.Run(t, &Function{log: logging.NewNopLogger()})
		})
	}
}
Generate / update golden files

The golden files can be generated/updated by running the test with the -update flag.

go test -v -update

Directories

Path Synopsis
cmd
tag-const-gen command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL