Skip to content

Scaffolding a new controller

The first step in writing a new controller is to generate the scaffolding. ORC provides an interactive scaffolding tool that generates most of the boilerplate code required for a new controller.

Running the scaffolding tool

To scaffold a new controller, run:

go run ./cmd/scaffold-controller

By default, the tool runs interactively, prompting you for each required value. For automation or reproducibility, use non-interactive mode with flags:

go run ./cmd/scaffold-controller -interactive=false \
    -kind=VolumeBackup \
    -gophercloud-client=NewBlockStorageV3 \
    -gophercloud-module=github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/backups \
    ...

After the scaffolding tool returned successfully, commit the raw scaffolding output before doing anything else:

git add .
git commit -m "$(cat <<'EOF'
Scaffolding for the VolumeBackup controller

$ go run ./cmd/scaffold-controller -interactive=false \
    -kind=VolumeBackup \
    -gophercloud-client=NewBlockStorageV3 \
    -gophercloud-module=github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/backups \
    ...
EOF
)"

The commit message must contain the exact command that was run (with all flags) so the scaffolding can be reproduced. Do not include any manual edits in this commit.

Next, perform the post-scaffolding integration steps (resource generator registration, make generate, scope wiring, manager registration) and commit them separately:

git add .
git commit -m "Generated code for the VolumeBackup controller"

This two-commit structure lets reviewers skip both generated commits and focus on your hand-written implementation. It also makes it easy to regenerate the scaffolding if tooling or conventions change. See Contributing for the full commit and PR guidelines.

Generated files

The scaffolding tool generates the following files:

API types

  • api/v1alpha1/<kind>_types.go - API type definitions with TODO markers

Controller implementation

  • internal/controllers/<kind>/actuator.go - Actuator implementation stubs
  • internal/controllers/<kind>/actuator_test.go - Actuator unit tests
  • internal/controllers/<kind>/controller.go - Controller setup and registration
  • internal/controllers/<kind>/status.go - Status writer implementation

OpenStack client

  • internal/osclients/<kind>.go - OpenStack client wrapper

Tests

  • test/apivalidations/<kind>_test.go - API validation tests (management policy, immutability)
  • internal/controllers/<kind>/tests/<kind>-create-minimal/ - Minimal creation test
  • internal/controllers/<kind>/tests/<kind>-create-full/ - Full creation test
  • internal/controllers/<kind>/tests/<kind>-import/ - Import test
  • internal/controllers/<kind>/tests/<kind>-import-error/ - Import error test
  • internal/controllers/<kind>/tests/<kind>-update/ - Mutability test
  • Additional test directories based on dependencies

Samples

  • config/samples/openstack_v1alpha1_<kind>.yaml - Example resource manifest

Post-scaffolding steps

After the scaffolding tool completes, you need to perform several manual integration steps. See Controller Initialisation for background on how the generated controller code is structured.

Register with the resource generator

Add the new resource to cmd/resource-generator/main.go:

var resources []templateFields = []templateFields{
    // ... existing resources ...
    {
        Name: "YourResource",
    },
}

Then regenerate the supporting code:

make generate

This generates additional files:

  • api/v1alpha1/zz_generated.<kind>-resource.go - Generated API helpers
  • internal/controllers/<kind>/zz_generated.adapter.go - Generated adapter
  • internal/controllers/<kind>/zz_generated.controller.go - Generated controller wrapper

This generator covers functionality common to all controllers. Its purpose is not only to reduce boilerplate, but also to guarantee consistency of behaviour across APIs.

Note

While it is possible to write this code manually, any controller requiring this potentially stretches assumptions made throughout the project. If this is required, consider whether changes can be made to avoid it, or whether further design work is needed in the scaffolding or generic controller code.

Add the OpenStack client to scope

Update three files in internal/scope/:

  • scope.go, add the client interface:

    type Scope interface {
        // ... existing methods ...
        NewYourResourceClient() (osclients.YourResourceClient, error)
    }
    

  • provider.go, implement the client constructor:

    func (s *providerScope) NewYourResourceClient() (osclients.YourResourceClient, error) {
        client, err := clients.NewYourResourceClient(s.providerClient, s.providerClientOpts)
        s.checkNewClientError(err)
        return client, err
    }
    

  • mock.go, add mock support:

    type MockScopeFactory struct {
        // ... existing clients ...
        YourResourceClient *mock.MockYourResourceClient
    }
    
    func NewMockScopeFactory(mockCtrl *gomock.Controller) *MockScopeFactory {
        // ... existing clients ...
        yourResourceClient := mock.NewMockServiceClient(mockCtrl)
    
        return &MockScopeFactory{
            // ... existing clients ...
            YourResourceClient: yourResourceClient,
        }
    }
    
    func (s *mockScope) NewYourResourceClient() (osclients.YourResourceClient, error) {
        return s.yourResourceClient, nil
    }
    

Register the controller

Add the controller to cmd/manager/main.go:

import (
    // ... existing imports ...
    yourresourcecontroller "github.com/k-orc/openstack-resource-controller/internal/controllers/yourresource"
)

// In the controllers slice:
controllers := []interfaces.Controller{
    // ... existing controllers ...
    yourresourcecontroller.New(scopeFactory),
}

Implement the TODOs

Search the generated code for TODO(scaffolding) markers and implement each one:

grep -r "TODO(scaffolding)" api/ internal/controllers/<kind>/ test/apivalidations/

Key areas requiring implementation:

  • API types: Define Filter, ResourceSpec, and ResourceStatus structs
  • Actuator: Implement CreateResource, DeleteResource, and optionally GetResourceReconcilers
  • Status writer: Implement ResourceAvailableStatus and ApplyResourceStatus
  • Tests: Ensure the tests for your controller are complete, including API validation tests for any resource-specific validations

Note

There is a high chance that the API for the resource you're working on differs from the stub the scaffolding tool created. For example, some resources don't have name, id, or description. If that's the case, adapt the generated code to match your resource.

Generate the OLM bundle

After implementation is complete:

make generate-bundle

Update documentation

  • Add the new controller to the README.md
  • Update any relevant user documentation