From 22e6ce7fe540cc6c9103f4250b391e0c153f8e2a Mon Sep 17 00:00:00 2001 From: Martin Schuppert Date: Tue, 21 Jan 2025 16:52:55 +0100 Subject: [PATCH] Allow customize http vhost config using HttpdCustomization.CustomConfigSecret This change allows to customize the httpd vhost config using this parameter to specify a secret that contains service config data. The content of each provided snippet gets rendered as a go template and placed into /etc/httpd/conf/httpd_custom__ . At the end of the vhost config in the default httpd template these custom configs get included using `Include conf/httpd_custom__*`. For information on how sections in httpd configuration get merged, check section "How the sections are merged" in https://httpd.apache.org/docs/current/sections.html#merging All possible parameters which can be use in a template can be looked up in the -config-data secret of the service like: $ oc get secret -n openstack heat-config-data -o json | jq -r .data.TemplateParameters | base64 -d or in the running pod of the service in the file: $ cat /var/lib/config-data/default/TemplateParameters The content is a versioned dump of the parameters of the service operator, like: ~~~ DatabaseConnection: mysql+pymysql://user:pwd@openstack.openstack.svc/keystone?read_default_file=/etc/my.cnf KeystoneEndpointInternal: https://keystone-internal.openstack.svc:5000 TransportURL: rabbit://user:pwd@rabbitmq.openstack.svc:5671/?ssl=1 VHosts: internal: Override: false SSLCertificateFile: /etc/pki/tls/certs/internal.crt SSLCertificateKeyFile: /etc/pki/tls/private/internal.key ServerName: keystone-internal.openstack.svc TLS: true public: Override: false SSLCertificateFile: /etc/pki/tls/certs/public.crt SSLCertificateKeyFile: /etc/pki/tls/private/public.key ServerName: keystone-public.openstack.svc TLS: true ... ~~~ Depends-On: https://github.com/openstack-k8s-operators/lib-common/pull/591 Depends-On: https://github.com/openstack-k8s-operators/lib-common/pull/593 Jira: https://issues.redhat.com/browse/OSPRH-13100 Signed-off-by: Martin Schuppert --- api/bases/heat.openstack.org_heatapis.yaml | 14 ++ api/bases/heat.openstack.org_heatcfnapis.yaml | 14 ++ api/bases/heat.openstack.org_heats.yaml | 28 +++ api/v1beta1/common_types.go | 13 + api/v1beta1/heatapi_types.go | 4 + api/v1beta1/heatcfnapi_types.go | 4 + api/v1beta1/zz_generated.deepcopy.go | 22 ++ .../bases/heat.openstack.org_heatapis.yaml | 14 ++ .../bases/heat.openstack.org_heatcfnapis.yaml | 14 ++ .../crd/bases/heat.openstack.org_heats.yaml | 28 +++ controllers/heat_controller.go | 115 +++++++-- controllers/heat_controller_test.go | 110 +++++++-- controllers/heatapi_controller.go | 22 ++ controllers/heatcfnapi_controller.go | 22 ++ go.mod | 4 +- templates/heat/config/heat-api-config.json | 8 +- templates/heat/config/heat-api-httpd.conf | 5 + templates/heat/config/heat-cfnapi-config.json | 8 +- templates/heat/config/heat-cfnapi-httpd.conf | 5 + tests/functional/heat_controller_test.go | 233 ++++++++++++++++++ 20 files changed, 641 insertions(+), 46 deletions(-) diff --git a/api/bases/heat.openstack.org_heatapis.yaml b/api/bases/heat.openstack.org_heatapis.yaml index 4f6aff36..c11fc2de 100644 --- a/api/bases/heat.openstack.org_heatapis.yaml +++ b/api/bases/heat.openstack.org_heatapis.yaml @@ -81,6 +81,20 @@ spec: But can also be used to add additional files. Those get added to the service config dir in /etc/ . TODO: -> implement type: object + httpdCustomization: + description: HttpdCustomization - customize the httpd service + properties: + customConfigSecret: + description: |- + CustomConfigSecret - customize the httpd vhost config using this parameter to specify + a secret that contains service config data. The content of each provided snippet gets + rendered as a go template and placed into /etc/httpd/conf/httpd_custom_ . + In the default httpd template at the end of the vhost those custom configs get + included using `Include conf/httpd_custom__*`. + For information on how sections in httpd configuration get merged, check section + "How the sections are merged" in https://httpd.apache.org/docs/current/sections.html#merging + type: string + type: object nodeSelector: additionalProperties: type: string diff --git a/api/bases/heat.openstack.org_heatcfnapis.yaml b/api/bases/heat.openstack.org_heatcfnapis.yaml index 09b7eb06..dede5932 100644 --- a/api/bases/heat.openstack.org_heatcfnapis.yaml +++ b/api/bases/heat.openstack.org_heatcfnapis.yaml @@ -81,6 +81,20 @@ spec: But can also be used to add additional files. Those get added to the service config dir in /etc/ . TODO: -> implement type: object + httpdCustomization: + description: HttpdCustomization - customize the httpd service + properties: + customConfigSecret: + description: |- + CustomConfigSecret - customize the httpd vhost config using this parameter to specify + a secret that contains service config data. The content of each provided snippet gets + rendered as a go template and placed into /etc/httpd/conf/httpd_custom_ . + In the default httpd template at the end of the vhost those custom configs get + included using `Include conf/httpd_custom__*`. + For information on how sections in httpd configuration get merged, check section + "How the sections are merged" in https://httpd.apache.org/docs/current/sections.html#merging + type: string + type: object nodeSelector: additionalProperties: type: string diff --git a/api/bases/heat.openstack.org_heats.yaml b/api/bases/heat.openstack.org_heats.yaml index 8132b8b2..e2ad0329 100644 --- a/api/bases/heat.openstack.org_heats.yaml +++ b/api/bases/heat.openstack.org_heats.yaml @@ -116,6 +116,20 @@ spec: But can also be used to add additional files. Those get added to the service config dir in /etc/ . TODO: -> implement type: object + httpdCustomization: + description: HttpdCustomization - customize the httpd service + properties: + customConfigSecret: + description: |- + CustomConfigSecret - customize the httpd vhost config using this parameter to specify + a secret that contains service config data. The content of each provided snippet gets + rendered as a go template and placed into /etc/httpd/conf/httpd_custom_ . + In the default httpd template at the end of the vhost those custom configs get + included using `Include conf/httpd_custom__*`. + For information on how sections in httpd configuration get merged, check section + "How the sections are merged" in https://httpd.apache.org/docs/current/sections.html#merging + type: string + type: object nodeSelector: additionalProperties: type: string @@ -406,6 +420,20 @@ spec: But can also be used to add additional files. Those get added to the service config dir in /etc/ . TODO: -> implement type: object + httpdCustomization: + description: HttpdCustomization - customize the httpd service + properties: + customConfigSecret: + description: |- + CustomConfigSecret - customize the httpd vhost config using this parameter to specify + a secret that contains service config data. The content of each provided snippet gets + rendered as a go template and placed into /etc/httpd/conf/httpd_custom_ . + In the default httpd template at the end of the vhost those custom configs get + included using `Include conf/httpd_custom__*`. + For information on how sections in httpd configuration get merged, check section + "How the sections are merged" in https://httpd.apache.org/docs/current/sections.html#merging + type: string + type: object nodeSelector: additionalProperties: type: string diff --git a/api/v1beta1/common_types.go b/api/v1beta1/common_types.go index 2acb4e8d..c3eb7289 100644 --- a/api/v1beta1/common_types.go +++ b/api/v1beta1/common_types.go @@ -103,3 +103,16 @@ type PasswordSelector struct { // StackDomainAdminPassword - Selector to get the heat stack domain admin password from the Secret StackDomainAdminPassword string `json:"stackDomainAdminPassword"` } + +// HttpdCustomization - customize the httpd service +type HttpdCustomization struct { + // +kubebuilder:validation:Optional + // CustomConfigSecret - customize the httpd vhost config using this parameter to specify + // a secret that contains service config data. The content of each provided snippet gets + // rendered as a go template and placed into /etc/httpd/conf/httpd_custom_ . + // In the default httpd template at the end of the vhost those custom configs get + // included using `Include conf/httpd_custom__*`. + // For information on how sections in httpd configuration get merged, check section + // "How the sections are merged" in https://httpd.apache.org/docs/current/sections.html#merging + CustomConfigSecret *string `json:"customConfigSecret,omitempty"` +} diff --git a/api/v1beta1/heatapi_types.go b/api/v1beta1/heatapi_types.go index 2da6c4fd..b77d8722 100644 --- a/api/v1beta1/heatapi_types.go +++ b/api/v1beta1/heatapi_types.go @@ -37,6 +37,10 @@ type HeatAPITemplateCore struct { // Override, provides the ability to override the generated manifest of several child resources. Override APIOverrideSpec `json:"override,omitempty"` + // +kubebuilder:validation:Optional + // HttpdCustomization - customize the httpd service + HttpdCustomization HttpdCustomization `json:"httpdCustomization,omitempty"` + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec // TLS - Parameters related to the TLS diff --git a/api/v1beta1/heatcfnapi_types.go b/api/v1beta1/heatcfnapi_types.go index 28241a16..536c642e 100644 --- a/api/v1beta1/heatcfnapi_types.go +++ b/api/v1beta1/heatcfnapi_types.go @@ -38,6 +38,10 @@ type HeatCfnAPITemplateCore struct { // Override, provides the ability to override the generated manifest of several child resources. Override APIOverrideSpec `json:"override,omitempty"` + // +kubebuilder:validation:Optional + // HttpdCustomization - customize the httpd service + HttpdCustomization HttpdCustomization `json:"httpdCustomization,omitempty"` + // +kubebuilder:validation:Optional // +operator-sdk:csv:customresourcedefinitions:type=spec // TLS - Parameters related to the TLS diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index b320725d..30fb6ad4 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -200,6 +200,7 @@ func (in *HeatAPITemplate) DeepCopy() *HeatAPITemplate { func (in *HeatAPITemplateCore) DeepCopyInto(out *HeatAPITemplateCore) { *out = *in in.Override.DeepCopyInto(&out.Override) + in.HttpdCustomization.DeepCopyInto(&out.HttpdCustomization) in.TLS.DeepCopyInto(&out.TLS) in.HeatServiceTemplate.DeepCopyInto(&out.HeatServiceTemplate) } @@ -339,6 +340,7 @@ func (in *HeatCfnAPITemplate) DeepCopy() *HeatCfnAPITemplate { func (in *HeatCfnAPITemplateCore) DeepCopyInto(out *HeatCfnAPITemplateCore) { *out = *in in.Override.DeepCopyInto(&out.Override) + in.HttpdCustomization.DeepCopyInto(&out.HttpdCustomization) in.TLS.DeepCopyInto(&out.TLS) in.HeatServiceTemplate.DeepCopyInto(&out.HeatServiceTemplate) } @@ -704,6 +706,26 @@ func (in *HeatTemplate) DeepCopy() *HeatTemplate { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HttpdCustomization) DeepCopyInto(out *HttpdCustomization) { + *out = *in + if in.CustomConfigSecret != nil { + in, out := &in.CustomConfigSecret, &out.CustomConfigSecret + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HttpdCustomization. +func (in *HttpdCustomization) DeepCopy() *HttpdCustomization { + if in == nil { + return nil + } + out := new(HttpdCustomization) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PasswordSelector) DeepCopyInto(out *PasswordSelector) { *out = *in diff --git a/config/crd/bases/heat.openstack.org_heatapis.yaml b/config/crd/bases/heat.openstack.org_heatapis.yaml index 4f6aff36..c11fc2de 100644 --- a/config/crd/bases/heat.openstack.org_heatapis.yaml +++ b/config/crd/bases/heat.openstack.org_heatapis.yaml @@ -81,6 +81,20 @@ spec: But can also be used to add additional files. Those get added to the service config dir in /etc/ . TODO: -> implement type: object + httpdCustomization: + description: HttpdCustomization - customize the httpd service + properties: + customConfigSecret: + description: |- + CustomConfigSecret - customize the httpd vhost config using this parameter to specify + a secret that contains service config data. The content of each provided snippet gets + rendered as a go template and placed into /etc/httpd/conf/httpd_custom_ . + In the default httpd template at the end of the vhost those custom configs get + included using `Include conf/httpd_custom__*`. + For information on how sections in httpd configuration get merged, check section + "How the sections are merged" in https://httpd.apache.org/docs/current/sections.html#merging + type: string + type: object nodeSelector: additionalProperties: type: string diff --git a/config/crd/bases/heat.openstack.org_heatcfnapis.yaml b/config/crd/bases/heat.openstack.org_heatcfnapis.yaml index 09b7eb06..dede5932 100644 --- a/config/crd/bases/heat.openstack.org_heatcfnapis.yaml +++ b/config/crd/bases/heat.openstack.org_heatcfnapis.yaml @@ -81,6 +81,20 @@ spec: But can also be used to add additional files. Those get added to the service config dir in /etc/ . TODO: -> implement type: object + httpdCustomization: + description: HttpdCustomization - customize the httpd service + properties: + customConfigSecret: + description: |- + CustomConfigSecret - customize the httpd vhost config using this parameter to specify + a secret that contains service config data. The content of each provided snippet gets + rendered as a go template and placed into /etc/httpd/conf/httpd_custom_ . + In the default httpd template at the end of the vhost those custom configs get + included using `Include conf/httpd_custom__*`. + For information on how sections in httpd configuration get merged, check section + "How the sections are merged" in https://httpd.apache.org/docs/current/sections.html#merging + type: string + type: object nodeSelector: additionalProperties: type: string diff --git a/config/crd/bases/heat.openstack.org_heats.yaml b/config/crd/bases/heat.openstack.org_heats.yaml index 8132b8b2..e2ad0329 100644 --- a/config/crd/bases/heat.openstack.org_heats.yaml +++ b/config/crd/bases/heat.openstack.org_heats.yaml @@ -116,6 +116,20 @@ spec: But can also be used to add additional files. Those get added to the service config dir in /etc/ . TODO: -> implement type: object + httpdCustomization: + description: HttpdCustomization - customize the httpd service + properties: + customConfigSecret: + description: |- + CustomConfigSecret - customize the httpd vhost config using this parameter to specify + a secret that contains service config data. The content of each provided snippet gets + rendered as a go template and placed into /etc/httpd/conf/httpd_custom_ . + In the default httpd template at the end of the vhost those custom configs get + included using `Include conf/httpd_custom__*`. + For information on how sections in httpd configuration get merged, check section + "How the sections are merged" in https://httpd.apache.org/docs/current/sections.html#merging + type: string + type: object nodeSelector: additionalProperties: type: string @@ -406,6 +420,20 @@ spec: But can also be used to add additional files. Those get added to the service config dir in /etc/ . TODO: -> implement type: object + httpdCustomization: + description: HttpdCustomization - customize the httpd service + properties: + customConfigSecret: + description: |- + CustomConfigSecret - customize the httpd vhost config using this parameter to specify + a secret that contains service config data. The content of each provided snippet gets + rendered as a go template and placed into /etc/httpd/conf/httpd_custom_ . + In the default httpd template at the end of the vhost those custom configs get + included using `Include conf/httpd_custom__*`. + For information on how sections in httpd configuration get merged, check section + "How the sections are merged" in https://httpd.apache.org/docs/current/sections.html#merging + type: string + type: object nodeSelector: additionalProperties: type: string diff --git a/controllers/heat_controller.go b/controllers/heat_controller.go index 7d2ddf3e..e4e9df1a 100644 --- a/controllers/heat_controller.go +++ b/controllers/heat_controller.go @@ -17,6 +17,7 @@ import ( "time" "github.com/go-logr/logr" + "gopkg.in/yaml.v2" "github.com/openstack-k8s-operators/lib-common/modules/common" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" @@ -174,18 +175,23 @@ func (r *HeatReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resul // fields to index to reconcile when change const ( - passwordSecretField = ".spec.secret" - transportURLSecretField = ".spec.transportURLSecret" - caBundleSecretNameField = ".spec.tls.caBundleSecretName" - tlsAPIInternalField = ".spec.tls.api.internal.secretName" - tlsAPIPublicField = ".spec.tls.api.public.secretName" - customServiceConfigField = ".spec.customServiceConfigSecrets" + passwordSecretField = ".spec.secret" + transportURLSecretField = ".spec.transportURLSecret" + caBundleSecretNameField = ".spec.tls.caBundleSecretName" + tlsAPIInternalField = ".spec.tls.api.internal.secretName" + tlsAPIPublicField = ".spec.tls.api.public.secretName" + customServiceConfigField = ".spec.customServiceConfigSecrets" + httpdCustomServiceConfigSecretField = ".spec.httpdCustomization.customServiceConfigSecret" + apiHttpdCustomServiceConfigSecretField = ".spec.heatAPI.httpdCustomization.customServiceConfigSecret" + cfnapiHttpdCustomServiceConfigSecretField = ".spec.heatCfnAPI.httpdCustomization.customServiceConfigSecret" ) var ( heatWatchFields = []string{ passwordSecretField, customServiceConfigField, + apiHttpdCustomServiceConfigSecretField, + cfnapiHttpdCustomServiceConfigSecretField, } heatAPIWatchFields = []string{ passwordSecretField, @@ -194,6 +200,7 @@ var ( tlsAPIInternalField, tlsAPIPublicField, customServiceConfigField, + httpdCustomServiceConfigSecretField, } heatCfnWatchFields = []string{ passwordSecretField, @@ -202,6 +209,7 @@ var ( tlsAPIInternalField, tlsAPIPublicField, customServiceConfigField, + httpdCustomServiceConfigSecretField, } heatEngineWatchFields = []string{ passwordSecretField, @@ -237,6 +245,30 @@ func (r *HeatReconciler) SetupWithManager(mgr ctrl.Manager) error { return err } + // index apiHttpdCustomServiceConfigSecretField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &heatv1beta1.Heat{}, apiHttpdCustomServiceConfigSecretField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*heatv1beta1.Heat) + if cr.Spec.HeatAPI.HttpdCustomization.CustomConfigSecret == nil { + return nil + } + return []string{*cr.Spec.HeatAPI.HttpdCustomization.CustomConfigSecret} + }); err != nil { + return err + } + + // index cfnapiHttpdCustomServiceConfigSecretField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &heatv1beta1.Heat{}, cfnapiHttpdCustomServiceConfigSecretField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*heatv1beta1.Heat) + if cr.Spec.HeatCfnAPI.HttpdCustomization.CustomConfigSecret == nil { + return nil + } + return []string{*cr.Spec.HeatCfnAPI.HttpdCustomization.CustomConfigSecret} + }); err != nil { + return err + } + memcachedFn := func(_ context.Context, o client.Object) []reconcile.Request { result := []reconcile.Request{} @@ -991,23 +1023,46 @@ func (r *HeatReconciler) generateServiceSecrets( templateParameters := initTemplateParameters(instance, authURL, password, domainAdminPassword, authEncryptionKey, transportURL, mc, databaseAccount, dbSecret) + httpdAPIOverrideSecret := &corev1.Secret{} + if instance.Spec.HeatAPI.HttpdCustomization.CustomConfigSecret != nil && *instance.Spec.HeatAPI.HttpdCustomization.CustomConfigSecret != "" { + httpdAPIOverrideSecret, _, err = oko_secret.GetSecret(ctx, h, *instance.Spec.HeatAPI.HttpdCustomization.CustomConfigSecret, instance.Namespace) + if err != nil { + return err + } + } + + httpdCfnAPIOverrideSecret := &corev1.Secret{} + if instance.Spec.HeatCfnAPI.HttpdCustomization.CustomConfigSecret != nil && *instance.Spec.HeatCfnAPI.HttpdCustomization.CustomConfigSecret != "" { + httpdCfnAPIOverrideSecret, _, err = oko_secret.GetSecret(ctx, h, *instance.Spec.HeatCfnAPI.HttpdCustomization.CustomConfigSecret, instance.Namespace) + if err != nil { + return err + } + } + // Render vhost configuration for API and CFN httpdAPIVhostConfig := map[string]interface{}{} httpdCfnAPIVhostConfig := map[string]interface{}{} + customTemplates := map[string]string{} for _, endpt := range []service.Endpoint{service.EndpointInternal, service.EndpointPublic} { var ( apiTLSEnabled = instance.Spec.HeatAPI.TLS.API.Enabled(endpt) cfnAPITLSEnabled = instance.Spec.HeatCfnAPI.TLS.API.Enabled(endpt) ) - renderVhost(httpdAPIVhostConfig, instance, endpt, heatapi.ServiceName, apiTLSEnabled) - renderVhost(httpdCfnAPIVhostConfig, instance, endpt, heatcfnapi.ServiceName, cfnAPITLSEnabled) + customTemplates = util.MergeMaps(customTemplates, + renderVhost(httpdAPIVhostConfig, instance, endpt, heatapi.ServiceName, apiTLSEnabled, httpdAPIOverrideSecret)) + customTemplates = util.MergeMaps(customTemplates, + renderVhost(httpdCfnAPIVhostConfig, instance, endpt, heatcfnapi.ServiceName, cfnAPITLSEnabled, httpdCfnAPIOverrideSecret)) } // create HeatAPI httpd vhost template parameters templateParameters["APIvHosts"] = httpdAPIVhostConfig templateParameters["CfnAPIvHosts"] = httpdCfnAPIVhostConfig - secrets := createSecretTemplates(instance, customData, templateParameters, secretLabels) + secrets, err := createSecretTemplates(instance, customData, templateParameters, secretLabels, customTemplates) + if err != nil { + return err + } + return oko_secret.EnsureSecrets(ctx, h, instance, secrets, envVars) } @@ -1303,23 +1358,31 @@ func generateCustomData(instance *heatv1beta1.Heat, tlsCfg *tls.Service, db *mar } // createSecretsTemplates - Takes inputs and renders the templates that will be used for our Secrets -func createSecretTemplates(instance *heatv1beta1.Heat, customData map[string]string, templateParameters map[string]interface{}, secretLabels map[string]string) []util.Template { +func createSecretTemplates(instance *heatv1beta1.Heat, customData map[string]string, templateParameters map[string]interface{}, secretLabels map[string]string, customTemplates map[string]string) ([]util.Template, error) { var ( secretName = fmt.Sprintf("%s-config-data", instance.Name) ) + // Marshal the templateParameters map to YAML + yamlData, err := yaml.Marshal(templateParameters) + if err != nil { + return []util.Template{}, fmt.Errorf("Error marshalling to YAML: %w", err) + } + customData[common.TemplateParameters] = string(yamlData) + return []util.Template{ // Secret { - Name: secretName, - Namespace: instance.Namespace, - Type: util.TemplateTypeConfig, - InstanceType: instance.Kind, - CustomData: customData, - ConfigOptions: templateParameters, - Labels: secretLabels, + Name: secretName, + Namespace: instance.Namespace, + Type: util.TemplateTypeConfig, + InstanceType: instance.Kind, + CustomData: customData, + ConfigOptions: templateParameters, + StringTemplate: customTemplates, + Labels: secretLabels, }, - } + }, nil } // initTemplateParameters - takes inputs related to external objects in the cluster and renders the @@ -1360,7 +1423,9 @@ func initTemplateParameters( } } -func renderVhost(httpdVhostConfig map[string]interface{}, instance *heatv1beta1.Heat, endpt service.Endpoint, serviceName string, tlsEnabled bool) { +func renderVhost(httpdVhostConfig map[string]interface{}, instance *heatv1beta1.Heat, endpt service.Endpoint, serviceName string, tlsEnabled bool, httpdOverrideSecret *corev1.Secret) map[string]string { + customTemplates := map[string]string{} + var ( ServerNameString = fmt.Sprintf("%s-%s.%s.svc", serviceName, endpt.String(), instance.Namespace) SSLCertFilePath = fmt.Sprintf("/etc/pki/tls/certs/%s.crt", endpt.String()) @@ -1374,7 +1439,19 @@ func renderVhost(httpdVhostConfig map[string]interface{}, instance *heatv1beta1. endptConfig["SSLCertificateFile"] = SSLCertFilePath endptConfig["SSLCertificateKeyFile"] = SSLKeyFilePath } + + endptConfig["Override"] = false + if httpdOverrideSecret != nil && len(httpdOverrideSecret.Data) > 0 { + endptConfig["Override"] = true + for key, data := range httpdOverrideSecret.Data { + if len(data) > 0 { + customTemplates["httpd_custom_"+serviceName+"_"+endpt.String()+"_"+key] = string(data) + } + } + } httpdVhostConfig[endpt.String()] = endptConfig + + return customTemplates } // validateAuthEncryptionKey - the heat_auth_encrption_key needs to be 32 characters long. This function validates diff --git a/controllers/heat_controller_test.go b/controllers/heat_controller_test.go index fee401cc..ce91ba6f 100644 --- a/controllers/heat_controller_test.go +++ b/controllers/heat_controller_test.go @@ -5,6 +5,7 @@ import ( "testing" heatv1beta1 "github.com/openstack-k8s-operators/heat-operator/api/v1beta1" + corev1 "k8s.io/api/core/v1" "github.com/openstack-k8s-operators/lib-common/modules/common/service" ) @@ -14,45 +15,108 @@ func TestRenderVhost(t *testing.T) { instanceTest1.Namespace = "test1HeatNamespace" tests := []struct { - name string - instance *heatv1beta1.Heat - endpt service.Endpoint - serviceName string - tlsEnabled bool + name string + instance *heatv1beta1.Heat + endpt service.Endpoint + serviceName string + tlsEnabled bool + httpdOverrideSecret *corev1.Secret + want map[string]interface{} + wantCustomTemplates map[string]string }{ { - name: "Basic case with TLS disabled", + name: "Basic case with TLS disabled", + instance: instanceTest1, + endpt: "internal", + serviceName: "my-service", + tlsEnabled: false, + wantCustomTemplates: map[string]string{}, + want: map[string]interface{}{ + "internal": map[string]interface{}{ + "ServerName": "my-service-internal.test1HeatNamespace.svc", + "TLS": false, + "Override": false, + }, + }, + }, + { + name: "Basic case with TLS enabled", + instance: instanceTest1, + endpt: "public", + serviceName: "my-service", + tlsEnabled: true, + wantCustomTemplates: map[string]string{}, + want: map[string]interface{}{ + "public": map[string]interface{}{ + "ServerName": "my-service-public.test1HeatNamespace.svc", + "TLS": true, + "SSLCertificateFile": "/etc/pki/tls/certs/public.crt", + "SSLCertificateKeyFile": "/etc/pki/tls/private/public.key", + "Override": false, + }, + }, + }, + { + name: "Basic case with TLS disabled and httpdOverrideSecret", instance: instanceTest1, endpt: "internal", serviceName: "my-service", tlsEnabled: false, + httpdOverrideSecret: &corev1.Secret{ + Data: map[string][]byte{ + "foo": []byte("bar"), + }, + }, + wantCustomTemplates: map[string]string{ + "httpd_custom_my-service_internal_foo": "bar", + }, + want: map[string]interface{}{ + "internal": map[string]interface{}{ + "ServerName": "my-service-internal.test1HeatNamespace.svc", + "TLS": false, + "Override": true, + }, + }, }, { - name: "Basic case with TLS enabled", + name: "Basic case with TLS enabled and httpdOverrideSecret", instance: instanceTest1, endpt: "public", serviceName: "my-service", tlsEnabled: true, + httpdOverrideSecret: &corev1.Secret{ + Data: map[string][]byte{ + "foo": []byte("bar"), + }, + }, + wantCustomTemplates: map[string]string{ + "httpd_custom_my-service_public_foo": "bar", + }, + want: map[string]interface{}{ + "public": map[string]interface{}{ + "ServerName": "my-service-public.test1HeatNamespace.svc", + "TLS": true, + "SSLCertificateFile": "/etc/pki/tls/certs/public.crt", + "SSLCertificateKeyFile": "/etc/pki/tls/private/public.key", + "Override": true, + }, + }, }, } - expected := map[string]interface{}{ - "internal": map[string]interface{}{ - "ServerName": "my-service-internal.test1HeatNamespace.svc", - "TLS": false, - }, - "public": map[string]interface{}{ - "ServerName": "my-service-public.test1HeatNamespace.svc", - "TLS": true, - "SSLCertificateFile": "/etc/pki/tls/certs/public.crt", - "SSLCertificateKeyFile": "/etc/pki/tls/private/public.key", - }, - } result := map[string]interface{}{} for _, tt := range tests { - renderVhost(result, tt.instance, tt.endpt, tt.serviceName, tt.tlsEnabled) - } - if !reflect.DeepEqual(result, expected) { - t.Errorf("Expected %v, got %v", expected, result) + tt := &tt + t.Run(tt.name, func(t *testing.T) { + customTemplates := renderVhost(result, tt.instance, tt.endpt, tt.serviceName, tt.tlsEnabled, tt.httpdOverrideSecret) + if !reflect.DeepEqual(result[string(tt.endpt)], tt.want[string(tt.endpt)]) { + t.Errorf("Expected %v\ngot %v", tt.want, result) + } + if !reflect.DeepEqual(customTemplates, tt.wantCustomTemplates) { + t.Errorf("CustomTemplate = %v, want %v", customTemplates, tt.wantCustomTemplates) + } + + }) + } } diff --git a/controllers/heatapi_controller.go b/controllers/heatapi_controller.go index 5c57efc7..bb7bb81c 100644 --- a/controllers/heatapi_controller.go +++ b/controllers/heatapi_controller.go @@ -19,6 +19,7 @@ package controllers import ( "context" "fmt" + "strings" "time" appsv1 "k8s.io/api/apps/v1" @@ -54,6 +55,7 @@ import ( "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/openstack-k8s-operators/lib-common/modules/common/tls" "github.com/openstack-k8s-operators/lib-common/modules/common/util" + "golang.org/x/exp/maps" ) // HeatAPIReconciler reconciles a Heat object @@ -237,6 +239,18 @@ func (r *HeatAPIReconciler) SetupWithManager(mgr ctrl.Manager) error { return err } + // index httpdOverrideSecretField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &heatv1beta1.HeatAPI{}, httpdCustomServiceConfigSecretField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*heatv1beta1.HeatAPI) + if cr.Spec.HttpdCustomization.CustomConfigSecret == nil { + return nil + } + return []string{*cr.Spec.HttpdCustomization.CustomConfigSecret} + }); err != nil { + return err + } + configMapFn := func(_ context.Context, o client.Object) []reconcile.Request { result := []reconcile.Request{} @@ -278,6 +292,8 @@ func (r *HeatAPIReconciler) SetupWithManager(mgr ctrl.Manager) error { // watch the config CMs we don't own Watches(&corev1.ConfigMap{}, handler.EnqueueRequestsFromMapFunc(configMapFn)). + Watches(&corev1.Secret{}, + handler.EnqueueRequestsFromMapFunc(configMapFn)). Watches( &corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), @@ -885,6 +901,12 @@ func (r *HeatAPIReconciler) generateServiceSecrets( customData[heat.DefaultsConfigFileName] = string(heatSecret.Data[heat.DefaultsConfigFileName]) customData[heat.CustomConfigFileName] = string(heatSecret.Data[heat.CustomConfigFileName]) customData[heat.CustomConfigSecretsFileName] = string(heatSecret.Data[heat.CustomConfigSecretsFileName]) + customData[common.TemplateParameters] = string(heatSecret.Data[common.TemplateParameters]) + for _, key := range maps.Keys(heatSecret.Data) { + if strings.HasPrefix(key, "httpd_custom_"+heatapi.ServiceName) { + customData[key] = string(heatSecret.Data[key]) + } + } customSecrets := "" for _, secretName := range instance.Spec.CustomServiceConfigSecrets { diff --git a/controllers/heatcfnapi_controller.go b/controllers/heatcfnapi_controller.go index 72904fc9..81f69efe 100644 --- a/controllers/heatcfnapi_controller.go +++ b/controllers/heatcfnapi_controller.go @@ -19,6 +19,7 @@ package controllers import ( "context" "fmt" + "strings" "time" appsv1 "k8s.io/api/apps/v1" @@ -54,6 +55,7 @@ import ( "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/openstack-k8s-operators/lib-common/modules/common/tls" "github.com/openstack-k8s-operators/lib-common/modules/common/util" + "golang.org/x/exp/maps" ) // HeatCfnAPIReconciler reconciles a Heat object @@ -239,6 +241,18 @@ func (r *HeatCfnAPIReconciler) SetupWithManager(mgr ctrl.Manager) error { return err } + // index httpdOverrideSecretField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &heatv1beta1.HeatCfnAPI{}, httpdCustomServiceConfigSecretField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*heatv1beta1.HeatCfnAPI) + if cr.Spec.HttpdCustomization.CustomConfigSecret == nil { + return nil + } + return []string{*cr.Spec.HttpdCustomization.CustomConfigSecret} + }); err != nil { + return err + } + configMapFn := func(_ context.Context, o client.Object) []reconcile.Request { result := []reconcile.Request{} @@ -280,6 +294,8 @@ func (r *HeatCfnAPIReconciler) SetupWithManager(mgr ctrl.Manager) error { // watch the config CMs we don't own Watches(&corev1.ConfigMap{}, handler.EnqueueRequestsFromMapFunc(configMapFn)). + Watches(&corev1.Secret{}, + handler.EnqueueRequestsFromMapFunc(configMapFn)). Watches( &corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), @@ -882,6 +898,12 @@ func (r *HeatCfnAPIReconciler) generateServiceSecrets( customData[heat.DefaultsConfigFileName] = string(heatSecret.Data[heat.DefaultsConfigFileName]) customData[heat.CustomConfigFileName] = string(heatSecret.Data[heat.CustomConfigFileName]) customData[heat.CustomConfigSecretsFileName] = string(heatSecret.Data[heat.CustomConfigSecretsFileName]) + customData[common.TemplateParameters] = string(heatSecret.Data[common.TemplateParameters]) + for _, key := range maps.Keys(heatSecret.Data) { + if strings.HasPrefix(key, "httpd_custom_"+heatcfnapi.ServiceName) { + customData[key] = string(heatSecret.Data[key]) + } + } customSecrets := "" for _, secretName := range instance.Spec.CustomServiceConfigSecrets { diff --git a/go.mod b/go.mod index 55c18902..c55a19a5 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,8 @@ require ( github.com/openstack-k8s-operators/lib-common/modules/openstack v0.5.1-0.20250124131400-f604bec9afd2 github.com/openstack-k8s-operators/lib-common/modules/test v0.5.1-0.20250124131400-f604bec9afd2 github.com/openstack-k8s-operators/mariadb-operator/api v0.5.1-0.20250114123606-99679fd7aabe + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 + gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.29.13 k8s.io/apimachinery v0.29.13 k8s.io/client-go v0.29.13 @@ -60,7 +62,6 @@ require ( github.com/spf13/pflag v1.0.5 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect @@ -73,7 +74,6 @@ require ( google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.29.13 // indirect k8s.io/component-base v0.29.13 // indirect diff --git a/templates/heat/config/heat-api-config.json b/templates/heat/config/heat-api-config.json index 118e1bb8..2a2717d7 100644 --- a/templates/heat/config/heat-api-config.json +++ b/templates/heat/config/heat-api-config.json @@ -4,7 +4,6 @@ { "source": "/var/lib/config-data/default/heat-api-httpd.conf", "dest": "/etc/httpd/conf/httpd.conf", - "merge": false, "preserve_properties": true, "perm": "0644" }, @@ -29,6 +28,13 @@ "perm": "0600", "optional": true, "merge": true + }, + { + "source": "/var/lib/config-data/default/httpd_custom_heat-api*", + "dest": "/etc/httpd/conf/", + "owner": "apache", + "perm": "0444", + "optional": true } ], "permissions": [ diff --git a/templates/heat/config/heat-api-httpd.conf b/templates/heat/config/heat-api-httpd.conf index 2e2c7f35..c156f009 100644 --- a/templates/heat/config/heat-api-httpd.conf +++ b/templates/heat/config/heat-api-httpd.conf @@ -54,5 +54,10 @@ ErrorLog /dev/stdout WSGIPassAuthorization On Timeout {{ $.Timeout }} + +{{- if $vhost.Override }} + Include conf/httpd_custom_heat-api_{{ $endpt }}_* +{{- end }} + {{ end }} diff --git a/templates/heat/config/heat-cfnapi-config.json b/templates/heat/config/heat-cfnapi-config.json index 8370c10f..dcdf4b57 100644 --- a/templates/heat/config/heat-cfnapi-config.json +++ b/templates/heat/config/heat-cfnapi-config.json @@ -4,7 +4,6 @@ { "source": "/var/lib/config-data/default/heat-cfnapi-httpd.conf", "dest": "/etc/httpd/conf/httpd.conf", - "merge": false, "preserve_properties": true, "perm": "0644" }, @@ -29,6 +28,13 @@ "perm": "0600", "optional": true, "merge": true + }, + { + "source": "/var/lib/config-data/default/httpd_custom_heat-cfnapi*", + "dest": "/etc/httpd/conf/", + "owner": "apache", + "perm": "0444", + "optional": true } ], "permissions": [ diff --git a/templates/heat/config/heat-cfnapi-httpd.conf b/templates/heat/config/heat-cfnapi-httpd.conf index cb696511..c266e904 100644 --- a/templates/heat/config/heat-cfnapi-httpd.conf +++ b/templates/heat/config/heat-cfnapi-httpd.conf @@ -54,5 +54,10 @@ ErrorLog /dev/stdout WSGIPassAuthorization On Timeout {{ $.Timeout }} + +{{- if $vhost.Override }} + Include conf/httpd_custom_heat-cfnapi_{{ $endpt }}_* +{{- end }} + {{ end }} diff --git a/tests/functional/heat_controller_test.go b/tests/functional/heat_controller_test.go index 95b9e696..7d2f0925 100644 --- a/tests/functional/heat_controller_test.go +++ b/tests/functional/heat_controller_test.go @@ -36,6 +36,7 @@ import ( "github.com/openstack-k8s-operators/heat-operator/pkg/heat" memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" + "github.com/openstack-k8s-operators/lib-common/modules/common" condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" ) @@ -630,6 +631,238 @@ var _ = Describe("Heat controller", func() { }) }) + When("A HeatAPI is created with HttpdCustomization.CustomConfigSecret", func() { + BeforeEach(func() { + customServiceConfigSecretName := types.NamespacedName{Name: "foo", Namespace: namespace} + customConfig := []byte(`CustomParam "foo" +CustomKeystoneInternalURL "{{ .KeystoneInternalURL }}"`) + th.CreateSecret( + customServiceConfigSecretName, + map[string][]byte{ + "bar.conf": customConfig, + }, + ) + + spec := GetDefaultHeatSpec() + heatAPI := GetDefaultHeatAPISpec() + heatAPI["httpdCustomization"] = map[string]interface{}{ + "customConfigSecret": customServiceConfigSecretName.Name, + } + spec["heatAPI"] = heatAPI + DeferCleanup(th.DeleteInstance, CreateHeat(heatName, spec)) + + DeferCleanup( + k8sClient.Delete, ctx, CreateHeatSecret(namespace, SecretName)) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) + infra.SimulateMemcachedReady(types.NamespacedName{ + Name: "memcached", + Namespace: namespace, + }) + DeferCleanup( + k8sClient.Delete, ctx, CreateHeatMessageBusSecret(namespace, HeatMessageBusSecretName)) + infra.SimulateTransportURLReady(heatTransportURLName) + keystoneAPIName := keystone.CreateKeystoneAPI(namespace) + keystoneAPI = keystone.GetKeystoneAPI(keystoneAPIName) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + namespace, + GetHeat(heatName).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetHeat(heatName).Spec.DatabaseAccount}) + mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: heat.DatabaseCRName}) + }) + + It("it renders the custom template and adds it to the config-data secret for HeatAPI", func() { + scrt := th.GetSecret(heatConfigSecretName) + Expect(scrt).ShouldNot(BeNil()) + Expect(scrt.Data).Should(HaveKey(common.TemplateParameters)) + configData := string(scrt.Data[common.TemplateParameters]) + + keystoneInternaURL := "http://keystone-internal.openstack.svc:5000" + Expect(configData).Should(ContainSubstring(fmt.Sprintf("KeystoneInternalURL: %s", keystoneInternaURL))) + + for _, cfg := range []string{"httpd_custom_heat-api_internal_bar.conf", "httpd_custom_heat-api_public_bar.conf"} { + Expect(scrt.Data).Should(HaveKey(cfg)) + configData := string(scrt.Data[cfg]) + Expect(configData).Should(ContainSubstring("CustomParam \"foo\"")) + Expect(configData).Should(ContainSubstring(fmt.Sprintf("CustomKeystoneInternalURL \"%s\"", keystoneInternaURL))) + } + }) + + It("it has NOT renderd the custom template and added it to the config-data secret for HeatCfnAPI", func() { + scrt := th.GetSecret(heatConfigSecretName) + Expect(scrt).ShouldNot(BeNil()) + Expect(scrt.Data).Should(HaveKey(common.TemplateParameters)) + configData := string(scrt.Data[common.TemplateParameters]) + keystoneInternaURL := "http://keystone-internal.openstack.svc:5000" + Expect(configData).Should(ContainSubstring(fmt.Sprintf("KeystoneInternalURL: %s", keystoneInternaURL))) + + Expect(scrt.Data).Should(Not(HaveKey("httpd_custom_heat-cfnapi_internal_bar.conf"))) + Expect(scrt.Data).Should(Not(HaveKey("httpd_custom_heat-cfnapi_public_bar.conf"))) + }) + }) + + When("A HeatCfnAPI is created with HttpdCustomization.CustomConfigSecret", func() { + BeforeEach(func() { + customServiceConfigSecretName := types.NamespacedName{Name: "foo", Namespace: namespace} + customConfig := []byte(`CustomParam "foo" +CustomKeystoneInternalURL "{{ .KeystoneInternalURL }}"`) + th.CreateSecret( + customServiceConfigSecretName, + map[string][]byte{ + "bar.conf": customConfig, + }, + ) + + spec := GetDefaultHeatSpec() + heatCfnAPI := GetDefaultHeatCFNAPISpec() + heatCfnAPI["httpdCustomization"] = map[string]interface{}{ + "customConfigSecret": customServiceConfigSecretName.Name, + } + spec["heatCfnAPI"] = heatCfnAPI + DeferCleanup(th.DeleteInstance, CreateHeat(heatName, spec)) + + DeferCleanup( + k8sClient.Delete, ctx, CreateHeatSecret(namespace, SecretName)) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) + infra.SimulateMemcachedReady(types.NamespacedName{ + Name: "memcached", + Namespace: namespace, + }) + DeferCleanup( + k8sClient.Delete, ctx, CreateHeatMessageBusSecret(namespace, HeatMessageBusSecretName)) + infra.SimulateTransportURLReady(heatTransportURLName) + keystoneAPIName := keystone.CreateKeystoneAPI(namespace) + keystoneAPI = keystone.GetKeystoneAPI(keystoneAPIName) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + namespace, + GetHeat(heatName).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetHeat(heatName).Spec.DatabaseAccount}) + mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: heat.DatabaseCRName}) + }) + + It("it renders the custom template and adds it to the config-data secret for HeatCfnAPI", func() { + scrt := th.GetSecret(heatConfigSecretName) + Expect(scrt).ShouldNot(BeNil()) + Expect(scrt.Data).Should(HaveKey(common.TemplateParameters)) + configData := string(scrt.Data[common.TemplateParameters]) + + keystoneInternaURL := "http://keystone-internal.openstack.svc:5000" + Expect(configData).Should(ContainSubstring(fmt.Sprintf("KeystoneInternalURL: %s", keystoneInternaURL))) + + for _, cfg := range []string{"httpd_custom_heat-cfnapi_internal_bar.conf", "httpd_custom_heat-cfnapi_public_bar.conf"} { + Expect(scrt.Data).Should(HaveKey(cfg)) + configData := string(scrt.Data[cfg]) + Expect(configData).Should(ContainSubstring("CustomParam \"foo\"")) + Expect(configData).Should(ContainSubstring(fmt.Sprintf("CustomKeystoneInternalURL \"%s\"", keystoneInternaURL))) + } + }) + + It("it has NOT renderd the custom template and added it to the config-data secret for HeatAPI", func() { + scrt := th.GetSecret(heatConfigSecretName) + Expect(scrt).ShouldNot(BeNil()) + Expect(scrt.Data).Should(HaveKey(common.TemplateParameters)) + configData := string(scrt.Data[common.TemplateParameters]) + keystoneInternaURL := "http://keystone-internal.openstack.svc:5000" + Expect(configData).Should(ContainSubstring(fmt.Sprintf("KeystoneInternalURL: %s", keystoneInternaURL))) + + Expect(scrt.Data).Should(Not(HaveKey("httpd_custom_heat-api_internal_bar.conf"))) + Expect(scrt.Data).Should(Not(HaveKey("httpd_custom_heat-api_public_bar.conf"))) + }) + }) + + When("A HeatAPI _and_ HeatCfnAPI is created with HttpdCustomization.CustomConfigSecret", func() { + BeforeEach(func() { + customServiceConfigSecretName := types.NamespacedName{Name: "foo", Namespace: namespace} + customConfig := []byte(`CustomParam "foo" +CustomKeystoneInternalURL "{{ .KeystoneInternalURL }}"`) + th.CreateSecret( + customServiceConfigSecretName, + map[string][]byte{ + "bar.conf": customConfig, + }, + ) + + spec := GetDefaultHeatSpec() + heatCfnAPI := GetDefaultHeatCFNAPISpec() + heatCfnAPI["httpdCustomization"] = map[string]interface{}{ + "customConfigSecret": customServiceConfigSecretName.Name, + } + + spec["heatCfnAPI"] = heatCfnAPI + + heatAPI := GetDefaultHeatAPISpec() + heatAPI["httpdCustomization"] = map[string]interface{}{ + "customConfigSecret": customServiceConfigSecretName.Name, + } + spec["heatAPI"] = heatAPI + + DeferCleanup(th.DeleteInstance, CreateHeat(heatName, spec)) + + DeferCleanup( + k8sClient.Delete, ctx, CreateHeatSecret(namespace, SecretName)) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) + infra.SimulateMemcachedReady(types.NamespacedName{ + Name: "memcached", + Namespace: namespace, + }) + DeferCleanup( + k8sClient.Delete, ctx, CreateHeatMessageBusSecret(namespace, HeatMessageBusSecretName)) + infra.SimulateTransportURLReady(heatTransportURLName) + keystoneAPIName := keystone.CreateKeystoneAPI(namespace) + keystoneAPI = keystone.GetKeystoneAPI(keystoneAPIName) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + namespace, + GetHeat(heatName).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetHeat(heatName).Spec.DatabaseAccount}) + mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: heat.DatabaseCRName}) + }) + + It("it renders the custom template and adds it to the config-data secret for HeatCfnAPI _and_ HeatAPI", func() { + scrt := th.GetSecret(heatConfigSecretName) + Expect(scrt).ShouldNot(BeNil()) + Expect(scrt.Data).Should(HaveKey(common.TemplateParameters)) + configData := string(scrt.Data[common.TemplateParameters]) + + keystoneInternaURL := "http://keystone-internal.openstack.svc:5000" + Expect(configData).Should(ContainSubstring(fmt.Sprintf("KeystoneInternalURL: %s", keystoneInternaURL))) + + for _, cfg := range []string{ + "httpd_custom_heat-cfnapi_internal_bar.conf", + "httpd_custom_heat-cfnapi_public_bar.conf", + "httpd_custom_heat-api_internal_bar.conf", + "httpd_custom_heat-api_public_bar.conf", + } { + Expect(scrt.Data).Should(HaveKey(cfg)) + configData := string(scrt.Data[cfg]) + Expect(configData).Should(ContainSubstring("CustomParam \"foo\"")) + Expect(configData).Should(ContainSubstring(fmt.Sprintf("CustomKeystoneInternalURL \"%s\"", keystoneInternaURL))) + } + }) + }) + // Run MariaDBAccount suite tests. these are pre-packaged ginkgo tests // that exercise standard account create / update patterns that should be // common to all controllers that ensure MariaDBAccount CRs.