From e9ab0bd7acd5ded156b2bf2b1460b2fe78e86ada Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 21 Aug 2025 14:07:28 -0400 Subject: [PATCH 01/23] scaffold out CRD properties for external object storage --- api/v1beta2/cryostat_types.go | 46 ++++++++++++++++++ api/v1beta2/zz_generated.deepcopy.go | 70 ++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/api/v1beta2/cryostat_types.go b/api/v1beta2/cryostat_types.go index 3bab1c31..8f49f05f 100644 --- a/api/v1beta2/cryostat_types.go +++ b/api/v1beta2/cryostat_types.go @@ -100,6 +100,10 @@ type CryostatSpec struct { // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Database Options" DatabaseOptions *DatabaseOptions `json:"databaseOptions,omitempty"` + // Options to configure the Cryostat application's object storage. If not provided, a managed instance will be automatically provisioned. + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Object Storage Options" + ObjectStorageOptions *ObjectStorageOptions `json:"objectStorageOptions,omitempty"` // Options to configure the Cryostat deployments and pods metadata // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Operand metadata" @@ -762,6 +766,48 @@ type DatabaseOptions struct { SecretName *string `json:"secretName,omitempty"` } +// ObjectStorageOptions provides configuration options to the Cryostat application's object storage. +type ObjectStorageOptions struct { + // Name of the secret containing the object storage secret access key. This secret must contain a + // STORAGE_ACCESS_KEY secret which is the object storage secret access key. If using an external S3 provider requiring + // authentication then this must be provided. It is recommended that the secret should be marked as immutable to avoid + // accidental changes to secret's data. + // More details: [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#secret-immutable) + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:io.kubernetes:Secret"} + SecretName *string `json:"secretName,omitempty"` + // Configuration for external object storage providers. + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Object Storage Provider Options" + Provider *ObjectStorageProviderOptions `json:"provider,omitempty"` + + // TODO add configuration to override default bucket names +} + +// ObjectStorageProviderOptions provides configuration options to the Cryostat application's external object storage. +type ObjectStorageProviderOptions struct { + // The complete URL (not including authentication information) to the external object storage provider. + // +optional + URL *string `json:"url,omitempty"` + // Whether path-style access should be used, as opposed to subdomain access. + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Use Path-Style Access",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} + UsePathStyleAccess *bool `json:"usePathStyleAccess,omitempty"` + // The object storage provider region. + // +optional + Region *string `json:"region,omitempty"` + // Whether Cryostat should trust all TLS certificates presented by the external object storage provider. + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="TLS Trust All",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} + TLSTrustAll *bool `json:"tlsTrustAll,omitempty"` + // The strategy Cryostat will use for storing files' metadata. The default 'tagging' strategy stores all metadata as object Tags. + // The 'metadata' strategy stores metadata as object Metadata, which is immutable but allows for more entries than Tags. + // The 'bucket' strategy stores metadata as separate files (ex. JSON object maps) in a dedicated bucket, + // with prefixes to differentiate the kind of object the metadata belongs to. + // +optional + MetadataMode *string `json:"metadataMode,omitempty"` +} + // AgentOptions provides customization for how the operator configures Cryostat Agents. type AgentOptions struct { // Disables hostname verification when Cryostat connects to Agents over TLS. diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index e0e2de6a..6a09c063 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -339,6 +339,11 @@ func (in *CryostatSpec) DeepCopyInto(out *CryostatSpec) { *out = new(DatabaseOptions) (*in).DeepCopyInto(*out) } + if in.ObjectStorageOptions != nil { + in, out := &in.ObjectStorageOptions, &out.ObjectStorageOptions + *out = new(ObjectStorageOptions) + (*in).DeepCopyInto(*out) + } if in.OperandMetadata != nil { in, out := &in.OperandMetadata, &out.OperandMetadata *out = new(OperandMetadata) @@ -595,6 +600,71 @@ func (in *NetworkPolicyConfig) DeepCopy() *NetworkPolicyConfig { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectStorageOptions) DeepCopyInto(out *ObjectStorageOptions) { + *out = *in + if in.SecretName != nil { + in, out := &in.SecretName, &out.SecretName + *out = new(string) + **out = **in + } + if in.Provider != nil { + in, out := &in.Provider, &out.Provider + *out = new(ObjectStorageProviderOptions) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStorageOptions. +func (in *ObjectStorageOptions) DeepCopy() *ObjectStorageOptions { + if in == nil { + return nil + } + out := new(ObjectStorageOptions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectStorageProviderOptions) DeepCopyInto(out *ObjectStorageProviderOptions) { + *out = *in + if in.URL != nil { + in, out := &in.URL, &out.URL + *out = new(string) + **out = **in + } + if in.UsePathStyleAccess != nil { + in, out := &in.UsePathStyleAccess, &out.UsePathStyleAccess + *out = new(bool) + **out = **in + } + if in.Region != nil { + in, out := &in.Region, &out.Region + *out = new(string) + **out = **in + } + if in.TLSTrustAll != nil { + in, out := &in.TLSTrustAll, &out.TLSTrustAll + *out = new(bool) + **out = **in + } + if in.MetadataMode != nil { + in, out := &in.MetadataMode, &out.MetadataMode + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStorageProviderOptions. +func (in *ObjectStorageProviderOptions) DeepCopy() *ObjectStorageProviderOptions { + if in == nil { + return nil + } + out := new(ObjectStorageProviderOptions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OpenShiftSSOConfig) DeepCopyInto(out *OpenShiftSSOConfig) { *out = *in From c0ad10810d6f8eb87895a9c067ddb301ce7e5bfc Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 21 Aug 2025 14:39:46 -0400 Subject: [PATCH 02/23] untested: rough out reconciler implementation for external S3 provider --- api/v1beta2/cryostat_types.go | 12 +-- .../resource_definitions.go | 97 +++++++++++++++---- internal/controllers/reconciler.go | 8 +- 3 files changed, 88 insertions(+), 29 deletions(-) diff --git a/api/v1beta2/cryostat_types.go b/api/v1beta2/cryostat_types.go index 8f49f05f..c6fbd356 100644 --- a/api/v1beta2/cryostat_types.go +++ b/api/v1beta2/cryostat_types.go @@ -769,9 +769,9 @@ type DatabaseOptions struct { // ObjectStorageOptions provides configuration options to the Cryostat application's object storage. type ObjectStorageOptions struct { // Name of the secret containing the object storage secret access key. This secret must contain a - // STORAGE_ACCESS_KEY secret which is the object storage secret access key. If using an external S3 provider requiring - // authentication then this must be provided. It is recommended that the secret should be marked as immutable to avoid - // accidental changes to secret's data. + // ACCESS_KEY secret which is the object storage access key ID, and a SECRET_KEY secret which is the object storage secret access key. + // If using an external S3 provider requiring authentication then this must be provided. + // It is recommended that the secret should be marked as immutable to avoid accidental changes to secret's data. // More details: [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#secret-immutable) // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:io.kubernetes:Secret"} @@ -787,16 +787,14 @@ type ObjectStorageOptions struct { // ObjectStorageProviderOptions provides configuration options to the Cryostat application's external object storage. type ObjectStorageProviderOptions struct { // The complete URL (not including authentication information) to the external object storage provider. - // +optional URL *string `json:"url,omitempty"` - // Whether path-style access should be used, as opposed to subdomain access. + // Whether path-style access should be used, as opposed to subdomain access. Defaults to true for compatibility. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Use Path-Style Access",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} UsePathStyleAccess *bool `json:"usePathStyleAccess,omitempty"` // The object storage provider region. - // +optional Region *string `json:"region,omitempty"` - // Whether Cryostat should trust all TLS certificates presented by the external object storage provider. + // Whether Cryostat should trust all TLS certificates presented by the external object storage provider. Defaults to false. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="TLS Trust All",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} TLSTrustAll *bool `json:"tlsTrustAll,omitempty"` diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index dc87ec41..bd0be3c6 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -1397,8 +1397,12 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag Value: "archivedrecordings", }, { - Name: "QUARKUS_S3_ENDPOINT_OVERRIDE", - Value: specs.StorageURL.String(), + Name: "CRYOSTAT_CONFIG_PATH", + Value: configPath, + }, + { + Name: "CRYOSTAT_TEMPLATE_PATH", + Value: templatesPath, }, { Name: "QUARKUS_S3_SYNC_CLIENT_TLS_KEY_MANAGERS_PROVIDER_TYPE", @@ -1410,26 +1414,74 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag // cryostat's truststore should include the storage certificate, so the S3 client should be able to load it from the system Value: "system-property", }, - { - Name: "QUARKUS_S3_PATH_STYLE_ACCESS", - Value: "true", - }, - { - Name: "QUARKUS_S3_AWS_REGION", - Value: "us-east-1", - }, { Name: "QUARKUS_S3_AWS_CREDENTIALS_TYPE", Value: "static", }, - { - Name: "CRYOSTAT_CONFIG_PATH", - Value: configPath, - }, - { - Name: "CRYOSTAT_TEMPLATE_PATH", - Value: templatesPath, - }, + } + + if cr.Spec.ObjectStorageOptions == nil { + // default environment variable settings for managed/provisioned cryostat-storage instance + envs = append(envs, []corev1.EnvVar{ + { + Name: "QUARKUS_S3_ENDPOINT_OVERRIDE", + Value: specs.StorageURL.String(), + }, + { + Name: "QUARKUS_S3_PATH_STYLE_ACCESS", + Value: "true", + }, + { + Name: "QUARKUS_S3_AWS_REGION", + Value: "us-east-1", + }, + }...) + } else { + if cr.Spec.ObjectStorageOptions.Provider.URL == nil { + // FIXME this is an invalid configuration - we should either return an error here, or there should be a check earlier that can do that + } + if cr.Spec.ObjectStorageOptions.Provider.Region == nil { + // FIXME this is an invalid configuration - we should either return an error here, or there should be a check earlier that can do that + } + envs = append(envs, []corev1.EnvVar{ + { + Name: "QUARKUS_S3_ENDPOINT_OVERRIDE", + Value: *cr.Spec.ObjectStorageOptions.Provider.URL, + }, + { + Name: "QUARKUS_S3_AWS_REGION", + Value: *cr.Spec.ObjectStorageOptions.Provider.Region, + }, + }...) + + usePathStyleAccess := true + if cr.Spec.ObjectStorageOptions.Provider != nil && cr.Spec.ObjectStorageOptions.Provider.UsePathStyleAccess != nil { + usePathStyleAccess = *cr.Spec.ObjectStorageOptions.Provider.UsePathStyleAccess + } + envs = append(envs, corev1.EnvVar{ + Name: "QUARKUS_S3_PATH_STYLE_ACCESS", + Value: strconv.FormatBool(usePathStyleAccess), + }) + + metadataMode := "tagging" + if cr.Spec.ObjectStorageOptions.Provider != nil && cr.Spec.ObjectStorageOptions.Provider.MetadataMode != nil { + metadataMode = *cr.Spec.ObjectStorageOptions.Provider.MetadataMode + } + envs = append(envs, corev1.EnvVar{ + Name: "STORAGE_METADATA_STORAGE_MODE", + Value: metadataMode, + }) + + tlsTrustAll := false + if cr.Spec.ObjectStorageOptions.Provider != nil && cr.Spec.ObjectStorageOptions.Provider.TLSTrustAll != nil { + tlsTrustAll = *cr.Spec.ObjectStorageOptions.Provider.TLSTrustAll + } + if tlsTrustAll { + envs = append(envs, corev1.EnvVar{ + Name: "QUARKUS_S3_SYNC_CLIENT_TLS_TRUST_MANAGERS_PROVIDER_TYPE", + Value: strconv.FormatBool(tlsTrustAll), + }) + } } mounts := []corev1.VolumeMount{ @@ -1463,7 +1515,7 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag }, }) - secretName = cr.Name + "-storage" + secretName = getStorageSecret(cr) envs = append(envs, corev1.EnvVar{ Name: "QUARKUS_S3_AWS_CREDENTIALS_STATIC_PROVIDER_ACCESS_KEY_ID", @@ -2292,3 +2344,10 @@ func getDatabaseSecret(cr *model.CryostatInstance) string { } return cr.Name + "-db" } + +func getStorageSecret(cr *model.CryostatInstance) string { + if cr.Spec.ObjectStorageOptions != nil && cr.Spec.ObjectStorageOptions.SecretName != nil { + return *cr.Spec.ObjectStorageOptions.SecretName + } + return cr.Name + "-storage" +} diff --git a/internal/controllers/reconciler.go b/internal/controllers/reconciler.go index abefc3e7..633038c6 100644 --- a/internal/controllers/reconciler.go +++ b/internal/controllers/reconciler.go @@ -309,9 +309,11 @@ func (r *Reconciler) reconcileCryostat(ctx context.Context, cr *model.CryostatIn return databaseResult, err } - storageResult, err := r.reconcileStorage(ctx, reqLogger, cr, tlsConfig, imageTags, serviceSpecs, *fsGroup) - if err != nil { - return storageResult, err + if cr.Spec.ObjectStorageOptions == nil { + storageResult, err := r.reconcileStorage(ctx, reqLogger, cr, tlsConfig, imageTags, serviceSpecs, *fsGroup) + if err != nil { + return storageResult, err + } } reportsResult, err := r.reconcileReports(ctx, reqLogger, cr, tlsConfig, imageTags, serviceSpecs) From ee06414077afe219c961ad9f3531c5e841f32eb3 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 21 Aug 2025 15:07:41 -0400 Subject: [PATCH 03/23] regenerate --- ...yostat-operator.clusterserviceversion.yaml | 23 ++++++++++- .../operator.cryostat.io_cryostats.yaml | 41 +++++++++++++++++++ .../bases/operator.cryostat.io_cryostats.yaml | 41 +++++++++++++++++++ ...yostat-operator.clusterserviceversion.yaml | 30 ++++++++++++++ 4 files changed, 134 insertions(+), 1 deletion(-) diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 363c73cd..cb67a4f2 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -24,7 +24,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:4.1.0-dev - createdAt: "2025-09-22T20:35:39Z" + createdAt: "2025-09-24T18:47:41Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { @@ -243,6 +243,27 @@ spec: path: networkPolicies.storageConfig.ingressDisabled x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Options to configure the Cryostat application's object storage. If not provided, a managed instance will be automatically provisioned. + displayName: Object Storage Options + path: objectStorageOptions + - description: Configuration for external object storage providers. + displayName: Object Storage Provider Options + path: objectStorageOptions.provider + - description: Whether Cryostat should trust all TLS certificates presented by the external object storage provider. Defaults to false. + displayName: TLS Trust All + path: objectStorageOptions.provider.tlsTrustAll + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Whether path-style access should be used, as opposed to subdomain access. Defaults to true for compatibility. + displayName: Use Path-Style Access + path: objectStorageOptions.provider.usePathStyleAccess + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: 'Name of the secret containing the object storage secret access key. This secret must contain a ACCESS_KEY secret which is the object storage access key ID, and a SECRET_KEY secret which is the object storage secret access key. If using an external S3 provider requiring authentication then this must be provided. It is recommended that the secret should be marked as immutable to avoid accidental changes to secret''s data. More details: [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#secret-immutable)' + displayName: Secret Name + path: objectStorageOptions.secretName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Secret - description: Options to configure the Cryostat deployments and pods metadata displayName: Operand metadata path: operandMetadata diff --git a/bundle/manifests/operator.cryostat.io_cryostats.yaml b/bundle/manifests/operator.cryostat.io_cryostats.yaml index e05bcf55..00fe4550 100644 --- a/bundle/manifests/operator.cryostat.io_cryostats.yaml +++ b/bundle/manifests/operator.cryostat.io_cryostats.yaml @@ -5958,6 +5958,47 @@ spec: type: boolean type: object type: object + objectStorageOptions: + description: Options to configure the Cryostat application's object + storage. If not provided, a managed instance will be automatically + provisioned. + properties: + provider: + description: Configuration for external object storage providers. + properties: + metadataMode: + description: |- + The strategy Cryostat will use for storing files' metadata. The default 'tagging' strategy stores all metadata as object Tags. + The 'metadata' strategy stores metadata as object Metadata, which is immutable but allows for more entries than Tags. + The 'bucket' strategy stores metadata as separate files (ex. JSON object maps) in a dedicated bucket, + with prefixes to differentiate the kind of object the metadata belongs to. + type: string + region: + description: The object storage provider region. + type: string + tlsTrustAll: + description: Whether Cryostat should trust all TLS certificates + presented by the external object storage provider. Defaults + to false. + type: boolean + url: + description: The complete URL (not including authentication + information) to the external object storage provider. + type: string + usePathStyleAccess: + description: Whether path-style access should be used, as + opposed to subdomain access. Defaults to true for compatibility. + type: boolean + type: object + secretName: + description: |- + Name of the secret containing the object storage secret access key. This secret must contain a + ACCESS_KEY secret which is the object storage access key ID, and a SECRET_KEY secret which is the object storage secret access key. + If using an external S3 provider requiring authentication then this must be provided. + It is recommended that the secret should be marked as immutable to avoid accidental changes to secret's data. + More details: [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#secret-immutable) + type: string + type: object operandMetadata: description: Options to configure the Cryostat deployments and pods metadata diff --git a/config/crd/bases/operator.cryostat.io_cryostats.yaml b/config/crd/bases/operator.cryostat.io_cryostats.yaml index 43e9ce0c..9e192e2e 100644 --- a/config/crd/bases/operator.cryostat.io_cryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_cryostats.yaml @@ -5945,6 +5945,47 @@ spec: type: boolean type: object type: object + objectStorageOptions: + description: Options to configure the Cryostat application's object + storage. If not provided, a managed instance will be automatically + provisioned. + properties: + provider: + description: Configuration for external object storage providers. + properties: + metadataMode: + description: |- + The strategy Cryostat will use for storing files' metadata. The default 'tagging' strategy stores all metadata as object Tags. + The 'metadata' strategy stores metadata as object Metadata, which is immutable but allows for more entries than Tags. + The 'bucket' strategy stores metadata as separate files (ex. JSON object maps) in a dedicated bucket, + with prefixes to differentiate the kind of object the metadata belongs to. + type: string + region: + description: The object storage provider region. + type: string + tlsTrustAll: + description: Whether Cryostat should trust all TLS certificates + presented by the external object storage provider. Defaults + to false. + type: boolean + url: + description: The complete URL (not including authentication + information) to the external object storage provider. + type: string + usePathStyleAccess: + description: Whether path-style access should be used, as + opposed to subdomain access. Defaults to true for compatibility. + type: boolean + type: object + secretName: + description: |- + Name of the secret containing the object storage secret access key. This secret must contain a + ACCESS_KEY secret which is the object storage access key ID, and a SECRET_KEY secret which is the object storage secret access key. + If using an external S3 provider requiring authentication then this must be provided. + It is recommended that the secret should be marked as immutable to avoid accidental changes to secret's data. + More details: [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#secret-immutable) + type: string + type: object operandMetadata: description: Options to configure the Cryostat deployments and pods metadata diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index 2c2b75e0..f662bc2c 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -278,6 +278,36 @@ spec: path: networkPolicies.storageConfig.ingressDisabled x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Options to configure the Cryostat application's object storage. + If not provided, a managed instance will be automatically provisioned. + displayName: Object Storage Options + path: objectStorageOptions + - description: Configuration for external object storage providers. + displayName: Object Storage Provider Options + path: objectStorageOptions.provider + - description: Whether Cryostat should trust all TLS certificates presented + by the external object storage provider. Defaults to false. + displayName: TLS Trust All + path: objectStorageOptions.provider.tlsTrustAll + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: Whether path-style access should be used, as opposed to subdomain + access. Defaults to true for compatibility. + displayName: Use Path-Style Access + path: objectStorageOptions.provider.usePathStyleAccess + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: 'Name of the secret containing the object storage secret access + key. This secret must contain a ACCESS_KEY secret which is the object storage + access key ID, and a SECRET_KEY secret which is the object storage secret + access key. If using an external S3 provider requiring authentication then + this must be provided. It is recommended that the secret should be marked + as immutable to avoid accidental changes to secret''s data. More details: + [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#secret-immutable)' + displayName: Secret Name + path: objectStorageOptions.secretName + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Secret - description: Options to configure the Cryostat deployments and pods metadata displayName: Operand metadata path: operandMetadata From d4d2d59625684f875091085f45ea1fd2b49b0098 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 21 Aug 2025 15:18:54 -0400 Subject: [PATCH 04/23] handle error (invalid config) cases --- .../resource_definitions/resource_definitions.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index bd0be3c6..b04c0d32 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -441,8 +441,12 @@ func NewPodForCR(cr *model.CryostatInstance, specs *ServiceSpecs, imageTags *Ima if err != nil { return nil, err } + coreContainer, err := NewCoreContainer(cr, specs, imageTags.CoreImageTag, tls, openshift) + if err != nil { + return nil, err + } containers := []corev1.Container{ - NewCoreContainer(cr, specs, imageTags.CoreImageTag, tls, openshift), + *coreContainer, NewGrafanaContainer(cr, imageTags.GrafanaImageTag, tls), NewJfrDatasourceContainer(cr, imageTags.DatasourceImageTag, specs, tls), *authProxy, @@ -1350,7 +1354,7 @@ func NewCoreContainerResource(cr *model.CryostatInstance) *corev1.ResourceRequir } func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag string, - tls *TLSConfig, openshift bool) corev1.Container { + tls *TLSConfig, openshift bool) (*corev1.Container, error) { configPath := "/opt/cryostat.d/conf.d" templatesPath := "/opt/cryostat.d/templates.d" credentialsPath := "/opt/cryostat.d/credentials.d" @@ -1438,10 +1442,10 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag }...) } else { if cr.Spec.ObjectStorageOptions.Provider.URL == nil { - // FIXME this is an invalid configuration - we should either return an error here, or there should be a check earlier that can do that + return nil, fmt.Errorf("cr.Spec.ObjectStorageOptions was not nil, but cr.Spec.ObjectStorageOptions.Provider.URL was nil") } if cr.Spec.ObjectStorageOptions.Provider.Region == nil { - // FIXME this is an invalid configuration - we should either return an error here, or there should be a check earlier that can do that + return nil, fmt.Errorf("cr.Spec.ObjectStorageOptions was not nil, but cr.Spec.ObjectStorageOptions.Provider.Region was nil") } envs = append(envs, []corev1.EnvVar{ { @@ -1739,7 +1743,7 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag } } - return corev1.Container{ + return &corev1.Container{ Name: cr.Name, Image: imageTag, ImagePullPolicy: common.GetPullPolicy(imageTag), @@ -1760,7 +1764,7 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag FailureThreshold: 18, }, SecurityContext: containerSc, - } + }, nil } func NewGrafanaContainerResource(cr *model.CryostatInstance) *corev1.ResourceRequirements { From ef4085b9ef5c633b9a95558f3655f46af0904fc1 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Mon, 25 Aug 2025 09:17:35 -0400 Subject: [PATCH 05/23] set StorageURL from external S3 CR URL --- internal/controllers/reconciler.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/controllers/reconciler.go b/internal/controllers/reconciler.go index 633038c6..919086f3 100644 --- a/internal/controllers/reconciler.go +++ b/internal/controllers/reconciler.go @@ -314,6 +314,11 @@ func (r *Reconciler) reconcileCryostat(ctx context.Context, cr *model.CryostatIn if err != nil { return storageResult, err } + } else { + serviceSpecs.StorageURL, err = url.Parse(*cr.Spec.ObjectStorageOptions.Provider.URL) + if err != nil { + return reconcile.Result{}, err + } } reportsResult, err := r.reconcileReports(ctx, reqLogger, cr, tlsConfig, imageTags, serviceSpecs) From 163db292c4dbc4c73d65cec5c7690ed48ef61a0a Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Mon, 25 Aug 2025 16:20:12 -0400 Subject: [PATCH 06/23] correct external S3 tlsTrustAll env var value --- .../common/resource_definitions/resource_definitions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index b04c0d32..140e23e0 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -1483,7 +1483,7 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag if tlsTrustAll { envs = append(envs, corev1.EnvVar{ Name: "QUARKUS_S3_SYNC_CLIENT_TLS_TRUST_MANAGERS_PROVIDER_TYPE", - Value: strconv.FormatBool(tlsTrustAll), + Value: "trust-all", }) } } From 5b4668637976d1cbe38537819601f59732962036 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Mon, 25 Aug 2025 16:20:32 -0400 Subject: [PATCH 07/23] add test --- internal/controllers/reconciler_test.go | 65 +++++++++++++++++++++++++ internal/test/resources.go | 41 +++++++++++++++- 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/internal/controllers/reconciler_test.go b/internal/controllers/reconciler_test.go index 79486ebc..5c868783 100644 --- a/internal/controllers/reconciler_test.go +++ b/internal/controllers/reconciler_test.go @@ -958,6 +958,71 @@ func (c *controllerTest) commonTests() { t.checkDeploymentHasTemplates() }) }) + Context("with external S3 object storage configuration", func() { + BeforeEach(func() { + secretName := "external-s3-creds" + t.StorageSecret = t.NewExternalStorageSecret(secretName) + t.objs = append(t.objs, t.NewCryostatWithExternalS3(secretName).Object, t.StorageSecret) + }) + JustBeforeEach(func() { + t.reconcileCryostatFully() + }) + It("Should update the core deployment", func() { + secretOptional := false + t.checkCoreHasEnvironmentVariables([]corev1.EnvVar{ + corev1.EnvVar{ + Name: "QUARKUS_S3_AWS_CREDENTIALS_STATIC_PROVIDER_ACCESS_KEY_ID", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: nil, + ResourceFieldRef: nil, + ConfigMapKeyRef: nil, + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "external-s3-creds", + }, + Key: "ACCESS_KEY", + Optional: &secretOptional, + }, + }, + }, + corev1.EnvVar{ + Name: "QUARKUS_S3_AWS_CREDENTIALS_STATIC_PROVIDER_SECRET_ACCESS_KEY", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: nil, + ResourceFieldRef: nil, + ConfigMapKeyRef: nil, + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "external-s3-creds", + }, + Key: "SECRET_KEY", + Optional: &secretOptional, + }, + }, + }, + corev1.EnvVar{ + Name: "QUARKUS_S3_ENDPOINT_OVERRIDE", + Value: "https://example.com:1234", + }, + corev1.EnvVar{ + Name: "QUARKUS_S3_PATH_STYLE_ACCESS", + Value: "true", + }, + corev1.EnvVar{ + Name: "QUARKUS_S3_AWS_REGION", + Value: "region-east-1", + }, + corev1.EnvVar{ + Name: "QUARKUS_S3_SYNC_CLIENT_TLS_TRUST_MANAGERS_PROVIDER_TYPE", + Value: "trust-all", + }, + corev1.EnvVar{ + Name: "STORAGE_METADATA_STORAGE_MODE", + Value: "tagging", + }, + }) + }) + }) Context("with custom PVC spec overriding all defaults", func() { BeforeEach(func() { t.objs = append(t.objs, t.NewCryostatWithPVCSpec().Object) diff --git a/internal/test/resources.go b/internal/test/resources.go index 1437af3f..2291e56c 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -59,6 +59,7 @@ type TestResources struct { DisableAgentHostnameVerify bool AllowAgentInsecure bool DatabaseSecret *corev1.Secret + StorageSecret *corev1.Secret } func NewTestScheme() *runtime.Scheme { @@ -223,6 +224,26 @@ func (r *TestResources) NewCryostatWithPVCSpec() *model.CryostatInstance { return cr } +func (r *TestResources) NewCryostatWithExternalS3(secretName string) *model.CryostatInstance { + cr := r.NewCryostat() + providerUrl := "https://example.com:1234" + region := "region-east-1" + usePathStyleAccess := true + tlsTrustAll := true + metadataMode := "tagging" + cr.Spec.ObjectStorageOptions = &operatorv1beta2.ObjectStorageOptions{ + SecretName: &secretName, + Provider: &operatorv1beta2.ObjectStorageProviderOptions{ + URL: &providerUrl, + Region: ®ion, + UsePathStyleAccess: &usePathStyleAccess, + TLSTrustAll: &tlsTrustAll, + MetadataMode: &metadataMode, + }, + } + return cr +} + func (r *TestResources) NewCryostatWithPVCSpecLegacy() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.StorageOptions = &operatorv1beta2.StorageConfigurations{ @@ -1764,6 +1785,19 @@ func (r *TestResources) NewCustomDatabaseSecret() *corev1.Secret { } } +func (r *TestResources) NewExternalStorageSecret(name string) *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: r.Namespace, + }, + Data: map[string][]byte{ + "SECRET_KEY": []byte("external-s3-secret"), + "ACCESS_KEY": []byte("external-s3-access"), + }, + } +} + func (r *TestResources) NewStorageSecret() *corev1.Secret { return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -2482,8 +2516,11 @@ func (r *TestResources) NewDatabasePodAnnotations() map[string]string { func (r *TestResources) NewStoragePodAnnotations() map[string]string { annotations := map[string]string{} - secrets := []*corev1.Secret{ - r.NewStorageSecret(), + secrets := []*corev1.Secret{} + if r.StorageSecret != nil { + secrets = append(secrets, r.StorageSecret) + } else { + secrets = append(secrets, r.NewStorageSecret()) } if r.TLS { secrets = append(secrets, From 32163dfcbea978b1974662fb6e0b831c3b54aa35 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Mon, 25 Aug 2025 16:47:45 -0400 Subject: [PATCH 08/23] do not set report sidecar storage auth when presigned transfers are expected to work anonymously --- .../resource_definitions.go | 21 ------------------- internal/test/resources.go | 17 --------------- 2 files changed, 38 deletions(-) diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index 140e23e0..ec40dd9e 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -873,27 +873,6 @@ func NewPodForReports(cr *model.CryostatInstance, imageTags *ImageTags, serviceS mounts := []corev1.VolumeMount{} volumes := []corev1.Volume{} - optional := false - secretName := cr.Name + "-storage" - envs = append(envs, - corev1.EnvVar{ - Name: "CRYOSTAT_STORAGE_AUTH_METHOD", - Value: "Basic", - }, - corev1.EnvVar{ - Name: "CRYOSTAT_STORAGE_AUTH", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: secretName, - }, - Key: "BASIC_AUTH_KEY", - Optional: &optional, - }, - }, - }, - ) - // Configure TLS key/cert if enabled livenessProbeScheme := corev1.URISchemeHTTP if tls != nil { diff --git a/internal/test/resources.go b/internal/test/resources.go index 2291e56c..0cf0c4aa 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -2871,7 +2871,6 @@ func (r *TestResources) NewReportsEnvironmentVariables(resources *corev1.Resourc } } opts := fmt.Sprintf("-XX:+PrintCommandLineFlags -XX:ActiveProcessorCount=%d -Dorg.openjdk.jmc.flightrecorder.parser.singlethreaded=%t", cpus, cpus < 2) - optional := false var storageProtocol string if r.TLS { storageProtocol = "https" @@ -2892,22 +2891,6 @@ func (r *TestResources) NewReportsEnvironmentVariables(resources *corev1.Resourc Name: "CRYOSTAT_STORAGE_BASE_URI", Value: fmt.Sprintf("%s://%s-storage.%s.svc.cluster.local:8333", storageProtocol, r.Name, r.Namespace), }, - { - Name: "CRYOSTAT_STORAGE_AUTH_METHOD", - Value: "Basic", - }, - { - Name: "CRYOSTAT_STORAGE_AUTH", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "cryostat-storage", - }, - Key: "BASIC_AUTH_KEY", - Optional: &optional, - }, - }, - }, } if r.TLS { envs = append(envs, corev1.EnvVar{ From 3b27d2115641483d95179ae9d7d5aa0f8bb51c8d Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Mon, 25 Aug 2025 16:48:06 -0400 Subject: [PATCH 09/23] do not include BASIC_AUTH_KEY in generated storage secret --- internal/controllers/secrets.go | 6 ------ internal/test/resources.go | 5 ++--- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/internal/controllers/secrets.go b/internal/controllers/secrets.go index 9bb55447..92075163 100644 --- a/internal/controllers/secrets.go +++ b/internal/controllers/secrets.go @@ -19,8 +19,6 @@ import ( "errors" "fmt" - b64 "encoding/base64" - "github.com/cryostatio/cryostat-operator/internal/controllers/constants" "github.com/cryostatio/cryostat-operator/internal/controllers/model" corev1 "k8s.io/api/core/v1" @@ -123,9 +121,6 @@ const storageSecretAccessKey = "ACCESS_KEY" // storageSecretUserKey indexes the password within the Cryostat storage Secret const storageSecretPassKey = "SECRET_KEY" -// storageSecretBasicAuthKey indexes the Basic auth credentials (Base64-encoded) within the Cryostat storage Secret -const storageSecretBasicAuthKey = "BASIC_AUTH_KEY" - func (r *Reconciler) reconcileStorageSecret(ctx context.Context, cr *model.CryostatInstance) error { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -145,7 +140,6 @@ func (r *Reconciler) reconcileStorageSecret(ctx context.Context, cr *model.Cryos passwd := r.GenPasswd(32) secret.StringData[storageSecretAccessKey] = accessKey secret.StringData[storageSecretPassKey] = passwd - secret.StringData[storageSecretBasicAuthKey] = b64.StdEncoding.EncodeToString(fmt.Appendf(nil, "%s:%s", accessKey, passwd)) } return nil }) diff --git a/internal/test/resources.go b/internal/test/resources.go index 0cf0c4aa..bd36cecf 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -1805,9 +1805,8 @@ func (r *TestResources) NewStorageSecret() *corev1.Secret { Namespace: r.Namespace, }, Data: map[string][]byte{ - "SECRET_KEY": []byte("object_storage"), - "ACCESS_KEY": []byte("cryostat"), - "BASIC_AUTH_KEY": []byte("Y3J5b3N0YXQ6b2JqZWN0X3N0b3JhZ2U="), + "SECRET_KEY": []byte("object_storage"), + "ACCESS_KEY": []byte("cryostat"), }, } } From 77c9da603176ce640d660247cd4f6bba1d83cbb1 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Mon, 25 Aug 2025 17:33:26 -0400 Subject: [PATCH 10/23] fixup! do not set report sidecar storage auth when presigned transfers are expected to work anonymously --- internal/test/resources.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/test/resources.go b/internal/test/resources.go index bd36cecf..e3ba7ec2 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -2536,9 +2536,7 @@ func (r *TestResources) NewStoragePodAnnotations() map[string]string { func (r *TestResources) NewReportsPodAnnotations() map[string]string { annotations := map[string]string{} - secrets := []*corev1.Secret{ - r.NewStorageSecret(), - } + secrets := []*corev1.Secret{} if r.TLS { secrets = append(secrets, r.NewCertSecret(r.NewReportsCert()), From da34d9b2b3f8ffffdb3bf8e7cf7491239710c6b9 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Wed, 24 Sep 2025 15:06:11 -0400 Subject: [PATCH 11/23] Apply suggestions Co-authored-by: Elliott Baron Signed-off-by: Andrew Azores --- api/v1beta2/cryostat_types.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/v1beta2/cryostat_types.go b/api/v1beta2/cryostat_types.go index c6fbd356..a43747d1 100644 --- a/api/v1beta2/cryostat_types.go +++ b/api/v1beta2/cryostat_types.go @@ -787,12 +787,14 @@ type ObjectStorageOptions struct { // ObjectStorageProviderOptions provides configuration options to the Cryostat application's external object storage. type ObjectStorageProviderOptions struct { // The complete URL (not including authentication information) to the external object storage provider. + // +operator-sdk:csv:customresourcedefinitions:type=spec URL *string `json:"url,omitempty"` // Whether path-style access should be used, as opposed to subdomain access. Defaults to true for compatibility. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Use Path-Style Access",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} UsePathStyleAccess *bool `json:"usePathStyleAccess,omitempty"` // The object storage provider region. + // +operator-sdk:csv:customresourcedefinitions:type=spec Region *string `json:"region,omitempty"` // Whether Cryostat should trust all TLS certificates presented by the external object storage provider. Defaults to false. // +optional @@ -803,6 +805,8 @@ type ObjectStorageProviderOptions struct { // The 'bucket' strategy stores metadata as separate files (ex. JSON object maps) in a dedicated bucket, // with prefixes to differentiate the kind of object the metadata belongs to. // +optional + // +kubebuilder:validation:Enum=tagging;metadata;bucket + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:select:tagging","urn:alm:descriptor:com.tectonic.ui:select:metadata","urn:alm:descriptor:com.tectonic.ui:select:bucket"} MetadataMode *string `json:"metadataMode,omitempty"` } From b5cd2555aa64d6772b9483b6267ecbcd5b86874a Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Wed, 24 Sep 2025 15:23:42 -0400 Subject: [PATCH 12/23] only apply Storage TLS secret configuration if we're actually using it - don't apply it (rely on system truststores) if we're using external S3 --- .../resource_definitions.go | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index ec40dd9e..015d3e7c 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -914,14 +914,23 @@ func NewPodForReports(cr *model.CryostatInstance, imageTags *ImageTags, serviceS Name: "QUARKUS_HTTP_INSECURE_REQUESTS", Value: "disabled", }, - { - Name: "CRYOSTAT_STORAGE_TLS_CA_PATH", - Value: path.Join(SecretMountPrefix, tls.StorageSecret, "ca.crt"), - }, - { - Name: "CRYOSTAT_STORAGE_TLS_CERT_PATH", - Value: path.Join(SecretMountPrefix, tls.StorageSecret, "tls.crt"), - }, + } + + // if we are deploying our own managed storage container with a TLS cert that we issued for it, + // configure that here. Otherwise if we are configured to talk to an external object storage + // provider, assume that it is using a well-known certificate signed by a root trust. + // TODO allow additional configuration via the CR to configure TLS for external providers + if cr.Spec.ObjectStorageOptions == nil || cr.Spec.ObjectStorageOptions.Provider == nil { + tlsEnvs = append(tlsEnvs, + corev1.EnvVar{ + Name: "CRYOSTAT_STORAGE_TLS_CA_PATH", + Value: path.Join(SecretMountPrefix, tls.StorageSecret, "ca.crt"), + }, + corev1.EnvVar{ + Name: "CRYOSTAT_STORAGE_TLS_CERT_PATH", + Value: path.Join(SecretMountPrefix, tls.StorageSecret, "tls.crt"), + }, + ) } tlsConfigName := "https" @@ -935,7 +944,6 @@ func NewPodForReports(cr *model.CryostatInstance, imageTags *ImageTags, serviceS tlsConfigName, path.Join(SecretMountPrefix, tls.ReportsSecret, corev1.TLSPrivateKeyKey), ) - tlsSecretMount := corev1.VolumeMount{ Name: "reports-tls-secret", MountPath: path.Join(SecretMountPrefix, tls.ReportsSecret), From 900149a0c09f9e240afb6a78b1ce29674e78b5b1 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Wed, 24 Sep 2025 15:23:50 -0400 Subject: [PATCH 13/23] update --- config/crd/bases/operator.cryostat.io_cryostats.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/crd/bases/operator.cryostat.io_cryostats.yaml b/config/crd/bases/operator.cryostat.io_cryostats.yaml index 9e192e2e..7ed69e82 100644 --- a/config/crd/bases/operator.cryostat.io_cryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_cryostats.yaml @@ -5959,6 +5959,10 @@ spec: The 'metadata' strategy stores metadata as object Metadata, which is immutable but allows for more entries than Tags. The 'bucket' strategy stores metadata as separate files (ex. JSON object maps) in a dedicated bucket, with prefixes to differentiate the kind of object the metadata belongs to. + enum: + - tagging + - metadata + - bucket type: string region: description: The object storage provider region. From 17f48da828b9446f0f4445266cb25479b6d43b08 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 2 Oct 2025 14:49:51 -0400 Subject: [PATCH 14/23] add CRD spec for storage bucket names --- api/v1beta2/cryostat_types.go | 37 ++++++++++++- api/v1beta2/zz_generated.deepcopy.go | 55 +++++++++++++++++++ .../bases/operator.cryostat.io_cryostats.yaml | 34 ++++++++++++ 3 files changed, 124 insertions(+), 2 deletions(-) diff --git a/api/v1beta2/cryostat_types.go b/api/v1beta2/cryostat_types.go index a43747d1..36dd2469 100644 --- a/api/v1beta2/cryostat_types.go +++ b/api/v1beta2/cryostat_types.go @@ -780,8 +780,10 @@ type ObjectStorageOptions struct { // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Object Storage Provider Options" Provider *ObjectStorageProviderOptions `json:"provider,omitempty"` - - // TODO add configuration to override default bucket names + // Configuration for object storage buckets. + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Storage Bucket Names" + StorageBucketNameOptions *StorageBucketNameOptions `json:"storageBucketNameOptions,omitempty"` } // ObjectStorageProviderOptions provides configuration options to the Cryostat application's external object storage. @@ -810,6 +812,37 @@ type ObjectStorageProviderOptions struct { MetadataMode *string `json:"metadataMode,omitempty"` } +type StorageBucketNameOptions struct { + // The name of the bucket used to store Archived JFR files. + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + ArchivedRecordings *string `json:"archivedRecordings,omitempty"` + // The name of the bucket used to store a cache of Automated Analysis reports attached to Archived JFR files. + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + ArchivedReports *string `json:"archivedReports,omitempty"` + // The name of the bucket used to store custom Event Templates. + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + EventTemplates *string `json:"eventTemplates,omitempty"` + // The name of the bucket used to store JMC Agent Probe templates. + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + JMCAgentProbeTemplates *string `json:"jmcAgentProbeTemplates,omitempty"` + // The name of the bucket used to store JVM heap dumps. + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + HeapDumps *string `json:"heapDumps,omitempty"` + // The name of the bucket used to storage JVM thread dumps. + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + ThreadDumps *string `json:"threadDumps,omitempty"` + // The name of the bucket used to storage metadata for other objects (ex. archived recordings). This is only used if the .spec.objectStorageOptions.provider.metadataMode is set to 'bucket'. + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + Metadata *string `json:"metadata,omitempty"` +} + // AgentOptions provides customization for how the operator configures Cryostat Agents. type AgentOptions struct { // Disables hostname verification when Cryostat connects to Agents over TLS. diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 6a09c063..43800753 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -613,6 +613,11 @@ func (in *ObjectStorageOptions) DeepCopyInto(out *ObjectStorageOptions) { *out = new(ObjectStorageProviderOptions) (*in).DeepCopyInto(*out) } + if in.StorageBucketNameOptions != nil { + in, out := &in.StorageBucketNameOptions, &out.StorageBucketNameOptions + *out = new(StorageBucketNameOptions) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectStorageOptions. @@ -1039,6 +1044,56 @@ func (in *ServiceConfigList) DeepCopy() *ServiceConfigList { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StorageBucketNameOptions) DeepCopyInto(out *StorageBucketNameOptions) { + *out = *in + if in.ArchivedRecordings != nil { + in, out := &in.ArchivedRecordings, &out.ArchivedRecordings + *out = new(string) + **out = **in + } + if in.ArchivedReports != nil { + in, out := &in.ArchivedReports, &out.ArchivedReports + *out = new(string) + **out = **in + } + if in.EventTemplates != nil { + in, out := &in.EventTemplates, &out.EventTemplates + *out = new(string) + **out = **in + } + if in.JMCAgentProbeTemplates != nil { + in, out := &in.JMCAgentProbeTemplates, &out.JMCAgentProbeTemplates + *out = new(string) + **out = **in + } + if in.HeapDumps != nil { + in, out := &in.HeapDumps, &out.HeapDumps + *out = new(string) + **out = **in + } + if in.ThreadDumps != nil { + in, out := &in.ThreadDumps, &out.ThreadDumps + *out = new(string) + **out = **in + } + if in.Metadata != nil { + in, out := &in.Metadata, &out.Metadata + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageBucketNameOptions. +func (in *StorageBucketNameOptions) DeepCopy() *StorageBucketNameOptions { + if in == nil { + return nil + } + out := new(StorageBucketNameOptions) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StorageConfiguration) DeepCopyInto(out *StorageConfiguration) { *out = *in diff --git a/config/crd/bases/operator.cryostat.io_cryostats.yaml b/config/crd/bases/operator.cryostat.io_cryostats.yaml index 7ed69e82..1987387d 100644 --- a/config/crd/bases/operator.cryostat.io_cryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_cryostats.yaml @@ -5989,6 +5989,40 @@ spec: It is recommended that the secret should be marked as immutable to avoid accidental changes to secret's data. More details: [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#secret-immutable) type: string + storageBucketNameOptions: + description: Configuration for object storage buckets. + properties: + archivedRecordings: + description: The name of the bucket used to store Archived + JFR files. + type: string + archivedReports: + description: The name of the bucket used to store a cache + of Automated Analysis reports attached to Archived JFR files. + type: string + eventTemplates: + description: The name of the bucket used to store custom Event + Templates. + type: string + heapDumps: + description: The name of the bucket used to store JVM heap + dumps. + type: string + jmcAgentProbeTemplates: + description: The name of the bucket used to store JMC Agent + Probe templates. + type: string + metadata: + description: The name of the bucket used to storage metadata + for other objects (ex. archived recordings). This is only + used if the .spec.objectStorageOptions.provider.metadataMode + is set to 'bucket'. + type: string + threadDumps: + description: The name of the bucket used to storage JVM thread + dumps. + type: string + type: object type: object operandMetadata: description: Options to configure the Cryostat deployments and pods From 0b455e3aa53d1a87ca84b3da733420b40d6b9628 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 2 Oct 2025 14:52:27 -0400 Subject: [PATCH 15/23] include heapdumps and threaddumps buckets in cryostat-storage precreate list --- .../common/resource_definitions/resource_definitions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index 015d3e7c..723b4b01 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -1850,7 +1850,7 @@ func NewStorageContainer(cr *model.CryostatInstance, imageTag string, tls *TLSCo envs := []corev1.EnvVar{ { Name: "CRYOSTAT_BUCKETS", - Value: "archivedrecordings,archivedreports,eventtemplates,probes", + Value: "archivedrecordings,archivedreports,eventtemplates,probes,heapdumps,threaddumps", }, { Name: "CRYOSTAT_ACCESS_KEY", From 93385428e52385e4a2ab2706a1b86bf2794b58ac Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 2 Oct 2025 15:00:50 -0400 Subject: [PATCH 16/23] implement handling for customized storage bucket names --- .../resource_definitions.go | 46 +++++++++++++++++-- internal/test/resources.go | 4 -- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index 723b4b01..61f05e2d 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -1383,10 +1383,6 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag Name: "QUARKUS_DATASOURCE_USERNAME", Value: "cryostat", }, - { - Name: "STORAGE_BUCKETS_ARCHIVE_NAME", - Value: "archivedrecordings", - }, { Name: "CRYOSTAT_CONFIG_PATH", Value: configPath, @@ -1463,6 +1459,45 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag Value: metadataMode, }) + if cr.Spec.ObjectStorageOptions.StorageBucketNameOptions != nil { + if cr.Spec.ObjectStorageOptions.StorageBucketNameOptions.ArchivedRecordings != nil { + envs = append(envs, corev1.EnvVar{ + Name: "STORAGE_BUCKETS_ARCHIVES_NAME", + Value: *cr.Spec.ObjectStorageOptions.StorageBucketNameOptions.ArchivedRecordings, + }) + } + if cr.Spec.ObjectStorageOptions.StorageBucketNameOptions.EventTemplates != nil { + envs = append(envs, corev1.EnvVar{ + Name: "STORAGE_BUCKETS_EVENT_TEMPLATES_NAME", + Value: *cr.Spec.ObjectStorageOptions.StorageBucketNameOptions.EventTemplates, + }) + } + if cr.Spec.ObjectStorageOptions.StorageBucketNameOptions.JMCAgentProbeTemplates != nil { + envs = append(envs, corev1.EnvVar{ + Name: "STORAGE_BUCKETS_PROBE_TEMPLATES_NAME", + Value: *cr.Spec.ObjectStorageOptions.StorageBucketNameOptions.JMCAgentProbeTemplates, + }) + } + if cr.Spec.ObjectStorageOptions.StorageBucketNameOptions.HeapDumps != nil { + envs = append(envs, corev1.EnvVar{ + Name: "STORAGE_BUCKETS_HEAP_DUMPS_NAME", + Value: *cr.Spec.ObjectStorageOptions.StorageBucketNameOptions.HeapDumps, + }) + } + if cr.Spec.ObjectStorageOptions.StorageBucketNameOptions.ThreadDumps != nil { + envs = append(envs, corev1.EnvVar{ + Name: "STORAGE_BUCKETS_THREAD_DUMPS_NAME", + Value: *cr.Spec.ObjectStorageOptions.StorageBucketNameOptions.ThreadDumps, + }) + } + if cr.Spec.ObjectStorageOptions.StorageBucketNameOptions.Metadata != nil { + envs = append(envs, corev1.EnvVar{ + Name: "STORAGE_BUCKETS_METADATA_NAME", + Value: *cr.Spec.ObjectStorageOptions.StorageBucketNameOptions.Metadata, + }) + } + } + tlsTrustAll := false if cr.Spec.ObjectStorageOptions.Provider != nil && cr.Spec.ObjectStorageOptions.Provider.TLSTrustAll != nil { tlsTrustAll = *cr.Spec.ObjectStorageOptions.Provider.TLSTrustAll @@ -1849,7 +1884,8 @@ func NewStorageContainer(cr *model.CryostatInstance, imageTag string, tls *TLSCo var containerSc *corev1.SecurityContext envs := []corev1.EnvVar{ { - Name: "CRYOSTAT_BUCKETS", + Name: "CRYOSTAT_BUCKETS", + // TODO use cr.Spec.ObjectStorageOptions.StorageBucketNameOptions if specified, with the list below as default values Value: "archivedrecordings,archivedreports,eventtemplates,probes,heapdumps,threaddumps", }, { diff --git a/internal/test/resources.go b/internal/test/resources.go index e3ba7ec2..8f88458b 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -2623,10 +2623,6 @@ func (r *TestResources) NewCoreEnvironmentVariables(reportsUrl string, ingress b Name: "QUARKUS_DATASOURCE_USERNAME", Value: "cryostat", }, - { - Name: "STORAGE_BUCKETS_ARCHIVE_NAME", - Value: "archivedrecordings", - }, { Name: "QUARKUS_S3_ENDPOINT_OVERRIDE", Value: fmt.Sprintf("%s://%s-storage.%s.svc.cluster.local:%d", storageProtocol, r.Name, r.Namespace, storagePort), From 4fe0ff4458fd8a174a8fbeb069d1dff2094a9fe9 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 2 Oct 2025 15:02:32 -0400 Subject: [PATCH 17/23] fixup! include heapdumps and threaddumps buckets in cryostat-storage precreate list --- internal/test/resources.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/test/resources.go b/internal/test/resources.go index 8f88458b..dba96bac 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -2913,7 +2913,7 @@ func (r *TestResources) NewStorageEnvironmentVariables() []corev1.EnvVar { envs := []corev1.EnvVar{ { Name: "CRYOSTAT_BUCKETS", - Value: "archivedrecordings,archivedreports,eventtemplates,probes", + Value: "archivedrecordings,archivedreports,eventtemplates,probes,heapdumps,threaddumps", }, { Name: "CRYOSTAT_ACCESS_KEY", From f314472fbdeb7ceefc32dc16c2c71084de5a4f71 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 2 Oct 2025 15:15:17 -0400 Subject: [PATCH 18/23] fixup! implement handling for customized storage bucket names --- .../resource_definitions.go | 6 ++ internal/controllers/reconciler_test.go | 56 ++++++++++++++++--- internal/test/resources.go | 30 ++++++++++ 3 files changed, 85 insertions(+), 7 deletions(-) diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index 61f05e2d..c9cb6993 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -1466,6 +1466,12 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag Value: *cr.Spec.ObjectStorageOptions.StorageBucketNameOptions.ArchivedRecordings, }) } + if cr.Spec.ObjectStorageOptions.StorageBucketNameOptions.ArchivedReports != nil { + envs = append(envs, corev1.EnvVar{ + Name: "CRYOSTAT_SERVICES_REPORTS_STORAGE_CACHE_NAME", + Value: *cr.Spec.ObjectStorageOptions.StorageBucketNameOptions.ArchivedReports, + }) + } if cr.Spec.ObjectStorageOptions.StorageBucketNameOptions.EventTemplates != nil { envs = append(envs, corev1.EnvVar{ Name: "STORAGE_BUCKETS_EVENT_TEMPLATES_NAME", diff --git a/internal/controllers/reconciler_test.go b/internal/controllers/reconciler_test.go index 5c868783..941e6fb9 100644 --- a/internal/controllers/reconciler_test.go +++ b/internal/controllers/reconciler_test.go @@ -970,7 +970,7 @@ func (c *controllerTest) commonTests() { It("Should update the core deployment", func() { secretOptional := false t.checkCoreHasEnvironmentVariables([]corev1.EnvVar{ - corev1.EnvVar{ + { Name: "QUARKUS_S3_AWS_CREDENTIALS_STATIC_PROVIDER_ACCESS_KEY_ID", ValueFrom: &corev1.EnvVarSource{ FieldRef: nil, @@ -985,7 +985,7 @@ func (c *controllerTest) commonTests() { }, }, }, - corev1.EnvVar{ + { Name: "QUARKUS_S3_AWS_CREDENTIALS_STATIC_PROVIDER_SECRET_ACCESS_KEY", ValueFrom: &corev1.EnvVarSource{ FieldRef: nil, @@ -1000,29 +1000,71 @@ func (c *controllerTest) commonTests() { }, }, }, - corev1.EnvVar{ + { Name: "QUARKUS_S3_ENDPOINT_OVERRIDE", Value: "https://example.com:1234", }, - corev1.EnvVar{ + { Name: "QUARKUS_S3_PATH_STYLE_ACCESS", Value: "true", }, - corev1.EnvVar{ + { Name: "QUARKUS_S3_AWS_REGION", Value: "region-east-1", }, - corev1.EnvVar{ + { Name: "QUARKUS_S3_SYNC_CLIENT_TLS_TRUST_MANAGERS_PROVIDER_TYPE", Value: "trust-all", }, - corev1.EnvVar{ + { Name: "STORAGE_METADATA_STORAGE_MODE", Value: "tagging", }, }) }) }) + Context("with S3 storage bucket names configuration", func() { + BeforeEach(func() { + secretName := "external-s3-creds" + t.StorageSecret = t.NewExternalStorageSecret(secretName) + t.objs = append(t.objs, t.NewCryostatWithCustomizedStorageBucketNames().Object, t.StorageSecret) + }) + JustBeforeEach(func() { + t.reconcileCryostatFully() + }) + It("Should update the core deployment", func() { + t.checkCoreHasEnvironmentVariables([]corev1.EnvVar{ + { + Name: "STORAGE_BUCKETS_ARCHIVES_NAME", + Value: "a", + }, + { + Name: "CRYOSTAT_SERVICES_REPORTS_STORAGE_CACHE_NAME", + Value: "b", + }, + { + Name: "STORAGE_BUCKETS_EVENT_TEMPLATES_NAME", + Value: "c", + }, + { + Name: "STORAGE_BUCKETS_PROBE_TEMPLATES_NAME", + Value: "d", + }, + { + Name: "STORAGE_BUCKETS_HEAP_DUMPS_NAME", + Value: "e", + }, + { + Name: "STORAGE_BUCKETS_THREAD_DUMPS_NAME", + Value: "f", + }, + { + Name: "STORAGE_BUCKETS_METADATA_NAME", + Value: "z", + }, + }) + }) + }) Context("with custom PVC spec overriding all defaults", func() { BeforeEach(func() { t.objs = append(t.objs, t.NewCryostatWithPVCSpec().Object) diff --git a/internal/test/resources.go b/internal/test/resources.go index dba96bac..6c693eba 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -244,6 +244,36 @@ func (r *TestResources) NewCryostatWithExternalS3(secretName string) *model.Cryo return cr } +func (r *TestResources) NewCryostatWithCustomizedStorageBucketNames() *model.CryostatInstance { + cr := r.NewCryostat() + providerUrl := "https://example.com:1234" + region := "region-east-1" + + archivedRecordings := "a" + archivedReports := "b" + eventTemplates := "c" + probeTemplates := "d" + heapDumps := "e" + threadDumps := "f" + metadata := "z" + cr.Spec.ObjectStorageOptions = &operatorv1beta2.ObjectStorageOptions{ + Provider: &operatorv1beta2.ObjectStorageProviderOptions{ + URL: &providerUrl, + Region: ®ion, + }, + StorageBucketNameOptions: &operatorv1beta2.StorageBucketNameOptions{ + ArchivedRecordings: &archivedRecordings, + ArchivedReports: &archivedReports, + EventTemplates: &eventTemplates, + JMCAgentProbeTemplates: &probeTemplates, + HeapDumps: &heapDumps, + ThreadDumps: &threadDumps, + Metadata: &metadata, + }, + } + return cr +} + func (r *TestResources) NewCryostatWithPVCSpecLegacy() *model.CryostatInstance { cr := r.NewCryostat() cr.Spec.StorageOptions = &operatorv1beta2.StorageConfigurations{ From 3ccaed6e6a8936d7a0f060621834f3af14e8c22a Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 2 Oct 2025 16:18:27 -0400 Subject: [PATCH 19/23] remove now-unused BASE_URI env var --- .../resource_definitions.go | 8 -------- internal/test/resources.go | 18 ------------------ 2 files changed, 26 deletions(-) diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index c9cb6993..5f308a4c 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -865,10 +865,6 @@ func NewPodForReports(cr *model.CryostatInstance, imageTags *ImageTags, serviceS Name: "QUARKUS_HTTP_HOST", Value: "0.0.0.0", }, - { - Name: "CRYOSTAT_STORAGE_BASE_URI", - Value: serviceSpecs.StorageURL.String(), - }, } mounts := []corev1.VolumeMount{} volumes := []corev1.Volume{} @@ -2157,10 +2153,6 @@ func NewJfrDatasourceContainer(cr *model.CryostatInstance, imageTag string, serv Name: "QUARKUS_HTTP_PORT", Value: strconv.Itoa(int(constants.DatasourceContainerPort)), }, - { - Name: "CRYOSTAT_STORAGE_BASE_URI", - Value: serviceSpecs.StorageURL.String(), - }, } mounts := []corev1.VolumeMount{} diff --git a/internal/test/resources.go b/internal/test/resources.go index 6c693eba..dd210542 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -2851,12 +2851,6 @@ func (r *TestResources) NewGrafanaEnvironmentVariables() []corev1.EnvVar { } func (r *TestResources) NewDatasourceEnvironmentVariables() []corev1.EnvVar { - var storageProtocol string - if r.TLS { - storageProtocol = "https" - } else { - storageProtocol = "http" - } envs := []corev1.EnvVar{ { Name: "QUARKUS_HTTP_HOST", @@ -2866,10 +2860,6 @@ func (r *TestResources) NewDatasourceEnvironmentVariables() []corev1.EnvVar { Name: "QUARKUS_HTTP_PORT", Value: "8989", }, - { - Name: "CRYOSTAT_STORAGE_BASE_URI", - Value: fmt.Sprintf("%s://%s-storage.%s.svc.cluster.local:8333", storageProtocol, r.Name, r.Namespace), - }, } if r.TLS { envs = append(envs, @@ -2894,12 +2884,8 @@ func (r *TestResources) NewReportsEnvironmentVariables(resources *corev1.Resourc } } opts := fmt.Sprintf("-XX:+PrintCommandLineFlags -XX:ActiveProcessorCount=%d -Dorg.openjdk.jmc.flightrecorder.parser.singlethreaded=%t", cpus, cpus < 2) - var storageProtocol string if r.TLS { - storageProtocol = "https" opts += " -Dquarkus.http.tls-configuration-name=https -Dquarkus.tls.https.reload-period=1h -Dquarkus.tls.https.key-store.pem.0.cert=/var/run/secrets/operator.cryostat.io/cryostat-reports-tls/tls.crt -Dquarkus.tls.https.key-store.pem.0.key=/var/run/secrets/operator.cryostat.io/cryostat-reports-tls/tls.key" - } else { - storageProtocol = "http" } envs := []corev1.EnvVar{ { @@ -2910,10 +2896,6 @@ func (r *TestResources) NewReportsEnvironmentVariables(resources *corev1.Resourc Name: "JAVA_OPTS_APPEND", Value: opts, }, - { - Name: "CRYOSTAT_STORAGE_BASE_URI", - Value: fmt.Sprintf("%s://%s-storage.%s.svc.cluster.local:8333", storageProtocol, r.Name, r.Namespace), - }, } if r.TLS { envs = append(envs, corev1.EnvVar{ From b38accbfcd4a1bdc61314ae0c8b44a5b28597154 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 2 Oct 2025 16:31:56 -0400 Subject: [PATCH 20/23] only apply Storage TLS secret configuration to jfr-datasource if using a managed cryostat-storage - don't apply it when using external S3 --- .../resource_definitions.go | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index 5f308a4c..f2f191b1 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -2164,16 +2164,23 @@ func NewJfrDatasourceContainer(cr *model.CryostatInstance, imageTag string, serv ReadOnly: true, } mounts = append(mounts, tlsSecretMount) - envs = append(envs, - corev1.EnvVar{ - Name: "CRYOSTAT_STORAGE_TLS_CA_PATH", - Value: path.Join(SecretMountPrefix, tls.StorageSecret, "s3", "ca.crt"), - }, - corev1.EnvVar{ - Name: "CRYOSTAT_STORAGE_TLS_CERT_PATH", - Value: path.Join(SecretMountPrefix, tls.StorageSecret, "s3", "tls.crt"), - }, - ) + + // if we are deploying our own managed storage container with a TLS cert that we issued for it, + // configure that here. Otherwise if we are configured to talk to an external object storage + // provider, assume that it is using a well-known certificate signed by a root trust. + // TODO allow additional configuration via the CR to configure TLS for external providers + if cr.Spec.ObjectStorageOptions == nil || cr.Spec.ObjectStorageOptions.Provider == nil { + envs = append(envs, + corev1.EnvVar{ + Name: "CRYOSTAT_STORAGE_TLS_CA_PATH", + Value: path.Join(SecretMountPrefix, tls.StorageSecret, "s3", "ca.crt"), + }, + corev1.EnvVar{ + Name: "CRYOSTAT_STORAGE_TLS_CERT_PATH", + Value: path.Join(SecretMountPrefix, tls.StorageSecret, "s3", "tls.crt"), + }, + ) + } } return corev1.Container{ From 824638c3857c99191b4bcd62845745aa827d44b9 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 3 Oct 2025 13:11:40 -0400 Subject: [PATCH 21/23] clean up unused resources when using external storage --- internal/controllers/networkpolicy.go | 10 ++++++--- internal/controllers/pvc.go | 9 ++++++++ internal/controllers/reconciler.go | 31 ++++++++++++++++++--------- internal/controllers/services.go | 4 ++++ 4 files changed, 41 insertions(+), 13 deletions(-) diff --git a/internal/controllers/networkpolicy.go b/internal/controllers/networkpolicy.go index c12d24eb..c85800bc 100644 --- a/internal/controllers/networkpolicy.go +++ b/internal/controllers/networkpolicy.go @@ -216,7 +216,8 @@ func (r *Reconciler) reconcileDatabaseNetworkPolicy(ctx context.Context, cr *mod }, } allDisabled := cr.Spec.NetworkPolicies != nil && cr.Spec.NetworkPolicies.DatabaseConfig != nil && cr.Spec.NetworkPolicies.DatabaseConfig.Disabled != nil && *cr.Spec.NetworkPolicies.DatabaseConfig.Disabled - if allDisabled || (cr.Spec.NetworkPolicies != nil && cr.Spec.NetworkPolicies.DatabaseConfig != nil && cr.Spec.NetworkPolicies.DatabaseConfig.IngressDisabled != nil && *cr.Spec.NetworkPolicies.DatabaseConfig.IngressDisabled) { + ingressDisabled := cr.Spec.NetworkPolicies != nil && cr.Spec.NetworkPolicies.DatabaseConfig != nil && cr.Spec.NetworkPolicies.DatabaseConfig.IngressDisabled != nil && *cr.Spec.NetworkPolicies.DatabaseConfig.IngressDisabled + if allDisabled || ingressDisabled { return r.deletePolicy(ctx, ingressPolicy) } @@ -256,7 +257,9 @@ func (r *Reconciler) reconcileStorageNetworkPolicy(ctx context.Context, cr *mode }, } allDisabled := cr.Spec.NetworkPolicies != nil && cr.Spec.NetworkPolicies.StorageConfig != nil && cr.Spec.NetworkPolicies.StorageConfig.Disabled != nil && *cr.Spec.NetworkPolicies.StorageConfig.Disabled - if allDisabled || (cr.Spec.NetworkPolicies != nil && cr.Spec.NetworkPolicies.StorageConfig != nil && cr.Spec.NetworkPolicies.StorageConfig.IngressDisabled != nil && *cr.Spec.NetworkPolicies.StorageConfig.IngressDisabled) { + deployManagedStorage := cr.Spec.ObjectStorageOptions == nil || cr.Spec.ObjectStorageOptions.Provider == nil + ingressDisabled := cr.Spec.NetworkPolicies != nil && cr.Spec.NetworkPolicies.StorageConfig != nil && cr.Spec.NetworkPolicies.StorageConfig.IngressDisabled != nil && *cr.Spec.NetworkPolicies.StorageConfig.IngressDisabled + if allDisabled || !deployManagedStorage || ingressDisabled { return r.deletePolicy(ctx, ingressPolicy) } @@ -302,7 +305,8 @@ func (r *Reconciler) reconcileReportsNetworkPolicy(ctx context.Context, cr *mode }, } allDisabled := cr.Spec.NetworkPolicies != nil && cr.Spec.NetworkPolicies.ReportsConfig != nil && cr.Spec.NetworkPolicies.ReportsConfig.Disabled != nil && *cr.Spec.NetworkPolicies.ReportsConfig.Disabled - if allDisabled || (cr.Spec.NetworkPolicies != nil && cr.Spec.NetworkPolicies.ReportsConfig != nil && cr.Spec.NetworkPolicies.ReportsConfig.IngressDisabled != nil && *cr.Spec.NetworkPolicies.ReportsConfig.IngressDisabled) { + ingressDisabled := cr.Spec.NetworkPolicies != nil && cr.Spec.NetworkPolicies.ReportsConfig != nil && cr.Spec.NetworkPolicies.ReportsConfig.IngressDisabled != nil && *cr.Spec.NetworkPolicies.ReportsConfig.IngressDisabled + if allDisabled || ingressDisabled { return r.deletePolicy(ctx, ingressPolicy) } diff --git a/internal/controllers/pvc.go b/internal/controllers/pvc.go index be410e2d..4034b28a 100644 --- a/internal/controllers/pvc.go +++ b/internal/controllers/pvc.go @@ -94,6 +94,15 @@ func (r *Reconciler) reconcileStoragePVC(ctx context.Context, cr *model.Cryostat cfg = (*operatorv1beta2.StorageConfiguration)(&cr.Spec.StorageOptions.LegacyStorageConfiguration) } } + deployManagedStorage := cr.Spec.ObjectStorageOptions == nil || cr.Spec.ObjectStorageOptions.Provider == nil + if !deployManagedStorage { + // If using external storage, do nothing. + // Don't delete the PVC to prevent accidental data loss + // depending on the reclaim policy. The user may be transitioning + // from a managed cryostat-storage instance to external storage, + // but the pre-existing cryostat-storage PVC may still contain data the user wants to retain. + return nil + } return r.reconcilePVC(ctx, cr, cfg, *resource.NewQuantity(DefaultStoragePVCSize, resource.BinarySI), &name) } diff --git a/internal/controllers/reconciler.go b/internal/controllers/reconciler.go index 919086f3..b6114290 100644 --- a/internal/controllers/reconciler.go +++ b/internal/controllers/reconciler.go @@ -309,16 +309,9 @@ func (r *Reconciler) reconcileCryostat(ctx context.Context, cr *model.CryostatIn return databaseResult, err } - if cr.Spec.ObjectStorageOptions == nil { - storageResult, err := r.reconcileStorage(ctx, reqLogger, cr, tlsConfig, imageTags, serviceSpecs, *fsGroup) - if err != nil { - return storageResult, err - } - } else { - serviceSpecs.StorageURL, err = url.Parse(*cr.Spec.ObjectStorageOptions.Provider.URL) - if err != nil { - return reconcile.Result{}, err - } + storageResult, err := r.reconcileStorage(ctx, reqLogger, cr, tlsConfig, imageTags, serviceSpecs, *fsGroup) + if err != nil { + return storageResult, err } reportsResult, err := r.reconcileReports(ctx, reqLogger, cr, tlsConfig, imageTags, serviceSpecs) @@ -481,11 +474,29 @@ func (r *Reconciler) reconcileStorage(ctx context.Context, reqLogger logr.Logger if err != nil { return reconcile.Result{}, err } + err = r.reconcileStorageNetworkPolicy(ctx, cr) if err != nil { return reconcile.Result{}, err } + deployment := resources.NewDeploymentForStorage(cr, imageTags, tls, r.IsOpenShift, fsGroup) + deployManagedStorage := cr.Spec.ObjectStorageOptions == nil || cr.Spec.ObjectStorageOptions.Provider == nil + if !deployManagedStorage { + serviceSpecs.StorageURL, err = url.Parse(*cr.Spec.ObjectStorageOptions.Provider.URL) + if err := r.Client.Delete(ctx, deployment); err != nil && !kerrors.IsNotFound(err) { + return reconcile.Result{}, err + } + + removeConditionIfPresent(cr, operatorv1beta2.ConditionTypeStorageDeploymentAvailable, + operatorv1beta2.ConditionTypeStorageDeploymentProgressing, + operatorv1beta2.ConditionTypeStorageDeploymentReplicaFailure) + err := r.Client.Status().Update(ctx, cr.Object) + if err != nil { + return reconcile.Result{}, err + } + return reconcile.Result{}, nil + } err = r.createOrUpdateDeployment(ctx, deployment, cr.Object) if err != nil { diff --git a/internal/controllers/services.go b/internal/controllers/services.go index d6b16fd8..131146b7 100644 --- a/internal/controllers/services.go +++ b/internal/controllers/services.go @@ -194,6 +194,10 @@ func (r *Reconciler) reconcileStorageService(ctx context.Context, cr *model.Cryo }, } + deployManagedStorage := cr.Spec.ObjectStorageOptions == nil || cr.Spec.ObjectStorageOptions.Provider == nil + if !deployManagedStorage { + return r.deleteService(ctx, svc) + } scheme := "http" if tls != nil { scheme = "https" From 7f5d4e44b871d0886874e80fc7d2280003de2490 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Tue, 7 Oct 2025 14:40:44 -0400 Subject: [PATCH 22/23] doc, regen --- api/v1beta2/cryostat_types.go | 2 +- ...yostat-operator.clusterserviceversion.yaml | 39 ++++++++++++++- .../operator.cryostat.io_cryostats.yaml | 40 ++++++++++++++++ .../bases/operator.cryostat.io_cryostats.yaml | 4 +- ...yostat-operator.clusterserviceversion.yaml | 47 +++++++++++++++++++ .../resource_definitions.go | 3 +- 6 files changed, 130 insertions(+), 5 deletions(-) diff --git a/api/v1beta2/cryostat_types.go b/api/v1beta2/cryostat_types.go index 36dd2469..c913db8d 100644 --- a/api/v1beta2/cryostat_types.go +++ b/api/v1beta2/cryostat_types.go @@ -780,7 +780,7 @@ type ObjectStorageOptions struct { // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Object Storage Provider Options" Provider *ObjectStorageProviderOptions `json:"provider,omitempty"` - // Configuration for object storage buckets. + // Configuration for object storage buckets. Only applies when external storage is configured, ie. .spec.ObjectStorageProviderOptions is non-nil. // +optional // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Storage Bucket Names" StorageBucketNameOptions *StorageBucketNameOptions `json:"storageBucketNameOptions,omitempty"` diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index cb67a4f2..837528e6 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -24,7 +24,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:4.1.0-dev - createdAt: "2025-09-24T18:47:41Z" + createdAt: "2025-10-07T18:40:23Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { @@ -249,11 +249,24 @@ spec: - description: Configuration for external object storage providers. displayName: Object Storage Provider Options path: objectStorageOptions.provider + - description: The strategy Cryostat will use for storing files' metadata. The default 'tagging' strategy stores all metadata as object Tags. The 'metadata' strategy stores metadata as object Metadata, which is immutable but allows for more entries than Tags. The 'bucket' strategy stores metadata as separate files (ex. JSON object maps) in a dedicated bucket, with prefixes to differentiate the kind of object the metadata belongs to. + displayName: Metadata Mode + path: objectStorageOptions.provider.metadataMode + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:select:tagging + - urn:alm:descriptor:com.tectonic.ui:select:metadata + - urn:alm:descriptor:com.tectonic.ui:select:bucket + - description: The object storage provider region. + displayName: Region + path: objectStorageOptions.provider.region - description: Whether Cryostat should trust all TLS certificates presented by the external object storage provider. Defaults to false. displayName: TLS Trust All path: objectStorageOptions.provider.tlsTrustAll x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: The complete URL (not including authentication information) to the external object storage provider. + displayName: URL + path: objectStorageOptions.provider.url - description: Whether path-style access should be used, as opposed to subdomain access. Defaults to true for compatibility. displayName: Use Path-Style Access path: objectStorageOptions.provider.usePathStyleAccess @@ -264,6 +277,30 @@ spec: path: objectStorageOptions.secretName x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret + - description: Configuration for object storage buckets. Only applies when external storage is configured, ie. .spec.ObjectStorageProviderOptions is non-nil. + displayName: Storage Bucket Names + path: objectStorageOptions.storageBucketNameOptions + - description: The name of the bucket used to store Archived JFR files. + displayName: Archived Recordings + path: objectStorageOptions.storageBucketNameOptions.archivedRecordings + - description: The name of the bucket used to store a cache of Automated Analysis reports attached to Archived JFR files. + displayName: Archived Reports + path: objectStorageOptions.storageBucketNameOptions.archivedReports + - description: The name of the bucket used to store custom Event Templates. + displayName: Event Templates + path: objectStorageOptions.storageBucketNameOptions.eventTemplates + - description: The name of the bucket used to store JVM heap dumps. + displayName: Heap Dumps + path: objectStorageOptions.storageBucketNameOptions.heapDumps + - description: The name of the bucket used to store JMC Agent Probe templates. + displayName: JMCAgent Probe Templates + path: objectStorageOptions.storageBucketNameOptions.jmcAgentProbeTemplates + - description: The name of the bucket used to storage metadata for other objects (ex. archived recordings). This is only used if the .spec.objectStorageOptions.provider.metadataMode is set to 'bucket'. + displayName: Metadata + path: objectStorageOptions.storageBucketNameOptions.metadata + - description: The name of the bucket used to storage JVM thread dumps. + displayName: Thread Dumps + path: objectStorageOptions.storageBucketNameOptions.threadDumps - description: Options to configure the Cryostat deployments and pods metadata displayName: Operand metadata path: operandMetadata diff --git a/bundle/manifests/operator.cryostat.io_cryostats.yaml b/bundle/manifests/operator.cryostat.io_cryostats.yaml index 00fe4550..0d46bf84 100644 --- a/bundle/manifests/operator.cryostat.io_cryostats.yaml +++ b/bundle/manifests/operator.cryostat.io_cryostats.yaml @@ -5972,6 +5972,10 @@ spec: The 'metadata' strategy stores metadata as object Metadata, which is immutable but allows for more entries than Tags. The 'bucket' strategy stores metadata as separate files (ex. JSON object maps) in a dedicated bucket, with prefixes to differentiate the kind of object the metadata belongs to. + enum: + - tagging + - metadata + - bucket type: string region: description: The object storage provider region. @@ -5998,6 +6002,42 @@ spec: It is recommended that the secret should be marked as immutable to avoid accidental changes to secret's data. More details: [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#secret-immutable) type: string + storageBucketNameOptions: + description: Configuration for object storage buckets. Only applies + when external storage is configured, ie. .spec.ObjectStorageProviderOptions + is non-nil. + properties: + archivedRecordings: + description: The name of the bucket used to store Archived + JFR files. + type: string + archivedReports: + description: The name of the bucket used to store a cache + of Automated Analysis reports attached to Archived JFR files. + type: string + eventTemplates: + description: The name of the bucket used to store custom Event + Templates. + type: string + heapDumps: + description: The name of the bucket used to store JVM heap + dumps. + type: string + jmcAgentProbeTemplates: + description: The name of the bucket used to store JMC Agent + Probe templates. + type: string + metadata: + description: The name of the bucket used to storage metadata + for other objects (ex. archived recordings). This is only + used if the .spec.objectStorageOptions.provider.metadataMode + is set to 'bucket'. + type: string + threadDumps: + description: The name of the bucket used to storage JVM thread + dumps. + type: string + type: object type: object operandMetadata: description: Options to configure the Cryostat deployments and pods diff --git a/config/crd/bases/operator.cryostat.io_cryostats.yaml b/config/crd/bases/operator.cryostat.io_cryostats.yaml index 1987387d..d6aabee4 100644 --- a/config/crd/bases/operator.cryostat.io_cryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_cryostats.yaml @@ -5990,7 +5990,9 @@ spec: More details: [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#secret-immutable) type: string storageBucketNameOptions: - description: Configuration for object storage buckets. + description: Configuration for object storage buckets. Only applies + when external storage is configured, ie. .spec.ObjectStorageProviderOptions + is non-nil. properties: archivedRecordings: description: The name of the bucket used to store Archived diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index f662bc2c..9591eed9 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -285,12 +285,31 @@ spec: - description: Configuration for external object storage providers. displayName: Object Storage Provider Options path: objectStorageOptions.provider + - description: The strategy Cryostat will use for storing files' metadata. The + default 'tagging' strategy stores all metadata as object Tags. The 'metadata' + strategy stores metadata as object Metadata, which is immutable but allows + for more entries than Tags. The 'bucket' strategy stores metadata as separate + files (ex. JSON object maps) in a dedicated bucket, with prefixes to differentiate + the kind of object the metadata belongs to. + displayName: Metadata Mode + path: objectStorageOptions.provider.metadataMode + x-descriptors: + - urn:alm:descriptor:com.tectonic.ui:select:tagging + - urn:alm:descriptor:com.tectonic.ui:select:metadata + - urn:alm:descriptor:com.tectonic.ui:select:bucket + - description: The object storage provider region. + displayName: Region + path: objectStorageOptions.provider.region - description: Whether Cryostat should trust all TLS certificates presented by the external object storage provider. Defaults to false. displayName: TLS Trust All path: objectStorageOptions.provider.tlsTrustAll x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch + - description: The complete URL (not including authentication information) to + the external object storage provider. + displayName: URL + path: objectStorageOptions.provider.url - description: Whether path-style access should be used, as opposed to subdomain access. Defaults to true for compatibility. displayName: Use Path-Style Access @@ -308,6 +327,34 @@ spec: path: objectStorageOptions.secretName x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret + - description: Configuration for object storage buckets. Only applies when external + storage is configured, ie. .spec.ObjectStorageProviderOptions is non-nil. + displayName: Storage Bucket Names + path: objectStorageOptions.storageBucketNameOptions + - description: The name of the bucket used to store Archived JFR files. + displayName: Archived Recordings + path: objectStorageOptions.storageBucketNameOptions.archivedRecordings + - description: The name of the bucket used to store a cache of Automated Analysis + reports attached to Archived JFR files. + displayName: Archived Reports + path: objectStorageOptions.storageBucketNameOptions.archivedReports + - description: The name of the bucket used to store custom Event Templates. + displayName: Event Templates + path: objectStorageOptions.storageBucketNameOptions.eventTemplates + - description: The name of the bucket used to store JVM heap dumps. + displayName: Heap Dumps + path: objectStorageOptions.storageBucketNameOptions.heapDumps + - description: The name of the bucket used to store JMC Agent Probe templates. + displayName: JMCAgent Probe Templates + path: objectStorageOptions.storageBucketNameOptions.jmcAgentProbeTemplates + - description: The name of the bucket used to storage metadata for other objects + (ex. archived recordings). This is only used if the .spec.objectStorageOptions.provider.metadataMode + is set to 'bucket'. + displayName: Metadata + path: objectStorageOptions.storageBucketNameOptions.metadata + - description: The name of the bucket used to storage JVM thread dumps. + displayName: Thread Dumps + path: objectStorageOptions.storageBucketNameOptions.threadDumps - description: Options to configure the Cryostat deployments and pods metadata displayName: Operand metadata path: operandMetadata diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index f2f191b1..dfd25ef1 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -1886,8 +1886,7 @@ func NewStorageContainer(cr *model.CryostatInstance, imageTag string, tls *TLSCo var containerSc *corev1.SecurityContext envs := []corev1.EnvVar{ { - Name: "CRYOSTAT_BUCKETS", - // TODO use cr.Spec.ObjectStorageOptions.StorageBucketNameOptions if specified, with the list below as default values + Name: "CRYOSTAT_BUCKETS", Value: "archivedrecordings,archivedreports,eventtemplates,probes,heapdumps,threaddumps", }, { From a9ba399f0a51a9f9b48643ac46417dc1a3fac17d Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Wed, 8 Oct 2025 16:28:27 -0400 Subject: [PATCH 23/23] rename and invert property --- api/v1beta2/cryostat_types.go | 6 +++--- api/v1beta2/zz_generated.deepcopy.go | 4 ++-- .../cryostat-operator.clusterserviceversion.yaml | 8 ++++---- bundle/manifests/operator.cryostat.io_cryostats.yaml | 7 ++++--- config/crd/bases/operator.cryostat.io_cryostats.yaml | 7 ++++--- .../cryostat-operator.clusterserviceversion.yaml | 8 ++++---- .../resource_definitions/resource_definitions.go | 8 ++++---- internal/test/resources.go | 12 ++++++------ 8 files changed, 31 insertions(+), 29 deletions(-) diff --git a/api/v1beta2/cryostat_types.go b/api/v1beta2/cryostat_types.go index c913db8d..bddcdcaa 100644 --- a/api/v1beta2/cryostat_types.go +++ b/api/v1beta2/cryostat_types.go @@ -791,10 +791,10 @@ type ObjectStorageProviderOptions struct { // The complete URL (not including authentication information) to the external object storage provider. // +operator-sdk:csv:customresourcedefinitions:type=spec URL *string `json:"url,omitempty"` - // Whether path-style access should be used, as opposed to subdomain access. Defaults to true for compatibility. + // Whether virtual host subdomain access should be used, as opposed to path-style access. Defaults to false for compatibility. // +optional - // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Use Path-Style Access",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} - UsePathStyleAccess *bool `json:"usePathStyleAccess,omitempty"` + // +operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Use Virtual Host Subdomain Access",xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} + UseVirtualHostAccess *bool `json:"useVirtualHostAccess,omitempty"` // The object storage provider region. // +operator-sdk:csv:customresourcedefinitions:type=spec Region *string `json:"region,omitempty"` diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 43800753..033916c1 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -638,8 +638,8 @@ func (in *ObjectStorageProviderOptions) DeepCopyInto(out *ObjectStorageProviderO *out = new(string) **out = **in } - if in.UsePathStyleAccess != nil { - in, out := &in.UsePathStyleAccess, &out.UsePathStyleAccess + if in.UseVirtualHostAccess != nil { + in, out := &in.UseVirtualHostAccess, &out.UseVirtualHostAccess *out = new(bool) **out = **in } diff --git a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 837528e6..07cef9ef 100644 --- a/bundle/manifests/cryostat-operator.clusterserviceversion.yaml +++ b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml @@ -24,7 +24,7 @@ metadata: capabilities: Seamless Upgrades categories: Monitoring, Developer Tools containerImage: quay.io/cryostat/cryostat-operator:4.1.0-dev - createdAt: "2025-10-07T18:40:23Z" + createdAt: "2025-10-08T20:21:35Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { @@ -267,9 +267,9 @@ spec: - description: The complete URL (not including authentication information) to the external object storage provider. displayName: URL path: objectStorageOptions.provider.url - - description: Whether path-style access should be used, as opposed to subdomain access. Defaults to true for compatibility. - displayName: Use Path-Style Access - path: objectStorageOptions.provider.usePathStyleAccess + - description: Whether virtual host subdomain access should be used, as opposed to path-style access. Defaults to false for compatibility. + displayName: Use Virtual Host Subdomain Access + path: objectStorageOptions.provider.useVirtualHostAccess x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: 'Name of the secret containing the object storage secret access key. This secret must contain a ACCESS_KEY secret which is the object storage access key ID, and a SECRET_KEY secret which is the object storage secret access key. If using an external S3 provider requiring authentication then this must be provided. It is recommended that the secret should be marked as immutable to avoid accidental changes to secret''s data. More details: [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#secret-immutable)' diff --git a/bundle/manifests/operator.cryostat.io_cryostats.yaml b/bundle/manifests/operator.cryostat.io_cryostats.yaml index 0d46bf84..887cd1d9 100644 --- a/bundle/manifests/operator.cryostat.io_cryostats.yaml +++ b/bundle/manifests/operator.cryostat.io_cryostats.yaml @@ -5989,9 +5989,10 @@ spec: description: The complete URL (not including authentication information) to the external object storage provider. type: string - usePathStyleAccess: - description: Whether path-style access should be used, as - opposed to subdomain access. Defaults to true for compatibility. + useVirtualHostAccess: + description: Whether virtual host subdomain access should + be used, as opposed to path-style access. Defaults to false + for compatibility. type: boolean type: object secretName: diff --git a/config/crd/bases/operator.cryostat.io_cryostats.yaml b/config/crd/bases/operator.cryostat.io_cryostats.yaml index d6aabee4..6ac0d899 100644 --- a/config/crd/bases/operator.cryostat.io_cryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_cryostats.yaml @@ -5976,9 +5976,10 @@ spec: description: The complete URL (not including authentication information) to the external object storage provider. type: string - usePathStyleAccess: - description: Whether path-style access should be used, as - opposed to subdomain access. Defaults to true for compatibility. + useVirtualHostAccess: + description: Whether virtual host subdomain access should + be used, as opposed to path-style access. Defaults to false + for compatibility. type: boolean type: object secretName: diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index 9591eed9..a7979e07 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -310,10 +310,10 @@ spec: the external object storage provider. displayName: URL path: objectStorageOptions.provider.url - - description: Whether path-style access should be used, as opposed to subdomain - access. Defaults to true for compatibility. - displayName: Use Path-Style Access - path: objectStorageOptions.provider.usePathStyleAccess + - description: Whether virtual host subdomain access should be used, as opposed + to path-style access. Defaults to false for compatibility. + displayName: Use Virtual Host Subdomain Access + path: objectStorageOptions.provider.useVirtualHostAccess x-descriptors: - urn:alm:descriptor:com.tectonic.ui:booleanSwitch - description: 'Name of the secret containing the object storage secret access diff --git a/internal/controllers/common/resource_definitions/resource_definitions.go b/internal/controllers/common/resource_definitions/resource_definitions.go index dfd25ef1..e1cb7293 100644 --- a/internal/controllers/common/resource_definitions/resource_definitions.go +++ b/internal/controllers/common/resource_definitions/resource_definitions.go @@ -1437,13 +1437,13 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag }, }...) - usePathStyleAccess := true - if cr.Spec.ObjectStorageOptions.Provider != nil && cr.Spec.ObjectStorageOptions.Provider.UsePathStyleAccess != nil { - usePathStyleAccess = *cr.Spec.ObjectStorageOptions.Provider.UsePathStyleAccess + useVirtualHostAccess := false + if cr.Spec.ObjectStorageOptions.Provider != nil && cr.Spec.ObjectStorageOptions.Provider.UseVirtualHostAccess != nil { + useVirtualHostAccess = *cr.Spec.ObjectStorageOptions.Provider.UseVirtualHostAccess } envs = append(envs, corev1.EnvVar{ Name: "QUARKUS_S3_PATH_STYLE_ACCESS", - Value: strconv.FormatBool(usePathStyleAccess), + Value: strconv.FormatBool(!useVirtualHostAccess), }) metadataMode := "tagging" diff --git a/internal/test/resources.go b/internal/test/resources.go index dd210542..0c3bacb7 100644 --- a/internal/test/resources.go +++ b/internal/test/resources.go @@ -228,17 +228,17 @@ func (r *TestResources) NewCryostatWithExternalS3(secretName string) *model.Cryo cr := r.NewCryostat() providerUrl := "https://example.com:1234" region := "region-east-1" - usePathStyleAccess := true + useVirtualHostAccess := false tlsTrustAll := true metadataMode := "tagging" cr.Spec.ObjectStorageOptions = &operatorv1beta2.ObjectStorageOptions{ SecretName: &secretName, Provider: &operatorv1beta2.ObjectStorageProviderOptions{ - URL: &providerUrl, - Region: ®ion, - UsePathStyleAccess: &usePathStyleAccess, - TLSTrustAll: &tlsTrustAll, - MetadataMode: &metadataMode, + URL: &providerUrl, + Region: ®ion, + UseVirtualHostAccess: &useVirtualHostAccess, + TLSTrustAll: &tlsTrustAll, + MetadataMode: &metadataMode, }, } return cr