From 8f429023522ad66c4fe6d7a1546b8759e467e5dc Mon Sep 17 00:00:00 2001 From: Scott Nichols Date: Thu, 7 Aug 2025 14:14:44 -0700 Subject: [PATCH] trim the implementing safestart guide Signed-off-by: Scott Nichols --- .../master/guides/implementing-safestart.md | 327 +----------------- 1 file changed, 4 insertions(+), 323 deletions(-) diff --git a/content/master/guides/implementing-safestart.md b/content/master/guides/implementing-safestart.md index e1d80e91..7dcf9bb1 100644 --- a/content/master/guides/implementing-safestart.md +++ b/content/master/guides/implementing-safestart.md @@ -59,96 +59,7 @@ Crossplane supports flexible capability matching. `safe-start`, `safestart`, and `safe-start` are all recognized as the same capability. {{< /hint >}} -### Step 2: Enhance managed resource definition generation - -Update your MRD generation to include connection details documentation: - -{{< tabs >}} -{{< tab "Go Controller Runtime" >}} -```go -// In your MRD generation code -type ManagedResourceDefinition struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec ManagedResourceDefinitionSpec `json:"spec"` - Status ManagedResourceDefinitionStatus `json:"status,omitempty"` -} - -type ManagedResourceDefinitionSpec struct { - // Standard CRD fields - Group string `json:"group"` - Names Names `json:"names"` - Scope string `json:"scope"` - - // safe-start-specific fields - ConnectionDetails []ConnectionDetail `json:"connectionDetails,omitempty"` - State ResourceState `json:"state,omitempty"` -} - -type ConnectionDetail struct { - Name string `json:"name"` - Description string `json:"description"` - Type string `json:"type"` - FromConnectionSecretKey string `json:"fromConnectionSecretKey,omitempty"` -} -``` -{{< /tab >}} - -{{< tab "Terrajet/Upjet Provider" >}} -```go -// In your provider configuration -func GetProvider() *ujconfig.Provider { - pc := ujconfig.NewProvider([]byte(providerSchema), resourcePrefix, modulePath, - ujconfig.WithIncludeList(ExternalNameConfigured()), - ujconfig.WithDefaultResourceOptions( - ExternalNameConfigurations(), - safe-startConfiguration(), // Add safe-start config - )) - - // Configure safe-start for specific resources - for _, configure := range []func(provider *ujconfig.Provider){ - configureConnectionDetails, - configureMRDDocumentation, - } { - configure(pc) - } - - return pc -} - -func configureConnectionDetails(p *ujconfig.Provider) { - // Example: RDS Instance connection details - p.AddResourceConfigurator("aws_db_instance", func(r *ujconfig.Resource) { - r.ConnectionDetails = map[string]ujconfig.ConnectionDetail{ - "endpoint": { - Description: "The RDS instance endpoint", - Type: "string", - FromConnectionSecretKey: "endpoint", - }, - "port": { - Description: "The port on which the DB accepts connections", - Type: "integer", - FromConnectionSecretKey: "port", - }, - "username": { - Description: "The master username for the database", - Type: "string", - FromConnectionSecretKey: "username", - }, - "password": { - Description: "The master password for the database", - Type: "string", - FromConnectionSecretKey: "password", - }, - } - }) -} -``` -{{< /tab >}} -{{< /tabs >}} - -### Step 3: Update RBAC Permissions +### Step 2: Update RBAC Permissions safe-start providers need extra permissions to manage CRDs dynamically. Crossplane's RBAC manager automatically provides these permissions when you install safe-start providers. @@ -162,10 +73,7 @@ Manual RBAC configuration is only required if you disable Crossplane's RBAC mana # safe-start permissions - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] - verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] -- apiGroups: ["apiextensions.crossplane.io"] - resources: ["managedresourcedefinitions"] - verbs: ["get", "list", "watch", "update", "patch"] + verbs: ["get", "list", "watch"] ``` **Manual configuration (only if you disable RBAC manager):** @@ -186,184 +94,10 @@ rules: # safe-start permissions - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] - verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] -- apiGroups: ["apiextensions.crossplane.io"] - resources: ["managedresourcedefinitions"] - verbs: ["get", "list", "watch", "update", "patch"] + verbs: ["get", "list", "watch"] ``` -### Step 4: Implement managed resource definition controller logic - -Add controller logic to handle MRD activation and CRD lifecycle: - -```go -package controller - -import ( - "context" - - apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - xpv1alpha1 "github.com/crossplane/crossplane/apis/apiextensions/v1alpha1" -) - -// MRDReconciler handles MRD activation -type MRDReconciler struct { - client.Client - Scheme *runtime.Scheme -} - -func (r *MRDReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { - mrd := &xpv1alpha1.ManagedResourceDefinition{} - if err := r.Get(ctx, req.NamespacedName, mrd); err != nil { - return reconcile.Result{}, client.IgnoreNotFound(err) - } - - // Check if MRD should be active - if mrd.Spec.State != nil && *mrd.Spec.State == xpv1alpha1.ResourceStateActive { - return r.ensureCRDExists(ctx, mrd) - } - - // If inactive, ensure CRD is removed - return r.ensureCRDRemoved(ctx, mrd) -} - -func (r *MRDReconciler) ensureCRDExists(ctx context.Context, mrd *xpv1alpha1.ManagedResourceDefinition) (reconcile.Result, error) { - crd := &apiextv1.CustomResourceDefinition{} - crdName := mrd.Spec.Names.Plural + "." + mrd.Spec.Group - - err := r.Get(ctx, types.NamespacedName{Name: crdName}, crd) - if client.IgnoreNotFound(err) != nil { - return reconcile.Result{}, err - } - - if err != nil { // CRD doesn't exist - return r.createCRD(ctx, mrd) - } - - // CRD exists, ensure it's up to date - return r.updateCRD(ctx, mrd, crd) -} - -func (r *MRDReconciler) createCRD(ctx context.Context, mrd *xpv1alpha1.ManagedResourceDefinition) (reconcile.Result, error) { - crd := &apiextv1.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: mrd.Spec.Names.Plural + "." + mrd.Spec.Group, - OwnerReferences: []metav1.OwnerReference{{ - APIVersion: mrd.APIVersion, - Kind: mrd.Kind, - Name: mrd.Name, - UID: mrd.UID, - Controller: pointer.Bool(true), - }}, - }, - Spec: mrd.Spec.CustomResourceDefinitionSpec, - } - - return reconcile.Result{}, r.Create(ctx, crd) -} -``` - -### Step 5: Update build and continuous integration processes - -Update your build process to generate MRDs alongside CRDs: - -{{< tabs >}} -{{< tab "Makefile" >}} -```makefile -# Update your Makefile to generate both CRDs and MRDs -.PHONY: generate -generate: controller-gen - $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." - $(CONTROLLER_GEN) crd:allowDangerousTypes=true paths="./..." output:crd:artifacts:config=package/crds - $(CONTROLLER_GEN) mrd:allowDangerousTypes=true paths="./..." output:mrd:artifacts:config=package/mrds - -# Add MRD generation tool -MRD_GEN = $(shell pwd)/bin/mrd-gen -.PHONY: mrd-gen -mrd-gen: ## Download mrd-gen locally if necessary. - $(call go-get-tool,$(MRD_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.13.0) - -# Update package generation to include MRDs -.PHONY: build-package -build-package: generate - mkdir -p package/ - cp package/crds/*.yaml package/ - cp package/mrds/*.yaml package/ - echo "# Package metadata with safe-start capability" > package/provider.yaml - echo "apiVersion: meta.pkg.crossplane.io/v1" >> package/provider.yaml - echo "kind: Provider" >> package/provider.yaml - echo "spec:" >> package/provider.yaml - echo " capabilities:" >> package/provider.yaml - echo " - safe-start" >> package/provider.yaml -``` -{{< /tab >}} - -{{< tab "GitHub Actions" >}} -```yaml -name: Build and Test safe-start Provider - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - test-safestart: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Go - uses: actions/setup-go@v4 - with: - go-version: '1.21' - - - name: Run Tests - run: make test - - - name: Generate MRDs - run: make generate - - - name: Verify MRD Generation - run: | - if [ ! -d "package/mrds" ]; then - echo "MRD generation failed" - exit 1 - fi - echo "Generated MRDs:" - ls -la package/mrds/ - - - name: Test safe-start Integration - run: | - # Start local cluster - make kind-up - make install-crossplane-v2 - - # Install provider with safe-start - make install-provider - - # Verify MRDs created but inactive - kubectl get mrds - kubectl get mrds -o jsonpath='{.items[*].spec.state}' | grep -q "Inactive" - - # Test activation policy - kubectl apply -f examples/activation-policy.yaml - - # Verify resources activate - sleep 30 - kubectl get mrds -o jsonpath='{.items[*].spec.state}' | grep -q "Active" - - # Test resource creation - kubectl apply -f examples/example-resource.yaml - kubectl wait --for=condition=Ready --timeout=300s -f examples/example-resource.yaml -``` -{{< /tab >}} -{{< /tabs >}} - -### Step 6: Add connection details documentation +### Step 3: Add connection details documentation Document connection details in your MRDs to help users understand resource capabilities: @@ -414,47 +148,6 @@ spec: ## Testing safe-start implementation -### Unit testing - -Test your MRD generation and controller logic: - -```go -func TestMRDGeneration(t *testing.T) { - // Test MRD generation with correct connection details - mrd := generateMRDForResource("Database") - - assert.Equal(t, "databases.rds.aws.example.io", mrd.Name) - assert.NotEmpty(t, mrd.Spec.ConnectionDetails) - - // Verify specific connection details - endpointDetail := findConnectionDetail(mrd, "endpoint") - assert.NotNil(t, endpointDetail) - assert.Equal(t, "string", endpointDetail.Type) - assert.Contains(t, endpointDetail.Description, "endpoint") -} - -func TestMRDActivation(t *testing.T) { - // Test MRD activation creates CRD - ctx := context.Background() - mrd := &v1alpha1.ManagedResourceDefinition{ - Spec: v1alpha1.ManagedResourceDefinitionSpec{ - State: &[]v1alpha1.ResourceState{v1alpha1.ResourceStateActive}[0], - }, - } - - reconciler := &MRDReconciler{Client: fakeClient} - result, err := reconciler.Reconcile(ctx, reconcile.Request{}) - - assert.NoError(t, err) - assert.False(t, result.Requeue) - - // Verify CRD creation - crd := &apiextv1.CustomResourceDefinition{} - err = fakeClient.Get(ctx, types.NamespacedName{Name: "databases.rds.aws.example.io"}, crd) - assert.NoError(t, err) -} -``` - ### Integration testing Test safe-start behavior in a real cluster: @@ -480,8 +173,6 @@ metadata: name: provider-example spec: package: registry.example.com/provider-example:latest - capabilities: - - safe-start EOF # Wait for provider installation @@ -568,16 +259,6 @@ spec: - "*.aws.example.io" # Activate all resources (legacy behavior) ``` -### Version compatibility matrix - -Document version compatibility: - -| Provider Version | Crossplane Version | safe-start Support | Notes | -|------------------|-------------------|------------------|-------| -| v1.x | v1.x - v2.x | No | Legacy CRD-only mode | -| v2.0 | v2.0+ | Yes | Full safe-start support | -| v2.1 | v2.0+ | Yes | Enhanced MRD features | - ## Documentation requirements Update your provider documentation to include: