Skip to content

Commit 3cd4f14

Browse files
xekDeydra71cursoragent
authored andcommitted
Application Credential support
Co-authored-by: Veronika Fisarova <vfisarov@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 2c8b0e7 commit 3cd4f14

26 files changed

Lines changed: 1133 additions & 7 deletions

api/bases/watcher.openstack.org_watchers.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,15 @@ spec:
460460
type: string
461461
type: object
462462
type: object
463+
auth:
464+
description: Auth - Parameters related to authentication (shared by
465+
all Watcher components)
466+
properties:
467+
applicationCredentialSecret:
468+
description: ApplicationCredentialSecret - Secret containing Application
469+
Credential ID and Secret
470+
type: string
471+
type: object
463472
customServiceConfig:
464473
description: |-
465474
CustomServiceConfig - customize the service config using this parameter to change service defaults,

api/v1beta1/common_types.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ type WatcherSpecCore struct {
129129
// APITimeout for Route and Apache
130130
APITimeout *int `json:"apiTimeout"`
131131

132+
// +kubebuilder:validation:Optional
133+
// +operator-sdk:csv:customresourcedefinitions:type=spec
134+
// Auth - Parameters related to authentication (shared by all Watcher components)
135+
Auth AuthSpec `json:"auth,omitempty"`
136+
132137
// +kubebuilder:validation:Optional
133138
// NotificationsBusInstance is the name of the RabbitMqCluster CR to select
134139
// the Message Bus Service instance used by the Watcher service to publish and consume notifications
@@ -139,6 +144,14 @@ type WatcherSpecCore struct {
139144
NotificationsBusInstance *string `json:"notificationsBusInstance,omitempty"`
140145
}
141146

147+
// AuthSpec defines authentication parameters
148+
type AuthSpec struct {
149+
// +kubebuilder:validation:Optional
150+
// +operator-sdk:csv:customresourcedefinitions:type=spec
151+
// ApplicationCredentialSecret - Secret containing Application Credential ID and Secret
152+
ApplicationCredentialSecret string `json:"applicationCredentialSecret,omitempty"`
153+
}
154+
142155
// PasswordSelector to identify the DB and AdminUser password from the Secret
143156
type PasswordSelector struct {
144157
// +kubebuilder:validation:Optional

api/v1beta1/conditions.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ const (
3838
WatcherAPIReadyErrorMessage = "WatcherAPI error occured %s"
3939
// WatcherPrometheusSecretErrorMessage -
4040
WatcherPrometheusSecretErrorMessage = "Error with prometheus config secret"
41+
// WatcherApplicationCredentialSecretErrorMessage -
42+
WatcherApplicationCredentialSecretErrorMessage = "Error with application credential secret"
4143
// WatcherApplierReadyInitMessage -
4244
WatcherApplierReadyInitMessage = "WatcherApplier creation not started"
4345
// WatcherApplierReadyRunningMessage -

api/v1beta1/zz_generated.deepcopy.go

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/watcher.openstack.org_watchers.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,15 @@ spec:
460460
type: string
461461
type: object
462462
type: object
463+
auth:
464+
description: Auth - Parameters related to authentication (shared by
465+
all Watcher components)
466+
properties:
467+
applicationCredentialSecret:
468+
description: ApplicationCredentialSecret - Secret containing Application
469+
Credential ID and Secret
470+
type: string
471+
type: object
463472
customServiceConfig:
464473
description: |-
465474
CustomServiceConfig - customize the service config using this parameter to change service defaults,

config/manifests/bases/watcher-operator.clusterserviceversion.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ spec:
5757
- description: TLS - Parameters related to the TLS
5858
displayName: TLS
5959
path: apiServiceTemplate.tls
60+
- description: Auth - Parameters related to authentication (shared by all Watcher
61+
components)
62+
displayName: Auth
63+
path: auth
64+
- description: ApplicationCredentialSecret - Secret containing Application Credential
65+
ID and Secret
66+
displayName: Application Credential Secret
67+
path: auth.applicationCredentialSecret
6068
version: v1beta1
6169
description: The Watcher Operator project
6270
displayName: Watcher Operator

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/onsi/gomega v1.39.0
1010
github.com/openshift/api v3.9.0+incompatible
1111
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260115124008-0121df869109
12-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260116230254-f54dd51650ac
12+
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260120112029-cd452f0497ba
1313
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251230215914-6ba873b49a35
1414
github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20251230215914-6ba873b49a35
1515
github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260105160121-f7a8ef85ce8d

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e h1:E1OdwSpqWuDPCedyU
120120
github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e/go.mod h1:Shkl4HanLwDiiBzakv+con/aMGnVE2MAGvoKp5oyYUo=
121121
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260115124008-0121df869109 h1:S+A67nntHZrL1lIL3qr91CpJj+A67M/G4t1cTKzeGdo=
122122
github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260115124008-0121df869109/go.mod h1:ZXwFlspJCdZEUjMbmaf61t5AMB4u2vMyAMMoe/vJroE=
123-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260116230254-f54dd51650ac h1:DZ/Cw3l4fQXTu2O78HAPIEhSYYZ7cR+QZv893Z+gvNU=
124-
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260116230254-f54dd51650ac/go.mod h1:xqvebn9DqLavxp2z8Rz/7i1S6M9MJhxmZVHC+S1uHX0=
123+
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260120112029-cd452f0497ba h1:4VaDkZFawGCkzwvfijnFLz0Gduxh17buj9fIwk0WULo=
124+
github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260120112029-cd452f0497ba/go.mod h1:xqvebn9DqLavxp2z8Rz/7i1S6M9MJhxmZVHC+S1uHX0=
125125
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251230215914-6ba873b49a35 h1:pF3mJ3nwq6r4qwom+rEWZNquZpcQW/iftHlJ1KPIDsk=
126126
github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20251230215914-6ba873b49a35/go.mod h1:kycZyoe7OZdW1HUghr2nI3N7wSJtNahXf6b/ypD14f4=
127127
github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20251230215914-6ba873b49a35 h1:IdcI8DFvW8rXtchONSzbDmhhRp1YyO2YaBJDBXr44Gk=

internal/controller/watcher_common.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const (
3636
tlsAPIPublicField = ".spec.tls.api.public.secretName"
3737
topologyField = ".spec.topologyRef.Name"
3838
memcachedInstanceField = ".spec.memcachedInstance"
39+
authAppCredSecretField = ".spec.auth.applicationCredentialSecret" //nolint:gosec // G101: Not actual credentials, just field path
3940
// service label for cinder endpoint
4041
endpointCinder = "cinder"
4142
)
@@ -60,6 +61,7 @@ var (
6061
watcherWatchFields = []string{
6162
passwordSecretField,
6263
prometheusSecretField,
64+
authAppCredSecretField,
6365
}
6466
decisionEngineWatchFields = []string{
6567
passwordSecretField,
@@ -133,6 +135,12 @@ var (
133135

134136
// ErrTransportURLFieldMissing indicates that the TransportURL secret does not have the 'transport_url' field
135137
ErrTransportURLFieldMissing = errors.New("the TransportURL secret does not have 'transport_url' field")
138+
139+
// ErrACSecretNotFound indicates that the ApplicationCredential secret was not found
140+
ErrACSecretNotFound = errors.New("ApplicationCredential secret not found")
141+
142+
// ErrACSecretMissingKeys indicates that the ApplicationCredential secret is missing required keys
143+
ErrACSecretMissingKeys = errors.New("ApplicationCredential secret missing required keys")
136144
)
137145

138146
// GetLogger returns a logger object with a prefix of "controller.name" and additional controller context fields

internal/controller/watcher_controller.go

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,47 @@ func (r *WatcherReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re
316316
return ctrl.Result{}, ErrRetrievingTransportURLSecretData
317317
}
318318

319+
// Try to get Application Credential from the secret specified in the CR
320+
var acData *keystonev1.ApplicationCredentialData
321+
if instance.Spec.Auth.ApplicationCredentialSecret != "" {
322+
acSecretObj, _, err := secret.GetSecret(ctx, helper, instance.Spec.Auth.ApplicationCredentialSecret, instance.Namespace)
323+
if err != nil {
324+
if k8s_errors.IsNotFound(err) {
325+
Log.Info("ApplicationCredential secret not found, waiting", "secret", instance.Spec.Auth.ApplicationCredentialSecret)
326+
instance.Status.Conditions.Set(condition.FalseCondition(
327+
condition.InputReadyCondition,
328+
condition.RequestedReason,
329+
condition.SeverityInfo,
330+
watcherv1beta1.WatcherApplicationCredentialSecretErrorMessage))
331+
return ctrl.Result{}, fmt.Errorf("%w: %s", ErrACSecretNotFound, instance.Spec.Auth.ApplicationCredentialSecret)
332+
}
333+
Log.Error(err, "Failed to get ApplicationCredential secret", "secret", instance.Spec.Auth.ApplicationCredentialSecret)
334+
instance.Status.Conditions.Set(condition.FalseCondition(
335+
condition.InputReadyCondition,
336+
condition.ErrorReason,
337+
condition.SeverityWarning,
338+
watcherv1beta1.WatcherApplicationCredentialSecretErrorMessage))
339+
return ctrl.Result{}, err
340+
}
341+
acID, okID := acSecretObj.Data[keystonev1.ACIDSecretKey]
342+
acSecretData, okSecret := acSecretObj.Data[keystonev1.ACSecretSecretKey]
343+
if okID && len(acID) > 0 && okSecret && len(acSecretData) > 0 {
344+
acData = &keystonev1.ApplicationCredentialData{
345+
ID: string(acID),
346+
Secret: string(acSecretData),
347+
}
348+
Log.Info("Using ApplicationCredentials auth", "secret", instance.Spec.Auth.ApplicationCredentialSecret)
349+
} else {
350+
Log.Error(nil, "ApplicationCredential secret missing required keys", "secret", instance.Spec.Auth.ApplicationCredentialSecret)
351+
instance.Status.Conditions.Set(condition.FalseCondition(
352+
condition.InputReadyCondition,
353+
condition.ErrorReason,
354+
condition.SeverityWarning,
355+
watcherv1beta1.WatcherApplicationCredentialSecretErrorMessage))
356+
return ctrl.Result{}, fmt.Errorf("%w: %s", ErrACSecretMissingKeys, instance.Spec.Auth.ApplicationCredentialSecret)
357+
}
358+
}
359+
319360
// Prometheus config secret
320361

321362
hashPrometheus, _, prometheusSecret, err := ensureSecret(
@@ -354,7 +395,7 @@ func (r *WatcherReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re
354395

355396
// End of Prometheus config secret
356397

357-
subLevelSecretName, err := r.createSubLevelSecret(ctx, helper, instance, transporturlSecret, notificationURLSecret, inputSecret, db)
398+
subLevelSecretName, err := r.createSubLevelSecret(ctx, helper, instance, transporturlSecret, notificationURLSecret, inputSecret, db, acData)
358399
if err != nil {
359400
return ctrl.Result{}, nil
360401
}
@@ -375,7 +416,7 @@ func (r *WatcherReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re
375416
// Generate config for dbsync
376417
configVars := make(map[string]env.Setter)
377418

378-
err = r.generateServiceConfigDBJobs(ctx, instance, db, &transporturlSecret, helper, &configVars)
419+
err = r.generateServiceConfigDBJobs(ctx, instance, db, &transporturlSecret, helper, &configVars, acData)
379420
if err != nil {
380421
instance.Status.Conditions.Set(condition.FalseCondition(
381422
condition.ServiceConfigReadyCondition,
@@ -775,6 +816,7 @@ func (r *WatcherReconciler) generateServiceConfigDBJobs(
775816
transporturlSecret *corev1.Secret,
776817
helper *helper.Helper,
777818
envVars *map[string]env.Setter,
819+
acData *keystonev1.ApplicationCredentialData,
778820
) error {
779821
Log := r.GetLogger(ctx)
780822
Log.Info("generateServiceConfigs - reconciling config for Watcher CR")
@@ -804,6 +846,12 @@ func (r *WatcherReconciler) generateServiceConfigDBJobs(
804846
"APIPublicPort": fmt.Sprintf("%d", watcher.WatcherPublicPort),
805847
}
806848

849+
// Add Application Credential data if provided
850+
if acData != nil {
851+
templateParameters["ACID"] = acData.ID
852+
templateParameters["ACSecret"] = acData.Secret
853+
}
854+
807855
return GenerateConfigsGeneric(ctx, helper, instance, envVars, templateParameters, customData, labels, true)
808856
}
809857

@@ -867,6 +915,7 @@ func (r *WatcherReconciler) createSubLevelSecret(
867915
notificationURLSecret *corev1.Secret,
868916
inputSecret corev1.Secret,
869917
db *mariadbv1.Database,
918+
acData *keystonev1.ApplicationCredentialData,
870919
) (string, error) {
871920
Log := r.GetLogger(ctx)
872921
Log.Info(fmt.Sprintf("Creating SubCr Level Secret for '%s'", instance.Name))
@@ -884,6 +933,13 @@ func (r *WatcherReconciler) createSubLevelSecret(
884933
watcher.GlobalCustomConfigFileName: instance.Spec.CustomServiceConfig,
885934
NotificationURLSelector: string(notificationURLSecret.Data[TransportURLSelector]),
886935
}
936+
937+
// Add Application Credential data if provided
938+
if acData != nil {
939+
data["ACID"] = acData.ID
940+
data["ACSecret"] = acData.Secret
941+
}
942+
887943
secretName := instance.Name
888944

889945
labels := labels.GetLabels(instance, labels.GetGroupLabel(watcher.ServiceName), map[string]string{})
@@ -1266,6 +1322,18 @@ func (r *WatcherReconciler) SetupWithManager(mgr ctrl.Manager) error {
12661322
return err
12671323
}
12681324

1325+
// index authAppCredSecretField
1326+
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &watcherv1beta1.Watcher{}, authAppCredSecretField, func(rawObj client.Object) []string {
1327+
// Extract the secret name from the spec, if one is provided
1328+
cr := rawObj.(*watcherv1beta1.Watcher)
1329+
if cr.Spec.Auth.ApplicationCredentialSecret == "" {
1330+
return nil
1331+
}
1332+
return []string{cr.Spec.Auth.ApplicationCredentialSecret}
1333+
}); err != nil {
1334+
return err
1335+
}
1336+
12691337
return ctrl.NewControllerManagedBy(mgr).
12701338
For(&watcherv1beta1.Watcher{}).
12711339
Owns(&watcherv1beta1.WatcherAPI{}).

0 commit comments

Comments
 (0)