diff --git a/api/v1beta2/cryostat_types.go b/api/v1beta2/cryostat_types.go index 3bab1c319..bddcdcaac 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,83 @@ 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 + // 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"} + 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"` + // 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"` +} + +// 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 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 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"` + // 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"` + // 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 + // +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"` +} + +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 e0e2de6ae..033916c1a 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,76 @@ 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) + } + 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. +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.UseVirtualHostAccess != nil { + in, out := &in.UseVirtualHostAccess, &out.UseVirtualHostAccess + *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 @@ -969,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/bundle/manifests/cryostat-operator.clusterserviceversion.yaml b/bundle/manifests/cryostat-operator.clusterserviceversion.yaml index 363c73cde..07cef9efb 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-10-08T20:21:35Z" description: JVM monitoring and profiling tool operatorframework.io/initialization-resource: |- { @@ -243,6 +243,64 @@ 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: 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 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)' + displayName: Secret Name + 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 e05bcf55f..887cd1d93 100644 --- a/bundle/manifests/operator.cryostat.io_cryostats.yaml +++ b/bundle/manifests/operator.cryostat.io_cryostats.yaml @@ -5958,6 +5958,88 @@ 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. + enum: + - tagging + - metadata + - bucket + 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 + 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: + 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 + 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 metadata diff --git a/config/crd/bases/operator.cryostat.io_cryostats.yaml b/config/crd/bases/operator.cryostat.io_cryostats.yaml index 43e9ce0cd..6ac0d8996 100644 --- a/config/crd/bases/operator.cryostat.io_cryostats.yaml +++ b/config/crd/bases/operator.cryostat.io_cryostats.yaml @@ -5945,6 +5945,88 @@ 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. + enum: + - tagging + - metadata + - bucket + 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 + 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: + 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 + 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 metadata diff --git a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml index 2c2b75e0d..a7979e07d 100644 --- a/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/cryostat-operator.clusterserviceversion.yaml @@ -278,6 +278,83 @@ 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: 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 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)' + displayName: Secret Name + 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 dc87ec41c..e1cb7293a 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, @@ -861,35 +865,10 @@ 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{} - 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 { @@ -931,14 +910,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" @@ -952,7 +940,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), @@ -1350,7 +1337,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" @@ -1393,12 +1380,12 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag Value: "cryostat", }, { - Name: "STORAGE_BUCKETS_ARCHIVE_NAME", - Value: "archivedrecordings", + Name: "CRYOSTAT_CONFIG_PATH", + Value: configPath, }, { - Name: "QUARKUS_S3_ENDPOINT_OVERRIDE", - Value: specs.StorageURL.String(), + Name: "CRYOSTAT_TEMPLATE_PATH", + Value: templatesPath, }, { Name: "QUARKUS_S3_SYNC_CLIENT_TLS_KEY_MANAGERS_PROVIDER_TYPE", @@ -1410,26 +1397,119 @@ 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 { + 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 { + return nil, fmt.Errorf("cr.Spec.ObjectStorageOptions was not nil, but cr.Spec.ObjectStorageOptions.Provider.Region was nil") + } + 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, + }, + }...) + + 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(!useVirtualHostAccess), + }) + + 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, + }) + + 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.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", + 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 + } + if tlsTrustAll { + envs = append(envs, corev1.EnvVar{ + Name: "QUARKUS_S3_SYNC_CLIENT_TLS_TRUST_MANAGERS_PROVIDER_TYPE", + Value: "trust-all", + }) + } } mounts := []corev1.VolumeMount{ @@ -1463,7 +1543,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", @@ -1687,7 +1767,7 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag } } - return corev1.Container{ + return &corev1.Container{ Name: cr.Name, Image: imageTag, ImagePullPolicy: common.GetPullPolicy(imageTag), @@ -1708,7 +1788,7 @@ func NewCoreContainer(cr *model.CryostatInstance, specs *ServiceSpecs, imageTag FailureThreshold: 18, }, SecurityContext: containerSc, - } + }, nil } func NewGrafanaContainerResource(cr *model.CryostatInstance) *corev1.ResourceRequirements { @@ -1807,7 +1887,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", @@ -2072,10 +2152,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{} @@ -2087,16 +2163,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{ @@ -2292,3 +2375,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/networkpolicy.go b/internal/controllers/networkpolicy.go index c12d24eb3..c85800bc9 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 be410e2d2..4034b28a0 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 abefc3e7a..b61142909 100644 --- a/internal/controllers/reconciler.go +++ b/internal/controllers/reconciler.go @@ -474,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/reconciler_test.go b/internal/controllers/reconciler_test.go index 79486ebc2..941e6fb99 100644 --- a/internal/controllers/reconciler_test.go +++ b/internal/controllers/reconciler_test.go @@ -958,6 +958,113 @@ 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{ + { + 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, + }, + }, + }, + { + 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, + }, + }, + }, + { + Name: "QUARKUS_S3_ENDPOINT_OVERRIDE", + Value: "https://example.com:1234", + }, + { + Name: "QUARKUS_S3_PATH_STYLE_ACCESS", + Value: "true", + }, + { + Name: "QUARKUS_S3_AWS_REGION", + Value: "region-east-1", + }, + { + Name: "QUARKUS_S3_SYNC_CLIENT_TLS_TRUST_MANAGERS_PROVIDER_TYPE", + Value: "trust-all", + }, + { + 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/controllers/secrets.go b/internal/controllers/secrets.go index 9bb554471..920751631 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/controllers/services.go b/internal/controllers/services.go index d6b16fd87..131146b7c 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" diff --git a/internal/test/resources.go b/internal/test/resources.go index 1437af3fd..0c3bacb7c 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,56 @@ 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" + useVirtualHostAccess := false + tlsTrustAll := true + metadataMode := "tagging" + cr.Spec.ObjectStorageOptions = &operatorv1beta2.ObjectStorageOptions{ + SecretName: &secretName, + Provider: &operatorv1beta2.ObjectStorageProviderOptions{ + URL: &providerUrl, + Region: ®ion, + UseVirtualHostAccess: &useVirtualHostAccess, + TLSTrustAll: &tlsTrustAll, + MetadataMode: &metadataMode, + }, + } + 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{ @@ -1764,6 +1815,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{ @@ -1771,9 +1835,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"), }, } } @@ -2482,8 +2545,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, @@ -2500,9 +2566,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()), @@ -2589,10 +2653,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), @@ -2791,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", @@ -2806,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, @@ -2834,13 +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) - optional := false - 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{ { @@ -2851,26 +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), - }, - { - 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{ @@ -2900,7 +2925,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",