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/go.mod b/api/go.mod index 18e879c1..8502d9fc 100644 --- a/api/go.mod +++ b/api/go.mod @@ -3,7 +3,7 @@ module github.com/openstack-k8s-operators/heat-operator/api go 1.21 require ( - github.com/openstack-k8s-operators/lib-common/modules/common v0.5.1-0.20241216113837-d172b3ac0f4e + github.com/openstack-k8s-operators/lib-common/modules/common v0.5.1-0.20250116145727-01a8948d5dd7 k8s.io/api v0.29.13 k8s.io/apimachinery v0.29.13 sigs.k8s.io/controller-runtime v0.17.6 diff --git a/api/go.sum b/api/go.sum index 0b9206de..0c5f945d 100644 --- a/api/go.sum +++ b/api/go.sum @@ -73,8 +73,8 @@ github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= -github.com/openstack-k8s-operators/lib-common/modules/common v0.5.1-0.20241216113837-d172b3ac0f4e h1:hf4kVQBkyG79WcHBxdQ25QrDBbGFdarebS1Tc0Xclq4= -github.com/openstack-k8s-operators/lib-common/modules/common v0.5.1-0.20241216113837-d172b3ac0f4e/go.mod h1:YpNTuJhDWhbXM50O3qBkhO7M+OOyRmWkNVmJ4y3cyFs= +github.com/openstack-k8s-operators/lib-common/modules/common v0.5.1-0.20250116145727-01a8948d5dd7 h1:vXHpH93PjbAgg5ZN6n5WmxkybVQOs0nhXvVw62o7aZs= +github.com/openstack-k8s-operators/lib-common/modules/common v0.5.1-0.20250116145727-01a8948d5dd7/go.mod h1:YpNTuJhDWhbXM50O3qBkhO7M+OOyRmWkNVmJ4y3cyFs= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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 c79ba8f6..08b1c3a3 100644 --- a/go.mod +++ b/go.mod @@ -12,10 +12,12 @@ require ( github.com/openstack-k8s-operators/heat-operator/api v0.3.1-0.20240214134649-6643d1b09d49 github.com/openstack-k8s-operators/infra-operator/apis v0.5.1-0.20241217184302-c302f3d72ada github.com/openstack-k8s-operators/keystone-operator/api v0.5.1-0.20241217165019-8e243bd36596 - github.com/openstack-k8s-operators/lib-common/modules/common v0.5.1-0.20241216113837-d172b3ac0f4e + github.com/openstack-k8s-operators/lib-common/modules/common v0.5.1-0.20250116145727-01a8948d5dd7 github.com/openstack-k8s-operators/lib-common/modules/openstack v0.5.1-0.20241216113837-d172b3ac0f4e github.com/openstack-k8s-operators/lib-common/modules/test v0.5.1-0.20241216113837-d172b3ac0f4e github.com/openstack-k8s-operators/mariadb-operator/api v0.5.1-0.20241217172849-1709c6e07dea + 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/go.sum b/go.sum index ced22334..0f2f59bc 100644 --- a/go.sum +++ b/go.sum @@ -82,8 +82,8 @@ github.com/openstack-k8s-operators/infra-operator/apis v0.5.1-0.20241217184302-c github.com/openstack-k8s-operators/infra-operator/apis v0.5.1-0.20241217184302-c302f3d72ada/go.mod h1:gznNWtIOdZLwyv3/LmWbDqtwRgtyzCw616Rwrn51DT0= github.com/openstack-k8s-operators/keystone-operator/api v0.5.1-0.20241217165019-8e243bd36596 h1:JKeShCY9BQj6cYDk44bgEIm8jPcvggodGxrW4ECzsv4= github.com/openstack-k8s-operators/keystone-operator/api v0.5.1-0.20241217165019-8e243bd36596/go.mod h1:CyuEOM1TpXKNUR1n8cudNtRzTEwkzv90JFkpDPPId8E= -github.com/openstack-k8s-operators/lib-common/modules/common v0.5.1-0.20241216113837-d172b3ac0f4e h1:hf4kVQBkyG79WcHBxdQ25QrDBbGFdarebS1Tc0Xclq4= -github.com/openstack-k8s-operators/lib-common/modules/common v0.5.1-0.20241216113837-d172b3ac0f4e/go.mod h1:YpNTuJhDWhbXM50O3qBkhO7M+OOyRmWkNVmJ4y3cyFs= +github.com/openstack-k8s-operators/lib-common/modules/common v0.5.1-0.20250116145727-01a8948d5dd7 h1:vXHpH93PjbAgg5ZN6n5WmxkybVQOs0nhXvVw62o7aZs= +github.com/openstack-k8s-operators/lib-common/modules/common v0.5.1-0.20250116145727-01a8948d5dd7/go.mod h1:YpNTuJhDWhbXM50O3qBkhO7M+OOyRmWkNVmJ4y3cyFs= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.5.1-0.20241216113837-d172b3ac0f4e h1:HFo4OqPY0x4ZQeaWI2YGonTXAGTQFt+rOEJlfZVhS7s= github.com/openstack-k8s-operators/lib-common/modules/openstack v0.5.1-0.20241216113837-d172b3ac0f4e/go.mod h1:IASoGvp5QM/tBJUd/8i8uIjj4DBnI+64Ydh4r7pmnvA= github.com/openstack-k8s-operators/lib-common/modules/test v0.5.1-0.20241216113837-d172b3ac0f4e h1:/iWDp3j+ET3gE5IjKHtdZaPd4SQyLHB/4L5jB16cV3I= diff --git a/templates/heat/config/heat-api-config.json b/templates/heat/config/heat-api-config.json index 118e1bb8..b3b6473f 100644 --- a/templates/heat/config/heat-api-config.json +++ b/templates/heat/config/heat-api-config.json @@ -29,6 +29,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..2c9ddc9d 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_{{ $endpt }}_* +{{- end }} + {{ end }} diff --git a/templates/heat/config/heat-cfnapi-config.json b/templates/heat/config/heat-cfnapi-config.json index 8370c10f..171b1eb8 100644 --- a/templates/heat/config/heat-cfnapi-config.json +++ b/templates/heat/config/heat-cfnapi-config.json @@ -29,6 +29,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..b55823ef 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_{{ $endpt }}_* +{{- end }} + {{ end }} diff --git a/tests/functional/heat_controller_test.go b/tests/functional/heat_controller_test.go index ac1816f4..30732c41 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" ) @@ -644,6 +645,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.