| name | new-controller |
| description | Create a new ORC controller for an OpenStack resource. Use when adding support for a new OpenStack resource type (e.g., LoadBalancer, FloatingIP). |
| disable-model-invocation | true |
Create New Controller
Create a new ORC controller for an OpenStack resource.
IMPORTANT: Complete ALL steps in order. Do not stop after implementing TODOs - you must also write E2E tests and run them. Ask the user for E2E_OSCLOUDS path if needed to run tests.
Prerequisites
Ask the user one by one about:
- What OpenStack resource to create (e.g., "VolumeBackup")
- Which service it belongs to (compute, network, blockstorage, identity, image)
- Does it need polling for availability or deletion? (i.e., does the resource have intermediate provisioning states like PENDING_CREATE, BUILD, etc.)
- Any dependencies on other ORC resources (required, optional, or import-only)?
- Is there a similar existing controller to use as reference? (e.g., Listener for LoadBalancer)
- Do they have
E2E_OSCLOUDS path to a clouds.yaml for running E2E tests locally? (If not, local E2E testing will be skipped)
- Any additional requirements or constraints? (e.g., cascade delete support, special validation rules, immutability requirements)
Step 1: Research the OpenStack Resource
Before scaffolding, research the resource to understand the exact field names:
- Read the gophercloud struct to get exact field names:
go doc <gophercloud-module>.<Type>
go doc <gophercloud-module>.CreateOpts
-
Look at a similar existing controller for patterns (if user provided one):
- Check their
*_types.go for API structure
- Check their
actuator.go for implementation patterns
-
Note the exact field names from gophercloud - use these when defining API types:
- If gophercloud has
VipSubnetID, name the ORC field VipSubnetRef (not just SubnetRef)
- If gophercloud has
FlavorID, name the ORC field FlavorRef
- Preserve prefixes like
Vip, Source, Destination etc.
Step 2: Run Scaffolding Tool
IMPORTANT: Build a single scaffolding command using the user's answers and the flags reference below. Run it exactly ONCE (user will be prompted to approve).
Use the field names discovered in Step 1 to inform your implementation later.
Scaffolding Flags Reference
Required flags:
| Flag | Description | Example |
|---|
-kind | The Kind of the new resource (PascalCase) | VolumeBackup, FloatingIP |
-gophercloud-client | The gophercloud function to instantiate a client | NewBlockStorageV3, NewNetworkV2 |
-gophercloud-module | Full gophercloud module import path | github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/backups |
Optional flags:
| Flag | Description | Default |
|---|
-gophercloud-type | The gophercloud struct type name | Same as -kind |
-openstack-json-object | Object name in OpenStack JSON responses | snake_case of kind (e.g., volume_backup) |
-available-polling-period | Polling period in seconds while waiting for resource to become available | 0 (available immediately) |
-deleting-polling-period | Polling period in seconds while waiting for resource to be deleted | 0 (deleted immediately) |
-required-create-dependency | Required dependency for creation (can repeat flag for multiple) | none |
-optional-create-dependency | Optional dependency for creation (can repeat flag for multiple) | none |
-import-dependency | Dependency for import filter (can repeat flag for multiple) | none |
-interactive | Run in interactive mode | true (set to false for scripted use) |
Common Gophercloud Clients
| Service | Client Function | Module Path Prefix |
|---|
| Compute | NewComputeV2 | github.com/gophercloud/gophercloud/v2/openstack/compute/v2/... |
| Network | NewNetworkV2 | github.com/gophercloud/gophercloud/v2/openstack/networking/v2/... |
| Block Storage | NewBlockStorageV3 | github.com/gophercloud/gophercloud/v2/openstack/blockstorage/v3/... |
| Identity | NewIdentityV3 | github.com/gophercloud/gophercloud/v2/openstack/identity/v3/... |
| Image | NewImageV2 | github.com/gophercloud/gophercloud/v2/openstack/image/v2/... |
Example Command (for reference only - build your own based on user input)
go run ./cmd/scaffold-controller -interactive=false \
-kind=Port \
-gophercloud-client=NewNetworkV2 \
-gophercloud-module=github.com/gophercloud/gophercloud/v2/openstack/networking/v2/ports \
-required-create-dependency=Network \
-optional-create-dependency=Subnet \
-optional-create-dependency=SecurityGroup \
-import-dependency=Network
After scaffolding completes, run code generation:
make generate
Commit the scaffolding with the command used:
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
)"
Step 3: Register with Resource Generator
Add the new resource to cmd/resource-generator/main.go in the resources slice:
var resources []templateFields = []templateFields{
{
Name: "VolumeBackup",
},
}
Then regenerate to create the zz_generated.*.go files:
make generate
Step 4: Add OpenStack Client to Scope
Update these files in internal/scope/:
scope.go
Add interface method:
NewYourResourceClient() (osclients.YourResourceClient, error)
provider.go
Implement the constructor:
func (s *providerScope) NewYourResourceClient() (osclients.YourResourceClient, error) {
return osclients.NewYourResourceClient(s.provider)
}
mock.go
Add mock client field and implementation for testing.
Step 5: Register Controller
Add to cmd/manager/main.go:
import (
yourresourcecontroller "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/yourresource"
)
controllers := []interfaces.Controller{
yourresourcecontroller.New(scopeFactory),
}
Step 6: Implement TODOs
Reference Documentation: For detailed patterns and rationale, see:
website/docs/development/controller-implementation.md - Progressing condition, ReconcileStatus, error handling, dependencies
website/docs/development/api-design.md - Filter, ResourceSpec, ResourceStatus conventions
website/docs/development/coding-standards.md - Code organization, naming, logging
Find all scaffolding TODOs:
grep -r "TODO(scaffolding)" api/v1alpha1/ internal/controllers/<kind>/
API Types (api/v1alpha1/_types.go)
Use the exact field names from gophercloud discovered in Step 1.
Define:
<Kind>ResourceSpec - Creation parameters with validation markers
<Kind>Filter - Import filter with MinProperties:=1
<Kind>ResourceStatus - Observed state fields
Actuator (internal/controllers//actuator.go)
Implement:
CreateResource() - Build CreateOpts, call OpenStack API
DeleteResource() - Call delete API
ListOSResourcesForImport() - Apply filter to list results
ListOSResourcesForAdoption() - Match by spec fields
GetResourceReconcilers() - (if resource supports updates)
ReconcileResourceActuator is optional: The generic reconciler detects it via type assertion at runtime — there is no factory method to implement. To opt in, add the reconcileResourceActuator type alias and interface assertion in actuator.go, then implement GetResourceReconcilers on the actuator struct:
type (
reconcileResourceActuator = interfaces.ReconcileResourceActuator[orcObjectPT, osResourceT]
resourceReconciler = interfaces.ResourceReconciler[orcObjectPT, osResourceT]
)
var _ reconcileResourceActuator = myActuator{}
func (actuator myActuator) GetResourceReconcilers(ctx context.Context, orcObject orcObjectPT, osResource *osResourceT, controller interfaces.ResourceController) ([]resourceReconciler, progress.ReconcileStatus) {
return []resourceReconciler{
actuator.updateResource,
}, nil
}
If the resource is fully immutable (no mutable fields, no tags, no sub-resources), skip this entirely — the generic reconciler will not call it.
Implementation Patterns
Follow the patterns in patterns.md when implementing the actuator and API types.
Status Writer (internal/controllers//status.go)
Implement:
ResourceAvailableStatus() - When is resource available?
ApplyResourceStatus() - Map OpenStack fields to status
Step 7: Write and Run Tests
This step is required - do not skip it.
Complete the scaffolded API validation test in test/apivalidations/<kind>_test.go by adding tests for any resource-specific validations (enums, numeric ranges, tag uniqueness, format validation, cross-field rules). Look for TODO(scaffolding) markers in the generated file.
Complete the E2E test stubs in internal/controllers/<kind>/tests/ and run tests following testing
Checklist