From 0102f8d7a983244afddd3e339c72e52d81b7616c Mon Sep 17 00:00:00 2001 From: Jan Knipper Date: Tue, 28 Jan 2025 16:44:21 +0100 Subject: [PATCH 1/4] Add application credential support to operator --- pkg/client/openstack/factory.go | 22 +++++----- pkg/cmd/kubernikus/operator.go | 19 ++++++++- pkg/controller/config/config.go | 15 ++++--- pkg/controller/operator.go | 73 ++++++++++++++++++++++----------- 4 files changed, 86 insertions(+), 43 deletions(-) diff --git a/pkg/client/openstack/factory.go b/pkg/client/openstack/factory.go index 6dd538abb1..2a35dd3499 100644 --- a/pkg/client/openstack/factory.go +++ b/pkg/client/openstack/factory.go @@ -35,22 +35,22 @@ type NotAvailableFactory struct { } func (_ NotAvailableFactory) KlusterClientFor(*kubernikus_v1.Kluster) (openstack_kluster.KlusterClient, error) { - return nil, errors.New("Openstack not configured") + return nil, errors.New("Openstack not configured #1") } func (_ NotAvailableFactory) ProjectClientFor(authOptions *tokens.AuthOptions) (openstack_project.ProjectClient, error) { - return nil, errors.New("Openstack not configured") + return nil, errors.New("Openstack not configured #2") } func (_ NotAvailableFactory) ProjectAdminClientFor(string) (openstack_project.ProjectClient, error) { - return nil, errors.New("Openstack not configured") + return nil, errors.New("Openstack not configured #3") } func (_ NotAvailableFactory) ProviderClientFor(authOptions *tokens.AuthOptions, logger log.Logger) (*gophercloud.ProviderClient, error) { - return nil, errors.New("Openstack not configured") + return nil, errors.New("Openstack not configured #4") } func (_ NotAvailableFactory) ProviderClientForKluster(kluster *kubernikus_v1.Kluster, logger log.Logger) (*gophercloud.ProviderClient, error) { - return nil, errors.New("Openstack not configured") + return nil, errors.New("Openstack not configured #5") } func (_ NotAvailableFactory) AdminClient() (admin.AdminClient, error) { - return nil, errors.New("Openstack not configured") + return nil, errors.New("Openstack not configured #6") } type factory struct { @@ -222,27 +222,27 @@ func (f *factory) ProviderClientFor(authOptions *tokens.AuthOptions, logger log. func (f *factory) serviceClientsFor(authOptions *tokens.AuthOptions, logger log.Logger) (*gophercloud.ServiceClient, *gophercloud.ServiceClient, *gophercloud.ServiceClient, *gophercloud.ServiceClient, error) { providerClient, err := f.ProviderClientFor(authOptions, logger) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, fmt.Errorf("Can't create provider client: %v", err) } identity, err := openstack.NewIdentityV3(providerClient, gophercloud.EndpointOpts{}) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, fmt.Errorf("Can't create identity client: %v", err) } compute, err := openstack.NewComputeV2(providerClient, gophercloud.EndpointOpts{}) compute.Microversion = "2.52" // 2.52 supports specifying server tags during create if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, fmt.Errorf("Can't create compute client: %v", err) } network, err := openstack.NewNetworkV2(providerClient, gophercloud.EndpointOpts{}) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, fmt.Errorf("Can't create network client: %v", err) } image, err := openstack.NewImageServiceV2(providerClient, gophercloud.EndpointOpts{}) if err != nil { - return nil, nil, nil, nil, err + return nil, nil, nil, nil, fmt.Errorf("Can't create image client: %v", err) } return identity, compute, network, image, nil diff --git a/pkg/cmd/kubernikus/operator.go b/pkg/cmd/kubernikus/operator.go index 858e354c25..d9557c4f99 100644 --- a/pkg/cmd/kubernikus/operator.go +++ b/pkg/cmd/kubernikus/operator.go @@ -62,6 +62,9 @@ func (o *Options) BindFlags(flags *pflag.FlagSet) { flags.StringVar(&o.Context, "context", "", "Override context") flags.StringVar(&o.ChartDirectory, "chart-directory", o.ChartDirectory, "Directory containing the kubernikus related charts") flags.StringVar(&o.Region, "region", o.Region, "Local region. (used for container image localization)") + flags.StringVar(&o.ApplicationCredentialID, "application-credential-id", o.ApplicationCredentialID, "Openstack application credential id") + flags.StringVar(&o.ApplicationCredentialName, "application-credential-name", o.ApplicationCredentialName, "Openstack application credential name") + flags.StringVar(&o.ApplicationCredentialSecret, "application-credential-secret", o.ApplicationCredentialSecret, "Openstack application credential secret") flags.StringVar(&o.AuthURL, "auth-url", o.AuthURL, "Openstack keystone url") flags.StringVar(&o.AuthUsername, "auth-username", o.AuthUsername, "Service user for kubernikus") flags.StringVar(&o.AuthPassword, "auth-password", "", "Service user password (if unset its read from env var OS_PASSWORD)") @@ -86,8 +89,20 @@ func (o *Options) Validate(c *cobra.Command, args []string) error { o.AuthPassword = os.Getenv("OS_PASSWORD") } - if o.AuthURL != "" && o.AuthPassword == "" { - return errors.New("you must specify the auth-password flag") + if o.ApplicationCredentialID == "" { + o.ApplicationCredentialID = os.Getenv("OS_APPLICATION_CREDENTIAL_ID") + } + + if o.ApplicationCredentialName == "" { + o.ApplicationCredentialName = os.Getenv("OS_APPLICATION_CREDENTIAL_NAME") + } + + if o.ApplicationCredentialSecret == "" { + o.ApplicationCredentialSecret = os.Getenv("OS_APPLICATION_CREDENTIAL_SECRET") + } + + if o.AuthURL != "" && o.AuthPassword == "" && o.ApplicationCredentialSecret == "" { + return errors.New("you must specify the auth-password or application credential flag") } return nil diff --git a/pkg/controller/config/config.go b/pkg/controller/config/config.go index e6d11f09a9..0201369bf5 100644 --- a/pkg/controller/config/config.go +++ b/pkg/controller/config/config.go @@ -20,12 +20,15 @@ type Controller interface { } type OpenstackConfig struct { - AuthURL string - AuthUsername string - AuthPassword string - AuthDomain string - AuthProject string - AuthProjectDomain string + ApplicationCredentialID string + ApplicationCredentialName string + ApplicationCredentialSecret string + AuthURL string + AuthUsername string + AuthPassword string + AuthDomain string + AuthProject string + AuthProjectDomain string } type HelmConfig struct { diff --git a/pkg/controller/operator.go b/pkg/controller/operator.go index 83347e6e45..e024357396 100644 --- a/pkg/controller/operator.go +++ b/pkg/controller/operator.go @@ -41,13 +41,16 @@ type KubernikusOperatorOptions struct { ChartDirectory string - AuthURL string - AuthUsername string - AuthPassword string - AuthDomain string - AuthProject string - AuthProjectDomain string - Region string + ApplicationCredentialID string + ApplicationCredentialName string + ApplicationCredentialSecret string + AuthURL string + AuthUsername string + AuthPassword string + AuthDomain string + AuthProject string + AuthProjectDomain string + Region string KubernikusDomain string KubernikusProjectID string @@ -81,13 +84,6 @@ func NewKubernikusOperator(options *KubernikusOperatorOptions, logger log.Logger o := &KubernikusOperator{ Config: config.Config{ - Openstack: config.OpenstackConfig{ - AuthURL: options.AuthURL, - AuthUsername: options.AuthUsername, - AuthPassword: options.AuthPassword, - AuthProject: options.AuthProjectDomain, - AuthProjectDomain: options.AuthProjectDomain, - }, Helm: config.HelmConfig{ ChartDirectory: options.ChartDirectory, }, @@ -103,6 +99,23 @@ func NewKubernikusOperator(options *KubernikusOperatorOptions, logger log.Logger Logger: logger, } + if options.ApplicationCredentialSecret != "" { + o.Config.Openstack = config.OpenstackConfig{ + ApplicationCredentialID: options.ApplicationCredentialID, + ApplicationCredentialName: options.ApplicationCredentialName, + ApplicationCredentialSecret: options.ApplicationCredentialSecret, + AuthURL: options.AuthURL, + } + } else { + o.Config.Openstack = config.OpenstackConfig{ + AuthURL: options.AuthURL, + AuthUsername: options.AuthUsername, + AuthPassword: options.AuthPassword, + AuthProject: options.AuthProjectDomain, + AuthProjectDomain: options.AuthProjectDomain, + } + } + o.Clients.Kubernetes, err = kube.NewClient(options.KubeConfig, options.Context, logger) if err != nil { @@ -132,16 +145,28 @@ func NewKubernikusOperator(options *KubernikusOperatorOptions, logger log.Logger return nil, fmt.Errorf("Couldn't create CRD: %s", err) } - adminAuthOptions := &tokens.AuthOptions{ - IdentityEndpoint: options.AuthURL, - Username: options.AuthUsername, - Password: options.AuthPassword, - DomainName: options.AuthDomain, - AllowReauth: true, - Scope: tokens.Scope{ - ProjectName: options.AuthProject, - DomainName: options.AuthProjectDomain, - }, + var adminAuthOptions *tokens.AuthOptions + + if options.ApplicationCredentialSecret != "" { + adminAuthOptions = &tokens.AuthOptions{ + IdentityEndpoint: options.AuthURL, + ApplicationCredentialID: options.ApplicationCredentialID, + ApplicationCredentialName: options.ApplicationCredentialName, + ApplicationCredentialSecret: options.ApplicationCredentialSecret, + AllowReauth: true, + } + } else { + adminAuthOptions = &tokens.AuthOptions{ + IdentityEndpoint: options.AuthURL, + Username: options.AuthUsername, + Password: options.AuthPassword, + DomainName: options.AuthDomain, + AllowReauth: true, + Scope: tokens.Scope{ + ProjectName: options.AuthProject, + DomainName: options.AuthProjectDomain, + }, + } } o.Factories.Kubernikus = kubernikus_informers.NewFilteredSharedInformerFactory(o.Clients.Kubernikus, DEFAULT_RECONCILIATION, options.Namespace, nil) From 0b5f7977650f9c9f3d4fb1019d3d6b4466bcaef2 Mon Sep 17 00:00:00 2001 From: Jan Knipper Date: Tue, 28 Jan 2025 17:21:12 +0100 Subject: [PATCH 2/4] Add app credential to operator chart --- charts/kubernikus/templates/operator.yaml | 25 +++++++++++++++++-- .../kubernikus/templates/secret-operator.yaml | 8 ++++-- charts/kubernikus/values.yaml | 3 +++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/charts/kubernikus/templates/operator.yaml b/charts/kubernikus/templates/operator.yaml index 64737bc31a..574c3f5457 100644 --- a/charts/kubernikus/templates/operator.yaml +++ b/charts/kubernikus/templates/operator.yaml @@ -7,6 +7,10 @@ kind: Deployment metadata: name: kubernikus-operator + {{- if .Values.openstack.application_credential_secret }} + annotations: + secret.reloader.stakater.com/reload: "kubernikus-operator" + {{- end }} labels: app: kubernikus type: operator @@ -36,7 +40,7 @@ spec: - kubernikus - operator - --chart-directory=/etc/kubernikus/charts - {{- if .Values.openstack.auth_url }} + {{- if and .Values.openstack.auth_url (emtpy .Values.openstack.application_credential_secret) }} {{- /* This is a bit convoluted but making sure that the url ends with /v3 */}} - --auth-url={{ trimSuffix "/" .Values.openstack.auth_url | trimSuffix "/v3" }}/v3 - --auth-username={{ .Values.openstack.auth_user_id }} @@ -72,13 +76,30 @@ spec: - name: USE_OCTAVIA value: "true" {{- end }} - {{- if .Values.openstack.auth_url }} + {{- if and .Values.openstack.auth_url (empty .Values.openstack.application_credential_secret) }} - name: OS_PASSWORD valueFrom: secretKeyRef: name: kubernikus-operator key: password {{- end }} + {{- if .Values.openstack.application_credential_secret }} + - name: OS_APPLICATION_CREDENTIAL_ID + valueFrom: + secretKeyRef: + name: kubernikus-operator + key: applicationCredentialID + - name: OS_APPLICATION_CREDENTIAL_NAME + valueFrom: + secretKeyRef: + name: kubernikus-operator + key: applicationCredentialName + - name: OS_APPLICATION_CREDENTIAL_SECRET + valueFrom: + secretKeyRef: + name: kubernikus-operator + key: applicationCredentialSecret + {{- end }} {{- if .Values.operator.resources }} resources: {{- toYaml .Values.operator.resources | nindent 12 }} {{- end}} diff --git a/charts/kubernikus/templates/secret-operator.yaml b/charts/kubernikus/templates/secret-operator.yaml index 0c340ac4ee..c9d78b8360 100644 --- a/charts/kubernikus/templates/secret-operator.yaml +++ b/charts/kubernikus/templates/secret-operator.yaml @@ -8,11 +8,15 @@ metadata: data: authURL: {{ required "openstack.auth_url undefined" .Values.openstack.auth_url | trimSuffix "/" | trimSuffix "/v3" | printf "%s/v3" | b64enc }} + {{- if .Values.openstack.application_credential_secret }} + applicationCredentialID: {{ required "openstack.application_credential_id undefined" .Values.openstack.application_credential_id | b64enc }} + applicationCredentialName: {{ required "openstack.application_credential_name undefined" .Values.openstack.application_credential_name | b64enc }} + applicationCredentialSecret: {{ required "openstack.application_credential_secret undefined" .Values.openstack.application_credential_secret | b64enc }} + {{- else }} username: {{ required "openstack.auth_user_id undefined" .Values.openstack.auth_user_id | b64enc }} password: {{ required "openstack.auth_user_password undefined" .Values.openstack.auth_user_password | b64enc }} userDomain: {{ required "openstack.auth_domain undefined" .Values.openstack.auth_domain | b64enc }} project: {{ required "openstack.auth_project undefined" .Values.openstack.auth_project | b64enc }} projectDomain: {{ required "openstack.auth_project_domain undefined" .Values.openstack.auth_project_domain | b64enc }} + {{- end }} {{- end }} - - diff --git a/charts/kubernikus/values.yaml b/charts/kubernikus/values.yaml index eff3c6af1b..b16102e9fd 100644 --- a/charts/kubernikus/values.yaml +++ b/charts/kubernikus/values.yaml @@ -8,6 +8,9 @@ openstack: {} #auth_domain: "Default" #auth_project: "master" #auth_project_domain: "Default" + #application_credential_id: + #application_credential_name: + #application_credential_secret: dex: ldap: From cbe94703b394b3ee74bbdccbf5aae450c71129b7 Mon Sep 17 00:00:00 2001 From: Jan Knipper Date: Wed, 29 Jan 2025 15:32:39 +0100 Subject: [PATCH 3/4] Fix chart --- charts/kubernikus/templates/operator.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/kubernikus/templates/operator.yaml b/charts/kubernikus/templates/operator.yaml index 574c3f5457..e3fa5b92ab 100644 --- a/charts/kubernikus/templates/operator.yaml +++ b/charts/kubernikus/templates/operator.yaml @@ -40,7 +40,7 @@ spec: - kubernikus - operator - --chart-directory=/etc/kubernikus/charts - {{- if and .Values.openstack.auth_url (emtpy .Values.openstack.application_credential_secret) }} + {{- if and .Values.openstack.auth_url (not .Values.openstack.application_credential_secret) }} {{- /* This is a bit convoluted but making sure that the url ends with /v3 */}} - --auth-url={{ trimSuffix "/" .Values.openstack.auth_url | trimSuffix "/v3" }}/v3 - --auth-username={{ .Values.openstack.auth_user_id }} @@ -76,7 +76,7 @@ spec: - name: USE_OCTAVIA value: "true" {{- end }} - {{- if and .Values.openstack.auth_url (empty .Values.openstack.application_credential_secret) }} + {{- if and .Values.openstack.auth_url (not .Values.openstack.application_credential_secret) }} - name: OS_PASSWORD valueFrom: secretKeyRef: From 527f147e342895e591001e9f11e9ea1913bbdf2c Mon Sep 17 00:00:00 2001 From: Jan Knipper Date: Wed, 29 Jan 2025 15:53:55 +0100 Subject: [PATCH 4/4] Fix operator deployment --- charts/kubernikus/templates/operator.yaml | 4 ++++ charts/kubernikus/templates/secret-operator.yaml | 8 ++++++-- charts/kubernikus/test-values.yaml | 3 +++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/charts/kubernikus/templates/operator.yaml b/charts/kubernikus/templates/operator.yaml index e3fa5b92ab..08ca575a25 100644 --- a/charts/kubernikus/templates/operator.yaml +++ b/charts/kubernikus/templates/operator.yaml @@ -84,16 +84,20 @@ spec: key: password {{- end }} {{- if .Values.openstack.application_credential_secret }} + {{- if .Values.openstack.application_credential_id }} - name: OS_APPLICATION_CREDENTIAL_ID valueFrom: secretKeyRef: name: kubernikus-operator key: applicationCredentialID + {{- end }} + {{- if .Values.openstack.application_credential_name }} - name: OS_APPLICATION_CREDENTIAL_NAME valueFrom: secretKeyRef: name: kubernikus-operator key: applicationCredentialName + {{- end }} - name: OS_APPLICATION_CREDENTIAL_SECRET valueFrom: secretKeyRef: diff --git a/charts/kubernikus/templates/secret-operator.yaml b/charts/kubernikus/templates/secret-operator.yaml index c9d78b8360..e1f68370e6 100644 --- a/charts/kubernikus/templates/secret-operator.yaml +++ b/charts/kubernikus/templates/secret-operator.yaml @@ -9,8 +9,12 @@ metadata: data: authURL: {{ required "openstack.auth_url undefined" .Values.openstack.auth_url | trimSuffix "/" | trimSuffix "/v3" | printf "%s/v3" | b64enc }} {{- if .Values.openstack.application_credential_secret }} - applicationCredentialID: {{ required "openstack.application_credential_id undefined" .Values.openstack.application_credential_id | b64enc }} - applicationCredentialName: {{ required "openstack.application_credential_name undefined" .Values.openstack.application_credential_name | b64enc }} + {{- if .Values.openstack.application_credential_id }} + applicationCredentialID: {{ .Values.openstack.application_credential_id | b64enc }} + {{- end }} + {{- if .Values.openstack.application_credential_name }} + applicationCredentialName: {{ .Values.openstack.application_credential_name | b64enc }} + {{- end }} applicationCredentialSecret: {{ required "openstack.application_credential_secret undefined" .Values.openstack.application_credential_secret | b64enc }} {{- else }} username: {{ required "openstack.auth_user_id undefined" .Values.openstack.auth_user_id | b64enc }} diff --git a/charts/kubernikus/test-values.yaml b/charts/kubernikus/test-values.yaml index c9f6a905a6..c0e0130da0 100644 --- a/charts/kubernikus/test-values.yaml +++ b/charts/kubernikus/test-values.yaml @@ -7,6 +7,9 @@ openstack: auth_project: xyz auth_project_domain: xyz region: xy-xy-1 + #application_credential_id: xyz + #application_credential_name: xyz + #application_credential_secret: xyz dex: clientSecret: uhu-ah-secret ldap: