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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
/bin
*.kubeconfig

# runtime artifacts of hack/run.bash
kubeconfigs/
*.log
28 changes: 28 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,31 @@ mv $(1) $(1)-$(3) ;\
} ;\
ln -sf $(1)-$(3) $(1)
endef

## ---------------------------------------------------------------------------
## Workflow targets for the simple-bucket example (see hack/run.bash;
## `task <name>` from Taskfile.yml is equivalent).
.PHONY: run-all translation setup provider example status crossplane \
rgd-gcp rgd-s3 provider-gcp provider-gcp-setup provider-gcp-example \
provider-gcp-translation forward cleanup destroy build

run-all: ; ./hack/run.bash all
translation: ; ./hack/run.bash translation
setup: ; ./hack/run.bash setup
provider: ; ./hack/run.bash provider
example: ; ./hack/run.bash example
status: ; ./hack/run.bash status
crossplane: ; ./hack/run.bash crossplane
rgd-gcp: ; ./hack/run.bash rgd:gcp
rgd-s3: ; ./hack/run.bash rgd:s3
provider-gcp: ; ./hack/run.bash provider-gcp
provider-gcp-setup: ; ./hack/run.bash provider-gcp:setup
provider-gcp-example: ; ./hack/run.bash provider-gcp:example
provider-gcp-translation: ; ./hack/run.bash provider-gcp:translation
forward: ; ./hack/run.bash forward
cleanup: ; ./hack/run.bash cleanup
destroy: ; ./hack/run.bash destroy

build:
cd api && go build ./... && go vet ./...
go build ./... && go vet ./...
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,13 @@ The consumer's binding and order are in `consumer/`.
`kind/manifests/` holds the floci fake-cloud storage backends.

The overarching play-by-play resource changes are documented in `DEMO.md`.

## Runnable example

[`examples/simple-bucket/`](examples/simple-bucket/) is a verified
end-to-end run of the extraction chain on one kind cluster and one kcp
workspace: a generic `Bucket` order (storage.opendefense.cloud) is
translated by a kro RGD into a vendor S3 resource and fulfilled on the
floci S3 emulator - credentials Secret and status flow back to the
consumer. `task all` (or `make run-all`) drives everything; see the example
README.
88 changes: 88 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Task runner entry points (https://taskfile.dev). Every task is a thin
# wrapper around hack/run.bash so that `make <target>` and plain
# `hack/run.bash <cmd>` behave identically - use whichever you prefer.
version: "3"

tasks:
default:
desc: List all tasks
cmds: [task --list]
silent: true

all:
desc: "EVERYTHING unattended: setup, operator, example bucket, results"
cmds: [./hack/run.bash all]

translation:
desc: "Show the bucket at every layer: generic API -> vendor API -> storage"
cmds: [./hack/run.bash translation]

setup:
desc: "Create kind cluster with kcp (1 workspace), kro, floci-aws, api-syncagent"
cmds: [./hack/run.bash setup]

provider:
desc: "Run the S3 vendor operator locally (blocks - use a second terminal)"
cmds: [./hack/run.bash provider]

example:
desc: "Order a Bucket through the generic API and show the result"
cmds: [./hack/run.bash example]

status:
desc: "Show the consumer-side view of the Bucket"
cmds: [./hack/run.bash status]

crossplane:
desc: "Install Crossplane + floci-gcp emulator + upbound provider-gcp-storage"
cmds: [./hack/run.bash crossplane]

rgd:gcp:
desc: "Switch translation to GCP/Crossplane (destroys existing Buckets)"
cmds: [./hack/run.bash rgd:gcp]

rgd:s3:
desc: "Switch translation back to S3/floci-aws (destroys existing Buckets)"
cmds: [./hack/run.bash rgd:s3]

provider-gcp:
desc: "EVERYTHING for the REAL provider-gcp controller (../provider-gcp) against floci-gcp"
cmds: [./hack/run.bash provider-gcp]

provider-gcp:setup:
desc: "Swap to the real Bucket schema, build+deploy provider-gcp + floci-gcp"
cmds: [./hack/run.bash provider-gcp:setup]

provider-gcp:example:
desc: "Order a Bucket and validate it against floci-gcp"
cmds: [./hack/run.bash provider-gcp:example]

provider-gcp:translation:
desc: "Re-print the provider-gcp result at every layer"
cmds: [./hack/run.bash provider-gcp:translation]

forward:
desc: "Restart all port-forwards (kcp, floci-aws, floci-gcp)"
cmds: [./hack/run.bash forward]

generate:
desc: "Regenerate deepcopy code and CRDs from the api/ module"
dir: api
cmds:
- go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.21.0
object:headerFile=/dev/null paths=./...
crd output:crd:artifacts:config=../config/crd

build:
desc: "Build and vet both Go modules"
cmds:
- cd api && go build ./... && go vet ./...
- go build ./... && go vet ./...

cleanup:
desc: "Remove example resources (cluster stays up)"
cmds: [./hack/run.bash cleanup]

destroy:
desc: "Delete the kind cluster and kubeconfigs"
cmds: [./hack/run.bash destroy]
5 changes: 4 additions & 1 deletion api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ module github.com/platform-mesh/example-api-extraction/api

go 1.26.0

require k8s.io/apimachinery v0.36.2
require (
k8s.io/apimachinery v0.36.2
sigs.k8s.io/controller-runtime v0.24.1
)

require (
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
Expand Down
24 changes: 24 additions & 0 deletions api/go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
Expand All @@ -6,9 +8,13 @@ github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sa
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand All @@ -17,6 +23,10 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/onsi/ginkgo/v2 v2.27.4 h1:fcEcQW/A++6aZAZQNUmNjvA9PSOzefMJBerHJ4t8v8Y=
github.com/onsi/ginkgo/v2 v2.27.4/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q=
github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand All @@ -30,16 +40,28 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.36.0 h1:SgqDhZzHdOtMk40xVSvCXkP9ME0H05hPM3p9AB1kL80=
k8s.io/api v0.36.0/go.mod h1:m1LVrGPNYax5NBHdO+QuAedXyuzTt4RryI/qnmNvs34=
k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ=
k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
Expand All @@ -48,6 +70,8 @@ k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hk
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4=
sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
Expand Down
176 changes: 176 additions & 0 deletions api/opendefense/v1alpha1/bucket_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
Copyright The Platform Mesh Authors.
SPDX-License-Identifier: Apache-2.0

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)

// StorageClass defines how objects in the bucket are stored and determines
// the SLA and the cost of storage.
// +kubebuilder:validation:Enum=Standard;Nearline;Coldline;Archive
type StorageClass string

const (
StorageClassStandard StorageClass = "Standard"
StorageClassNearline StorageClass = "Nearline"
StorageClassColdline StorageClass = "Coldline"
StorageClassArchive StorageClass = "Archive"
)

// BucketSpec defines the desired state of a Bucket.
type BucketSpec struct {
// BucketName is the name of the bucket.
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:example="my-bucket"
BucketName string `json:"bucketName"`

// BucketPolicy holds a provider-neutral bucket policy document.
// +optional
BucketPolicy *runtime.RawExtension `json:"bucketPolicy,omitempty"`

// BucketSize defines the size (in GB) of the bucket.
// +optional
// +kubebuilder:default=20
// +kubebuilder:example=50
BucketSize int64 `json:"bucketSize,omitempty"`

// Description of bucket contents.
// +optional
// +kubebuilder:example="my-bucket-for-backups"
Description string `json:"description,omitempty"`

// Encryption enables server-side encryption at rest.
// +optional
// +kubebuilder:default=false
// +kubebuilder:example=true
Encryption bool `json:"encryption,omitempty"`

// Permissions holds access permission definitions for the bucket.
// +optional
Permissions *runtime.RawExtension `json:"permissions,omitempty"`

// ProviderConfig holds optional, provider-specific Bucket settings.
// It is intentionally schemaless: each provider documents the keys it
// understands. The S3 PoC provider understands
// {"provider": "<name>"} to select one of its configured backends,
// which is also the switch used during a migration cutover.
// +optional
ProviderConfig *runtime.RawExtension `json:"providerConfig,omitempty"`

// Region defines the region where the bucket is stored.
// +optional
// +kubebuilder:example="de-boe-1"
Region string `json:"region,omitempty"`

// StorageClass defines how objects in the bucket are stored and
// determines the SLA and the cost of storage.
// +optional
// +kubebuilder:default=Standard
StorageClass StorageClass `json:"storageClass,omitempty"`
}

// SecretRef references a Kubernetes Secret by name. The Secret lives in the
// same namespace as the referencing resource.
type SecretRef struct {
// Name is the metadata.name of the Secret resource.
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:example="example-secret"
Name string `json:"name"`
}

// BucketStatus defines the observed state of a Bucket, populated by the
// provider controller.
type BucketStatus struct {
// AllocatedRegion is the region where the bucket is stored.
// +optional
AllocatedRegion string `json:"allocatedRegion,omitempty"`

// BucketID is the unique identifier assigned to the bucket.
// +optional
// +kubebuilder:example="obs-e2f5g8"
BucketID string `json:"bucketId,omitempty"`

// BucketSecret references the Kubernetes Secret containing the bucket
// credentials. The Secret lives in the same namespace as the Bucket.
// +optional
BucketSecret *SecretRef `json:"bucketSecret,omitempty"`

// Conditions represent the latest available observations.
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`

// EncryptionStatus holds the observed encryption state.
// +optional
EncryptionStatus *runtime.RawExtension `json:"encryptionStatus,omitempty"`

// Endpoint is the S3-compatible endpoint URL.
// +optional
// +kubebuilder:example="https://my-bucket.s3.provider.io"
Endpoint string `json:"endpoint,omitempty"`

// ProviderStatus holds provider-specific observed state that has no
// generic representation. The S3 PoC provider records
// {"provider": "<name>"} here so a provider switch (migration cutover)
// is observable.
// +optional
ProviderStatus *runtime.RawExtension `json:"providerStatus,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Bucket",type=string,JSONPath=`.spec.bucketName`
// +kubebuilder:printcolumn:name="Region",type=string,JSONPath=`.status.allocatedRegion`
// +kubebuilder:printcolumn:name="Endpoint",type=string,JSONPath=`.status.endpoint`
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`

// Bucket is the Schema for the buckets API.
type Bucket struct {
metav1.TypeMeta `json:",inline"`

// metadata is a standard object metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitempty"`

// spec defines the desired state of the Bucket
// +required
Spec BucketSpec `json:"spec"`

// status defines the observed state of the Bucket
// +optional
Status BucketStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// BucketList contains a list of Bucket.
type BucketList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Bucket `json:"items"`
}

func init() {
SchemeBuilder.Register(&Bucket{}, &BucketList{})
}
Loading