Skip to content
Merged
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
14 changes: 14 additions & 0 deletions api/v1alpha1/project_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ type ProjectResourceSpec struct {
// +optional
Description *string `json:"description,omitempty"`

// domainRef is a reference to the ORC Domain which this resource is associated with.
// +optional
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="domainRef is immutable"
DomainRef *KubernetesNameRef `json:"domainRef,omitempty"`

// enabled defines whether a project is enabled or not. Default is true.
// +optional
Enabled *bool `json:"enabled,omitempty"`
Expand All @@ -83,6 +88,10 @@ type ProjectFilter struct {
// +optional
Name *KeystoneName `json:"name,omitempty"`

// domainRef is a reference to the ORC Domain which this resource is associated with.
// +optional
DomainRef *KubernetesNameRef `json:"domainRef,omitempty"`

FilterByKeystoneTags `json:",inline"`
}

Expand All @@ -98,6 +107,11 @@ type ProjectResourceStatus struct {
// +optional
Description string `json:"description,omitempty"`

// domainID is the ID of the Domain to which the resource is associated.
// +kubebuilder:validation:MaxLength=1024
// +optional
DomainID string `json:"domainID,omitempty"`

// enabled represents whether a project is enabled or not.
// +optional
Enabled *bool `json:"enabled,omitempty"`
Expand Down
10 changes: 10 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions cmd/models-schema/zz_generated.openapi.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions config/crd/bases/openstack.k-orc.cloud_projects.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@ spec:
error state and will not continue to retry.
minProperties: 1
properties:
domainRef:
description: domainRef is a reference to the ORC Domain which
this resource is associated with.
maxLength: 253
minLength: 1
type: string
name:
description: name of the existing resource
maxLength: 64
Expand Down Expand Up @@ -195,6 +201,15 @@ spec:
maxLength: 65535
minLength: 1
type: string
domainRef:
description: domainRef is a reference to the ORC Domain which
this resource is associated with.
maxLength: 253
minLength: 1
type: string
x-kubernetes-validations:
- message: domainRef is immutable
rule: self == oldSelf
enabled:
description: enabled defines whether a project is enabled or not.
Default is true.
Expand Down Expand Up @@ -326,6 +341,11 @@ spec:
resource.
maxLength: 65535
type: string
domainID:
description: domainID is the ID of the Domain to which the resource
is associated.
maxLength: 1024
type: string
enabled:
description: enabled represents whether a project is enabled or
not.
Expand Down
41 changes: 39 additions & 2 deletions internal/controllers/project/actuator.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
generic "github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/interfaces"
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress"
"github.com/k-orc/openstack-resource-controller/v2/internal/logging"
"github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency"
orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors"
"github.com/k-orc/openstack-resource-controller/v2/internal/util/tags"
)
Expand All @@ -53,7 +55,8 @@ type projectClient interface {
}

type projectActuator struct {
osClient projectClient
osClient projectClient
k8sClient client.Client
}

var _ createResourceActuator = projectActuator{}
Expand Down Expand Up @@ -85,8 +88,23 @@ func (actuator projectActuator) ListOSResourcesForAdoption(ctx context.Context,
}

func (actuator projectActuator) ListOSResourcesForImport(ctx context.Context, orcObject orcObjectPT, filter filterT) (iter.Seq2[*osResourceT, error], progress.ReconcileStatus) {
var reconcileStatus progress.ReconcileStatus

domain, rs := dependency.FetchDependency(
ctx, actuator.k8sClient, orcObject.Namespace, filter.DomainRef, "Domain",
func(dep *orcv1alpha1.Domain) bool {
return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
},
)
reconcileStatus = reconcileStatus.WithReconcileStatus(rs)

if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
return nil, reconcileStatus
}

listOpts := projects.ListOpts{
Name: string(ptr.Deref(filter.Name, "")),
DomainID: ptr.Deref(domain.Status.ID, ""),
Tags: tags.Join(filter.Tags),
TagsAny: tags.Join(filter.TagsAny),
NotTags: tags.Join(filter.NotTags),
Expand All @@ -104,6 +122,23 @@ func (actuator projectActuator) CreateResource(ctx context.Context, obj orcObjec
return nil, progress.WrapError(
orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Creation requested, but spec.resource is not set"))
}
var reconcileStatus progress.ReconcileStatus

var domainID string
if resource.DomainRef != nil {
domain, domainDepRS := domainDependency.GetDependency(
ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Domain) bool {
return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
},
)
reconcileStatus = reconcileStatus.WithReconcileStatus(domainDepRS)
if domain != nil {
domainID = ptr.Deref(domain.Status.ID, "")
}
}
if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
return nil, reconcileStatus
}

tags := make([]string, len(resource.Tags))
for i := range resource.Tags {
Expand All @@ -115,6 +150,7 @@ func (actuator projectActuator) CreateResource(ctx context.Context, obj orcObjec
createOpts := projects.CreateOpts{
Name: getResourceName(obj),
Description: ptr.Deref(resource.Description, ""),
DomainID: domainID,
Enabled: resource.Enabled,
Tags: tags,
}
Expand Down Expand Up @@ -251,7 +287,8 @@ func newActuator(ctx context.Context, orcObject *orcv1alpha1.Project, controller
}

return projectActuator{
osClient: osClient,
osClient: osClient,
k8sClient: controller.GetK8sClient(),
}, nil
}

Expand Down
46 changes: 46 additions & 0 deletions internal/controllers/project/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"errors"

ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/controller"

orcv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
Expand All @@ -29,6 +30,8 @@ import (
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/reconciler"
"github.com/k-orc/openstack-resource-controller/v2/internal/scope"
"github.com/k-orc/openstack-resource-controller/v2/internal/util/credentials"
"github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency"
"github.com/k-orc/openstack-resource-controller/v2/pkg/predicates"
)

const controllerName = "project"
Expand All @@ -48,15 +51,58 @@ func (projectReconcilerConstructor) GetName() string {
return controllerName
}

var domainDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ProjectList, *orcv1alpha1.Domain](
"spec.resource.domainRef",
func(project *orcv1alpha1.Project) []string {
resource := project.Spec.Resource
if resource == nil || resource.DomainRef == nil {
return nil
}
return []string{string(*resource.DomainRef)}
},
finalizer, externalObjectFieldOwner,
)

var domainImportDependency = dependency.NewDependency[*orcv1alpha1.ProjectList, *orcv1alpha1.Domain](
"spec.import.filter.domainRef",
func(project *orcv1alpha1.Project) []string {
resource := project.Spec.Import
if resource == nil || resource.Filter == nil || resource.Filter.DomainRef == nil {
return nil
}
return []string{string(*resource.Filter.DomainRef)}
},
)

// SetupWithManager sets up the controller with the Manager.
func (c projectReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
log := ctrl.LoggerFrom(ctx)
k8sClient := mgr.GetClient()

domainWatchEventHandler, err := domainDependency.WatchEventHandler(log, k8sClient)
if err != nil {
return err
}

domainImportWatchEventHandler, err := domainImportDependency.WatchEventHandler(log, k8sClient)
if err != nil {
return err
}

builder := ctrl.NewControllerManagedBy(mgr).
WithOptions(options).
Watches(&orcv1alpha1.Domain{}, domainWatchEventHandler,
builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})),
).
// A second watch is necessary because we need a different handler that omits deletion guards
Watches(&orcv1alpha1.Domain{}, domainImportWatchEventHandler,
builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Domain{})),
).
For(&orcv1alpha1.Project{})

if err := errors.Join(
domainDependency.AddToManager(ctx, mgr),
domainImportDependency.AddToManager(ctx, mgr),
credentialsDependency.AddToManager(ctx, mgr),
credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency),
); err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/controllers/project/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func (projectStatusWriter) ResourceAvailableStatus(orcObject *orcv1alpha1.Projec
func (projectStatusWriter) ApplyResourceStatus(_ logr.Logger, osResource *projects.Project, statusApply *statusApplyT) {
resourceStatus := orcapplyconfigv1alpha1.ProjectResourceStatus().
WithName(osResource.Name).
WithDomainID(osResource.DomainID).
WithEnabled(osResource.Enabled).
WithTags(osResource.Tags...)
if osResource.Description != "" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,25 @@ status:
tags:
- tag1
- tag2
conditions:
- type: Available
status: "True"
reason: Success
- type: Progressing
status: "False"
reason: Success
---
apiVersion: kuttl.dev/v1beta1
kind: TestAssert
resourceRefs:
- apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Project
name: project-create-full
ref: project
- apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Domain
name: project-create-full
ref: domain
assertAll:
- celExpr: "project.status.id != ''"
- celExpr: "project.status.resource.domainID == domain.status.id"
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Domain
metadata:
name: project-create-full
spec:
cloudCredentialsRef:
cloudName: openstack-admin
secretName: openstack-clouds
managementPolicy: managed
resource: {}
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Project
metadata:
name: project-create-full
Expand All @@ -11,6 +22,7 @@ spec:
resource:
name: project-create-full-override
description: Project from "create full" test
domainRef: project-create-full
enabled: false
tags:
- tag1
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
apiVersion: kuttl.dev/v1beta1
kind: TestAssert
resourceRefs:
- apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Domain
name: project-create-full
ref: domain
assertAll:
- celExpr: "domain.status.resource.enabled == false"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Domain
metadata:
name: project-create-full
spec:
resource:
enabled: false
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ Create a project using all available fields, and verify that the observed state

Also validate that the OpenStack resource uses the name from the spec when it is specified.

## Step 01

By default the enabled field is set to true, the enabled field needs to be disabled.

Disabling the Domain is required before deletion in Openstack.

## Reference

https://k-orc.cloud/development/writing-tests/#create-full
Loading
Loading