From a8a4d632e31bdb24c1fce1cbf914ad5d1d91eca5 Mon Sep 17 00:00:00 2001 From: Viliam Krizan Date: Mon, 3 Feb 2025 15:28:09 +0100 Subject: [PATCH 1/6] chore(config): regenerate --- controllers/cloud.redhat.com/config/types.go | 887 ++++++++++--------- 1 file changed, 444 insertions(+), 443 deletions(-) diff --git a/controllers/cloud.redhat.com/config/types.go b/controllers/cloud.redhat.com/config/types.go index 2f9866aad..c5349b89c 100644 --- a/controllers/cloud.redhat.com/config/types.go +++ b/controllers/cloud.redhat.com/config/types.go @@ -2,691 +2,692 @@ package config -import "fmt" import "encoding/json" +import "fmt" import "reflect" -// UnmarshalJSON implements json.Unmarshaler. -func (j *TopicConfig) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { - return err - } - if v, ok := raw["name"]; !ok || v == nil { - return fmt.Errorf("field name: required") - } - if v, ok := raw["requestedName"]; !ok || v == nil { - return fmt.Errorf("field requestedName: required") - } - type Plain TopicConfig - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err - } - *j = TopicConfig(plain) - return nil -} - -// UnmarshalJSON implements json.Unmarshaler. -func (j *DatabaseConfig) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { - return err - } - if v, ok := raw["adminPassword"]; !ok || v == nil { - return fmt.Errorf("field adminPassword: required") - } - if v, ok := raw["adminUsername"]; !ok || v == nil { - return fmt.Errorf("field adminUsername: required") - } - if v, ok := raw["hostname"]; !ok || v == nil { - return fmt.Errorf("field hostname: required") - } - if v, ok := raw["name"]; !ok || v == nil { - return fmt.Errorf("field name: required") - } - if v, ok := raw["password"]; !ok || v == nil { - return fmt.Errorf("field password: required") - } - if v, ok := raw["port"]; !ok || v == nil { - return fmt.Errorf("field port: required") - } - if v, ok := raw["sslMode"]; !ok || v == nil { - return fmt.Errorf("field sslMode: required") - } - if v, ok := raw["username"]; !ok || v == nil { - return fmt.Errorf("field username: required") - } - type Plain DatabaseConfig - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err - } - *j = DatabaseConfig(plain) - return nil -} - // ClowdApp deployment configuration for Clowder enabled apps. type AppConfig struct { // Defines the path to the BOPURL. - BOPURL *string `json:"BOPURL,omitempty"` + BOPURL *string `json:"BOPURL,omitempty" yaml:"BOPURL,omitempty" mapstructure:"BOPURL,omitempty"` // Database corresponds to the JSON schema field "database". - Database *DatabaseConfig `json:"database,omitempty"` + Database *DatabaseConfig `json:"database,omitempty" yaml:"database,omitempty" mapstructure:"database,omitempty"` // Endpoints corresponds to the JSON schema field "endpoints". - Endpoints []DependencyEndpoint `json:"endpoints,omitempty"` + Endpoints []DependencyEndpoint `json:"endpoints,omitempty" yaml:"endpoints,omitempty" mapstructure:"endpoints,omitempty"` // FeatureFlags corresponds to the JSON schema field "featureFlags". - FeatureFlags *FeatureFlagsConfig `json:"featureFlags,omitempty"` + FeatureFlags *FeatureFlagsConfig `json:"featureFlags,omitempty" yaml:"featureFlags,omitempty" mapstructure:"featureFlags,omitempty"` // A set of configMap/secret hashes - HashCache *string `json:"hashCache,omitempty"` + HashCache *string `json:"hashCache,omitempty" yaml:"hashCache,omitempty" mapstructure:"hashCache,omitempty"` // The external hostname of the deployment, where applicable - Hostname *string `json:"hostname,omitempty"` + Hostname *string `json:"hostname,omitempty" yaml:"hostname,omitempty" mapstructure:"hostname,omitempty"` // InMemoryDb corresponds to the JSON schema field "inMemoryDb". - InMemoryDb *InMemoryDBConfig `json:"inMemoryDb,omitempty"` + InMemoryDb *InMemoryDBConfig `json:"inMemoryDb,omitempty" yaml:"inMemoryDb,omitempty" mapstructure:"inMemoryDb,omitempty"` // Kafka corresponds to the JSON schema field "kafka". - Kafka *KafkaConfig `json:"kafka,omitempty"` + Kafka *KafkaConfig `json:"kafka,omitempty" yaml:"kafka,omitempty" mapstructure:"kafka,omitempty"` // Logging corresponds to the JSON schema field "logging". - Logging LoggingConfig `json:"logging"` + Logging LoggingConfig `json:"logging" yaml:"logging" mapstructure:"logging"` // Metadata corresponds to the JSON schema field "metadata". - Metadata *AppMetadata `json:"metadata,omitempty"` + Metadata *AppMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty" mapstructure:"metadata,omitempty"` // Defines the path to the metrics server that the app should be configured to // listen on for metric traffic. - MetricsPath string `json:"metricsPath"` + MetricsPath string `json:"metricsPath" yaml:"metricsPath" mapstructure:"metricsPath"` // Defines the metrics port that the app should be configured to listen on for // metric traffic. - MetricsPort int `json:"metricsPort"` + MetricsPort int `json:"metricsPort" yaml:"metricsPort" mapstructure:"metricsPort"` // ObjectStore corresponds to the JSON schema field "objectStore". - ObjectStore *ObjectStoreConfig `json:"objectStore,omitempty"` + ObjectStore *ObjectStoreConfig `json:"objectStore,omitempty" yaml:"objectStore,omitempty" mapstructure:"objectStore,omitempty"` // PrivateEndpoints corresponds to the JSON schema field "privateEndpoints". - PrivateEndpoints []PrivateDependencyEndpoint `json:"privateEndpoints,omitempty"` + PrivateEndpoints []PrivateDependencyEndpoint `json:"privateEndpoints,omitempty" yaml:"privateEndpoints,omitempty" mapstructure:"privateEndpoints,omitempty"` // Defines the private port that the app should be configured to listen on for API // traffic. - PrivatePort *int `json:"privatePort,omitempty"` + PrivatePort *int `json:"privatePort,omitempty" yaml:"privatePort,omitempty" mapstructure:"privatePort,omitempty"` // Defines the public port that the app should be configured to listen on for API // traffic. - PublicPort *int `json:"publicPort,omitempty"` + PublicPort *int `json:"publicPort,omitempty" yaml:"publicPort,omitempty" mapstructure:"publicPort,omitempty"` // Defines the port CA path - TlsCAPath *string `json:"tlsCAPath,omitempty"` + TlsCAPath *string `json:"tlsCAPath,omitempty" yaml:"tlsCAPath,omitempty" mapstructure:"tlsCAPath,omitempty"` // Deprecated: Use 'publicPort' instead. - WebPort *int `json:"webPort,omitempty"` + WebPort *int `json:"webPort,omitempty" yaml:"webPort,omitempty" mapstructure:"webPort,omitempty"` +} + +// Arbitrary metadata pertaining to the application application +type AppMetadata struct { + // Metadata pertaining to an application's deployments + Deployments []DeploymentMetadata `json:"deployments,omitempty" yaml:"deployments,omitempty" mapstructure:"deployments,omitempty"` + + // Name of the ClowdEnvironment this ClowdApp runs in + EnvName *string `json:"envName,omitempty" yaml:"envName,omitempty" mapstructure:"envName,omitempty"` + + // Name of the ClowdApp + Name *string `json:"name,omitempty" yaml:"name,omitempty" mapstructure:"name,omitempty"` +} + +// Broker Configuration +type BrokerConfig struct { + // Authtype corresponds to the JSON schema field "authtype". + Authtype *BrokerConfigAuthtype `json:"authtype,omitempty" yaml:"authtype,omitempty" mapstructure:"authtype,omitempty"` + + // CA certificate trust list for broker in PEM format. If absent, client should + // use OS default trust list + Cacert *string `json:"cacert,omitempty" yaml:"cacert,omitempty" mapstructure:"cacert,omitempty"` + + // Hostname of kafka broker + Hostname string `json:"hostname" yaml:"hostname" mapstructure:"hostname"` + + // Port of kafka broker + Port *int `json:"port,omitempty" yaml:"port,omitempty" mapstructure:"port,omitempty"` + + // Sasl corresponds to the JSON schema field "sasl". + Sasl *KafkaSASLConfig `json:"sasl,omitempty" yaml:"sasl,omitempty" mapstructure:"sasl,omitempty"` + + // Broker security procotol, expect one of either: SASL_SSL, SSL + SecurityProtocol *string `json:"securityProtocol,omitempty" yaml:"securityProtocol,omitempty" mapstructure:"securityProtocol,omitempty"` +} + +type BrokerConfigAuthtype string + +const BrokerConfigAuthtypeSasl BrokerConfigAuthtype = "sasl" + +// Cloud Watch configuration +type CloudWatchConfig struct { + // Defines the access key that the app should use for configuring CloudWatch. + AccessKeyId string `json:"accessKeyId" yaml:"accessKeyId" mapstructure:"accessKeyId"` + + // Defines the logGroup that the app should use for configuring CloudWatch. + LogGroup string `json:"logGroup" yaml:"logGroup" mapstructure:"logGroup"` + + // Defines the region that the app should use for configuring CloudWatch. + Region string `json:"region" yaml:"region" mapstructure:"region"` + + // Defines the secret key that the app should use for configuring CloudWatch. + SecretAccessKey string `json:"secretAccessKey" yaml:"secretAccessKey" mapstructure:"secretAccessKey"` +} + +// Database Configuration +type DatabaseConfig struct { + // Defines the pgAdmin password. + AdminPassword string `json:"adminPassword" yaml:"adminPassword" mapstructure:"adminPassword"` + + // Defines the pgAdmin username. + AdminUsername string `json:"adminUsername" yaml:"adminUsername" mapstructure:"adminUsername"` + + // Defines the hostname of the database configured for the ClowdApp. + Hostname string `json:"hostname" yaml:"hostname" mapstructure:"hostname"` + + // Defines the database name. + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // Defines the password for the standard user. + Password string `json:"password" yaml:"password" mapstructure:"password"` + + // Defines the port of the database configured for the ClowdApp. + Port int `json:"port" yaml:"port" mapstructure:"port"` + + // Defines the CA used to access the database. + RdsCa *string `json:"rdsCa,omitempty" yaml:"rdsCa,omitempty" mapstructure:"rdsCa,omitempty"` + + // Defines the postgres SSL mode that should be used. + SslMode string `json:"sslMode" yaml:"sslMode" mapstructure:"sslMode"` + + // Defines a username with standard access to the database. + Username string `json:"username" yaml:"username" mapstructure:"username"` +} + +// Dependent service connection info +type DependencyEndpoint struct { + // The top level api path that the app should serve from /api/ + // (deprecated, use apiPaths) + ApiPath string `json:"apiPath" yaml:"apiPath" mapstructure:"apiPath"` + + // The list of API paths (each matching format: '/api/some-path/') that this app + // will serve requests from + ApiPaths []string `json:"apiPaths,omitempty" yaml:"apiPaths,omitempty" mapstructure:"apiPaths,omitempty"` + + // The app name of the ClowdApp hosting the service. + App string `json:"app" yaml:"app" mapstructure:"app"` + + // The hostname of the dependent service. + Hostname string `json:"hostname" yaml:"hostname" mapstructure:"hostname"` + + // The PodSpec name of the dependent service inside the ClowdApp. + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // The port of the dependent service. + Port int `json:"port" yaml:"port" mapstructure:"port"` + + // The TLS port of the dependent service. + TlsPort *int `json:"tlsPort,omitempty" yaml:"tlsPort,omitempty" mapstructure:"tlsPort,omitempty"` +} + +// Deployment Metadata +type DeploymentMetadata struct { + // Image used by deployment + Image string `json:"image" yaml:"image" mapstructure:"image"` + + // Name of deployment + Name string `json:"name" yaml:"name" mapstructure:"name"` +} + +// Feature Flags Configuration +type FeatureFlagsConfig struct { + // Defines the client access token to use when connect to the FeatureFlags server + ClientAccessToken *string `json:"clientAccessToken,omitempty" yaml:"clientAccessToken,omitempty" mapstructure:"clientAccessToken,omitempty"` + + // Defines the hostname for the FeatureFlags server + Hostname string `json:"hostname" yaml:"hostname" mapstructure:"hostname"` + + // Defines the port for the FeatureFlags server + Port int `json:"port" yaml:"port" mapstructure:"port"` + + // Details the scheme to use for FeatureFlags http/https + Scheme FeatureFlagsConfigScheme `json:"scheme" yaml:"scheme" mapstructure:"scheme"` +} + +type FeatureFlagsConfigScheme string + +const FeatureFlagsConfigSchemeHttp FeatureFlagsConfigScheme = "http" +const FeatureFlagsConfigSchemeHttps FeatureFlagsConfigScheme = "https" + +// In Memory DB Configuration +type InMemoryDBConfig struct { + // Defines the hostname for the In Memory DB server configuration. + Hostname string `json:"hostname" yaml:"hostname" mapstructure:"hostname"` + + // Defines the password for the In Memory DB server configuration. + Password *string `json:"password,omitempty" yaml:"password,omitempty" mapstructure:"password,omitempty"` + + // Defines the port for the In Memory DB server configuration. + Port int `json:"port" yaml:"port" mapstructure:"port"` + + // Defines the sslMode used by the In Memory DB server coniguration + SslMode *bool `json:"sslMode,omitempty" yaml:"sslMode,omitempty" mapstructure:"sslMode,omitempty"` + + // Defines the username for the In Memory DB server configuration. + Username *string `json:"username,omitempty" yaml:"username,omitempty" mapstructure:"username,omitempty"` +} + +// Kafka Configuration +type KafkaConfig struct { + // Defines the brokers the app should connect to for Kafka services. + Brokers []BrokerConfig `json:"brokers" yaml:"brokers" mapstructure:"brokers"` + + // Defines a list of the topic configurations available to the application. + Topics []TopicConfig `json:"topics" yaml:"topics" mapstructure:"topics"` +} + +// SASL Configuration for Kafka +type KafkaSASLConfig struct { + // Broker SASL password + Password *string `json:"password,omitempty" yaml:"password,omitempty" mapstructure:"password,omitempty"` + + // Broker SASL mechanism, expect: SCRAM-SHA-512 + SaslMechanism *string `json:"saslMechanism,omitempty" yaml:"saslMechanism,omitempty" mapstructure:"saslMechanism,omitempty"` + + // Broker security protocol, expect one of either: SASL_SSL, SSL. DEPRECATED, use + // the top level securityProtocol field instead + SecurityProtocol *string `json:"securityProtocol,omitempty" yaml:"securityProtocol,omitempty" mapstructure:"securityProtocol,omitempty"` + + // Broker SASL username + Username *string `json:"username,omitempty" yaml:"username,omitempty" mapstructure:"username,omitempty"` +} + +// Logging Configuration +type LoggingConfig struct { + // Cloudwatch corresponds to the JSON schema field "cloudwatch". + Cloudwatch *CloudWatchConfig `json:"cloudwatch,omitempty" yaml:"cloudwatch,omitempty" mapstructure:"cloudwatch,omitempty"` + + // Defines the type of logging configuration + Type string `json:"type" yaml:"type" mapstructure:"type"` +} + +// Object Storage Bucket +type ObjectStoreBucket struct { + // Defines the access key for specificed bucket. + AccessKey *string `json:"accessKey,omitempty" yaml:"accessKey,omitempty" mapstructure:"accessKey,omitempty"` + + // Defines the endpoint for the Object Storage server configuration. + Endpoint *string `json:"endpoint,omitempty" yaml:"endpoint,omitempty" mapstructure:"endpoint,omitempty"` + + // The actual name of the bucket being accessed. + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // Defines the region for the specified bucket. + Region *string `json:"region,omitempty" yaml:"region,omitempty" mapstructure:"region,omitempty"` + + // The name that was requested for the bucket in the ClowdApp. + RequestedName string `json:"requestedName" yaml:"requestedName" mapstructure:"requestedName"` + + // Defines the secret key for the specified bucket. + SecretKey *string `json:"secretKey,omitempty" yaml:"secretKey,omitempty" mapstructure:"secretKey,omitempty"` + + // Details if the Object Server uses TLS. + Tls *bool `json:"tls,omitempty" yaml:"tls,omitempty" mapstructure:"tls,omitempty"` } // UnmarshalJSON implements json.Unmarshaler. -func (j *DependencyEndpoint) UnmarshalJSON(b []byte) error { +func (j *InMemoryDBConfig) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["apiPath"]; !ok || v == nil { - return fmt.Errorf("field apiPath: required") - } - if v, ok := raw["app"]; !ok || v == nil { - return fmt.Errorf("field app: required") - } if v, ok := raw["hostname"]; !ok || v == nil { - return fmt.Errorf("field hostname: required") - } - if v, ok := raw["name"]; !ok || v == nil { - return fmt.Errorf("field name: required") + return fmt.Errorf("field hostname in InMemoryDBConfig: required") } if v, ok := raw["port"]; !ok || v == nil { - return fmt.Errorf("field port: required") + return fmt.Errorf("field port in InMemoryDBConfig: required") } - type Plain DependencyEndpoint + type Plain InMemoryDBConfig var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = DependencyEndpoint(plain) + *j = InMemoryDBConfig(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. -func (j *PrivateDependencyEndpoint) UnmarshalJSON(b []byte) error { +func (j *BrokerConfig) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["app"]; !ok || v == nil { - return fmt.Errorf("field app: required") - } if v, ok := raw["hostname"]; !ok || v == nil { - return fmt.Errorf("field hostname: required") - } - if v, ok := raw["name"]; !ok || v == nil { - return fmt.Errorf("field name: required") - } - if v, ok := raw["port"]; !ok || v == nil { - return fmt.Errorf("field port: required") + return fmt.Errorf("field hostname in BrokerConfig: required") } - type Plain PrivateDependencyEndpoint + type Plain BrokerConfig var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = PrivateDependencyEndpoint(plain) + *j = BrokerConfig(plain) return nil } +// Topic Configuration +type TopicConfig struct { + // The name of the actual topic on the Kafka server. + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // The name that the app requested in the ClowdApp definition. + RequestedName string `json:"requestedName" yaml:"requestedName" mapstructure:"requestedName"` +} + // UnmarshalJSON implements json.Unmarshaler. -func (j *ObjectStoreConfig) UnmarshalJSON(b []byte) error { +func (j *TopicConfig) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["hostname"]; !ok || v == nil { - return fmt.Errorf("field hostname: required") - } - if v, ok := raw["port"]; !ok || v == nil { - return fmt.Errorf("field port: required") + if v, ok := raw["name"]; !ok || v == nil { + return fmt.Errorf("field name in TopicConfig: required") } - if v, ok := raw["tls"]; !ok || v == nil { - return fmt.Errorf("field tls: required") + if v, ok := raw["requestedName"]; !ok || v == nil { + return fmt.Errorf("field requestedName in TopicConfig: required") } - type Plain ObjectStoreConfig + type Plain TopicConfig var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = ObjectStoreConfig(plain) + *j = TopicConfig(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. -func (j *FeatureFlagsConfigScheme) UnmarshalJSON(b []byte) error { +func (j *BrokerConfigAuthtype) UnmarshalJSON(b []byte) error { var v string if err := json.Unmarshal(b, &v); err != nil { return err } var ok bool - for _, expected := range enumValues_FeatureFlagsConfigScheme { + for _, expected := range enumValues_BrokerConfigAuthtype { if reflect.DeepEqual(v, expected) { ok = true break } } if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_FeatureFlagsConfigScheme, v) + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_BrokerConfigAuthtype, v) } - *j = FeatureFlagsConfigScheme(v) + *j = BrokerConfigAuthtype(v) return nil } // UnmarshalJSON implements json.Unmarshaler. -func (j *ObjectStoreBucket) UnmarshalJSON(b []byte) error { +func (j *KafkaConfig) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["name"]; !ok || v == nil { - return fmt.Errorf("field name: required") + if v, ok := raw["brokers"]; !ok || v == nil { + return fmt.Errorf("field brokers in KafkaConfig: required") } - if v, ok := raw["requestedName"]; !ok || v == nil { - return fmt.Errorf("field requestedName: required") + if v, ok := raw["topics"]; !ok || v == nil { + return fmt.Errorf("field topics in KafkaConfig: required") } - type Plain ObjectStoreBucket + type Plain KafkaConfig var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = ObjectStoreBucket(plain) + *j = KafkaConfig(plain) return nil } -// Arbitrary metadata pertaining to the application application -type AppMetadata struct { - // Metadata pertaining to an application's deployments - Deployments []DeploymentMetadata `json:"deployments,omitempty"` - - // Name of the ClowdEnvironment this ClowdApp runs in - EnvName *string `json:"envName,omitempty"` - - // Name of the ClowdApp - Name *string `json:"name,omitempty"` +var enumValues_FeatureFlagsConfigScheme = []interface{}{ + "http", + "https", } // UnmarshalJSON implements json.Unmarshaler. -func (j *DeploymentMetadata) UnmarshalJSON(b []byte) error { +func (j *CloudWatchConfig) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["image"]; !ok || v == nil { - return fmt.Errorf("field image: required") + if v, ok := raw["accessKeyId"]; !ok || v == nil { + return fmt.Errorf("field accessKeyId in CloudWatchConfig: required") } - if v, ok := raw["name"]; !ok || v == nil { - return fmt.Errorf("field name: required") + if v, ok := raw["logGroup"]; !ok || v == nil { + return fmt.Errorf("field logGroup in CloudWatchConfig: required") } - type Plain DeploymentMetadata + if v, ok := raw["region"]; !ok || v == nil { + return fmt.Errorf("field region in CloudWatchConfig: required") + } + if v, ok := raw["secretAccessKey"]; !ok || v == nil { + return fmt.Errorf("field secretAccessKey in CloudWatchConfig: required") + } + type Plain CloudWatchConfig var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = DeploymentMetadata(plain) + *j = CloudWatchConfig(plain) return nil } +var enumValues_BrokerConfigAuthtype = []interface{}{ + "sasl", +} + // UnmarshalJSON implements json.Unmarshaler. -func (j *FeatureFlagsConfig) UnmarshalJSON(b []byte) error { +func (j *LoggingConfig) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["hostname"]; !ok || v == nil { - return fmt.Errorf("field hostname: required") - } - if v, ok := raw["port"]; !ok || v == nil { - return fmt.Errorf("field port: required") - } - if v, ok := raw["scheme"]; !ok || v == nil { - return fmt.Errorf("field scheme: required") + if v, ok := raw["type"]; !ok || v == nil { + return fmt.Errorf("field type in LoggingConfig: required") } - type Plain FeatureFlagsConfig + type Plain LoggingConfig var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = FeatureFlagsConfig(plain) + *j = LoggingConfig(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. -func (j *LoggingConfig) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { +func (j *FeatureFlagsConfigScheme) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { return err } - if v, ok := raw["type"]; !ok || v == nil { - return fmt.Errorf("field type: required") + var ok bool + for _, expected := range enumValues_FeatureFlagsConfigScheme { + if reflect.DeepEqual(v, expected) { + ok = true + break + } } - type Plain LoggingConfig - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_FeatureFlagsConfigScheme, v) } - *j = LoggingConfig(plain) + *j = FeatureFlagsConfigScheme(v) return nil } // UnmarshalJSON implements json.Unmarshaler. -func (j *InMemoryDBConfig) UnmarshalJSON(b []byte) error { +func (j *DeploymentMetadata) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["hostname"]; !ok || v == nil { - return fmt.Errorf("field hostname: required") + if v, ok := raw["image"]; !ok || v == nil { + return fmt.Errorf("field image in DeploymentMetadata: required") } - if v, ok := raw["port"]; !ok || v == nil { - return fmt.Errorf("field port: required") + if v, ok := raw["name"]; !ok || v == nil { + return fmt.Errorf("field name in DeploymentMetadata: required") } - type Plain InMemoryDBConfig + type Plain DeploymentMetadata var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = InMemoryDBConfig(plain) + *j = DeploymentMetadata(plain) return nil } -type BrokerConfigAuthtype string - // UnmarshalJSON implements json.Unmarshaler. -func (j *CloudWatchConfig) UnmarshalJSON(b []byte) error { +func (j *DependencyEndpoint) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["accessKeyId"]; !ok || v == nil { - return fmt.Errorf("field accessKeyId: required") + if v, ok := raw["apiPath"]; !ok || v == nil { + return fmt.Errorf("field apiPath in DependencyEndpoint: required") } - if v, ok := raw["logGroup"]; !ok || v == nil { - return fmt.Errorf("field logGroup: required") + if v, ok := raw["app"]; !ok || v == nil { + return fmt.Errorf("field app in DependencyEndpoint: required") } - if v, ok := raw["region"]; !ok || v == nil { - return fmt.Errorf("field region: required") + if v, ok := raw["hostname"]; !ok || v == nil { + return fmt.Errorf("field hostname in DependencyEndpoint: required") } - if v, ok := raw["secretAccessKey"]; !ok || v == nil { - return fmt.Errorf("field secretAccessKey: required") + if v, ok := raw["name"]; !ok || v == nil { + return fmt.Errorf("field name in DependencyEndpoint: required") } - type Plain CloudWatchConfig + if v, ok := raw["port"]; !ok || v == nil { + return fmt.Errorf("field port in DependencyEndpoint: required") + } + type Plain DependencyEndpoint var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = CloudWatchConfig(plain) - return nil -} - -// UnmarshalJSON implements json.Unmarshaler. -func (j *BrokerConfigAuthtype) UnmarshalJSON(b []byte) error { - var v string - if err := json.Unmarshal(b, &v); err != nil { - return err - } - var ok bool - for _, expected := range enumValues_BrokerConfigAuthtype { - if reflect.DeepEqual(v, expected) { - ok = true - break - } - } - if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_BrokerConfigAuthtype, v) - } - *j = BrokerConfigAuthtype(v) + *j = DependencyEndpoint(plain) return nil } -const BrokerConfigAuthtypeSasl BrokerConfigAuthtype = "sasl" - -// Cloud Watch configuration -type CloudWatchConfig struct { - // Defines the access key that the app should use for configuring CloudWatch. - AccessKeyId string `json:"accessKeyId"` - - // Defines the logGroup that the app should use for configuring CloudWatch. - LogGroup string `json:"logGroup"` - - // Defines the region that the app should use for configuring CloudWatch. - Region string `json:"region"` - - // Defines the secret key that the app should use for configuring CloudWatch. - SecretAccessKey string `json:"secretAccessKey"` -} - -// Broker Configuration -type BrokerConfig struct { - // Authtype corresponds to the JSON schema field "authtype". - Authtype *BrokerConfigAuthtype `json:"authtype,omitempty"` - - // CA certificate trust list for broker in PEM format. If absent, client should - // use OS default trust list - Cacert *string `json:"cacert,omitempty"` - - // Hostname of kafka broker - Hostname string `json:"hostname"` - - // Port of kafka broker - Port *int `json:"port,omitempty"` - - // Sasl corresponds to the JSON schema field "sasl". - Sasl *KafkaSASLConfig `json:"sasl,omitempty"` - - // Broker security procotol, expect one of either: SASL_SSL, SSL - SecurityProtocol *string `json:"securityProtocol,omitempty"` -} - // UnmarshalJSON implements json.Unmarshaler. -func (j *BrokerConfig) UnmarshalJSON(b []byte) error { +func (j *FeatureFlagsConfig) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } if v, ok := raw["hostname"]; !ok || v == nil { - return fmt.Errorf("field hostname: required") + return fmt.Errorf("field hostname in FeatureFlagsConfig: required") } - type Plain BrokerConfig + if v, ok := raw["port"]; !ok || v == nil { + return fmt.Errorf("field port in FeatureFlagsConfig: required") + } + if v, ok := raw["scheme"]; !ok || v == nil { + return fmt.Errorf("field scheme in FeatureFlagsConfig: required") + } + type Plain FeatureFlagsConfig var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = BrokerConfig(plain) + *j = FeatureFlagsConfig(plain) return nil } // UnmarshalJSON implements json.Unmarshaler. -func (j *KafkaConfig) UnmarshalJSON(b []byte) error { +func (j *ObjectStoreBucket) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["brokers"]; !ok || v == nil { - return fmt.Errorf("field brokers: required") + if v, ok := raw["name"]; !ok || v == nil { + return fmt.Errorf("field name in ObjectStoreBucket: required") } - if v, ok := raw["topics"]; !ok || v == nil { - return fmt.Errorf("field topics: required") + if v, ok := raw["requestedName"]; !ok || v == nil { + return fmt.Errorf("field requestedName in ObjectStoreBucket: required") } - type Plain KafkaConfig + type Plain ObjectStoreBucket var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = KafkaConfig(plain) + *j = ObjectStoreBucket(plain) return nil } -// Database Configuration -type DatabaseConfig struct { - // Defines the pgAdmin password. - AdminPassword string `json:"adminPassword"` - - // Defines the pgAdmin username. - AdminUsername string `json:"adminUsername"` - - // Defines the hostname of the database configured for the ClowdApp. - Hostname string `json:"hostname"` - - // Defines the database name. - Name string `json:"name"` - - // Defines the password for the standard user. - Password string `json:"password"` - - // Defines the port of the database configured for the ClowdApp. - Port int `json:"port"` - - // Defines the CA used to access the database. - RdsCa *string `json:"rdsCa,omitempty"` - - // Defines the postgres SSL mode that should be used. - SslMode string `json:"sslMode"` - - // Defines a username with standard access to the database. - Username string `json:"username"` -} - -// Dependent service connection info -type DependencyEndpoint struct { - // The top level api path that the app should serve from /api/ - // (deprecated, use apiPaths) - ApiPath string `json:"apiPath"` - - // The list of API paths (each matching format: '/api/some-path/') that this app - // will serve requests from - ApiPaths []string `json:"apiPaths,omitempty"` - - // The app name of the ClowdApp hosting the service. - App string `json:"app"` - - // The hostname of the dependent service. - Hostname string `json:"hostname"` - - // The PodSpec name of the dependent service inside the ClowdApp. - Name string `json:"name"` - - // The port of the dependent service. - Port int `json:"port"` - - // The TLS port of the dependent service. - TlsPort *int `json:"tlsPort,omitempty"` -} - -// Deployment Metadata -type DeploymentMetadata struct { - // Image used by deployment - Image string `json:"image"` - - // Name of deployment - Name string `json:"name"` -} - -// Feature Flags Configuration -type FeatureFlagsConfig struct { - // Defines the client access token to use when connect to the FeatureFlags server - ClientAccessToken *string `json:"clientAccessToken,omitempty"` - - // Defines the hostname for the FeatureFlags server - Hostname string `json:"hostname"` - - // Defines the port for the FeatureFlags server - Port int `json:"port"` - - // Details the scheme to use for FeatureFlags http/https - Scheme FeatureFlagsConfigScheme `json:"scheme"` -} - -type FeatureFlagsConfigScheme string - -const FeatureFlagsConfigSchemeHttp FeatureFlagsConfigScheme = "http" -const FeatureFlagsConfigSchemeHttps FeatureFlagsConfigScheme = "https" - -// In Memory DB Configuration -type InMemoryDBConfig struct { - // Defines the hostname for the In Memory DB server configuration. - Hostname string `json:"hostname"` - - // Defines the password for the In Memory DB server configuration. - Password *string `json:"password,omitempty"` - - // Defines the port for the In Memory DB server configuration. - Port int `json:"port"` - - // Defines the sslMode used by the In Memory DB server coniguration - SslMode *bool `json:"sslMode,omitempty"` - - // Defines the username for the In Memory DB server configuration. - Username *string `json:"username,omitempty"` -} - -// Kafka Configuration -type KafkaConfig struct { - // Defines the brokers the app should connect to for Kafka services. - Brokers []BrokerConfig `json:"brokers"` - - // Defines a list of the topic configurations available to the application. - Topics []TopicConfig `json:"topics"` -} - -// SASL Configuration for Kafka -type KafkaSASLConfig struct { - // Broker SASL password - Password *string `json:"password,omitempty"` - - // Broker SASL mechanism, expect: SCRAM-SHA-512 - SaslMechanism *string `json:"saslMechanism,omitempty"` - - // Broker security protocol, expect one of either: SASL_SSL, SSL. DEPRECATED, use - // the top level securityProtocol field instead - SecurityProtocol *string `json:"securityProtocol,omitempty"` - - // Broker SASL username - Username *string `json:"username,omitempty"` -} - -// Logging Configuration -type LoggingConfig struct { - // Cloudwatch corresponds to the JSON schema field "cloudwatch". - Cloudwatch *CloudWatchConfig `json:"cloudwatch,omitempty"` - - // Defines the type of logging configuration - Type string `json:"type"` -} - -// Object Storage Bucket -type ObjectStoreBucket struct { - // Defines the access key for specificed bucket. - AccessKey *string `json:"accessKey,omitempty"` - - // Defines the endpoint for the Object Storage server configuration. - Endpoint *string `json:"endpoint,omitempty"` - - // The actual name of the bucket being accessed. - Name string `json:"name"` - - // Defines the region for the specified bucket. - Region *string `json:"region,omitempty"` - - // The name that was requested for the bucket in the ClowdApp. - RequestedName string `json:"requestedName"` - - // Defines the secret key for the specified bucket. - SecretKey *string `json:"secretKey,omitempty"` - - // Details if the Object Server uses TLS. - Tls *bool `json:"tls,omitempty"` -} - // Object Storage Configuration type ObjectStoreConfig struct { // Defines the access key for the Object Storage server configuration. - AccessKey *string `json:"accessKey,omitempty"` + AccessKey *string `json:"accessKey,omitempty" yaml:"accessKey,omitempty" mapstructure:"accessKey,omitempty"` // Buckets corresponds to the JSON schema field "buckets". - Buckets []ObjectStoreBucket `json:"buckets,omitempty"` + Buckets []ObjectStoreBucket `json:"buckets,omitempty" yaml:"buckets,omitempty" mapstructure:"buckets,omitempty"` // Defines the hostname for the Object Storage server configuration. - Hostname string `json:"hostname"` + Hostname string `json:"hostname" yaml:"hostname" mapstructure:"hostname"` // Defines the port for the Object Storage server configuration. - Port int `json:"port"` + Port int `json:"port" yaml:"port" mapstructure:"port"` // Defines the secret key for the Object Storage server configuration. - SecretKey *string `json:"secretKey,omitempty"` + SecretKey *string `json:"secretKey,omitempty" yaml:"secretKey,omitempty" mapstructure:"secretKey,omitempty"` // Details if the Object Server uses TLS. - Tls bool `json:"tls"` + Tls bool `json:"tls" yaml:"tls" mapstructure:"tls"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ObjectStoreConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["hostname"]; !ok || v == nil { + return fmt.Errorf("field hostname in ObjectStoreConfig: required") + } + if v, ok := raw["port"]; !ok || v == nil { + return fmt.Errorf("field port in ObjectStoreConfig: required") + } + if v, ok := raw["tls"]; !ok || v == nil { + return fmt.Errorf("field tls in ObjectStoreConfig: required") + } + type Plain ObjectStoreConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ObjectStoreConfig(plain) + return nil } // Dependent service connection info type PrivateDependencyEndpoint struct { // The app name of the ClowdApp hosting the service. - App string `json:"app"` + App string `json:"app" yaml:"app" mapstructure:"app"` // The hostname of the dependent service. - Hostname string `json:"hostname"` + Hostname string `json:"hostname" yaml:"hostname" mapstructure:"hostname"` // The PodSpec name of the dependent service inside the ClowdApp. - Name string `json:"name"` + Name string `json:"name" yaml:"name" mapstructure:"name"` // The port of the dependent service. - Port int `json:"port"` + Port int `json:"port" yaml:"port" mapstructure:"port"` // The TLS port of the dependent service. - TlsPort *int `json:"tlsPort,omitempty"` + TlsPort *int `json:"tlsPort,omitempty" yaml:"tlsPort,omitempty" mapstructure:"tlsPort,omitempty"` } -// Topic Configuration -type TopicConfig struct { - // The name of the actual topic on the Kafka server. - Name string `json:"name"` - - // The name that the app requested in the ClowdApp definition. - RequestedName string `json:"requestedName"` +// UnmarshalJSON implements json.Unmarshaler. +func (j *PrivateDependencyEndpoint) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["app"]; !ok || v == nil { + return fmt.Errorf("field app in PrivateDependencyEndpoint: required") + } + if v, ok := raw["hostname"]; !ok || v == nil { + return fmt.Errorf("field hostname in PrivateDependencyEndpoint: required") + } + if v, ok := raw["name"]; !ok || v == nil { + return fmt.Errorf("field name in PrivateDependencyEndpoint: required") + } + if v, ok := raw["port"]; !ok || v == nil { + return fmt.Errorf("field port in PrivateDependencyEndpoint: required") + } + type Plain PrivateDependencyEndpoint + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = PrivateDependencyEndpoint(plain) + return nil } -var enumValues_BrokerConfigAuthtype = []interface{}{ - "sasl", -} -var enumValues_FeatureFlagsConfigScheme = []interface{}{ - "http", - "https", +// UnmarshalJSON implements json.Unmarshaler. +func (j *DatabaseConfig) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if v, ok := raw["adminPassword"]; !ok || v == nil { + return fmt.Errorf("field adminPassword in DatabaseConfig: required") + } + if v, ok := raw["adminUsername"]; !ok || v == nil { + return fmt.Errorf("field adminUsername in DatabaseConfig: required") + } + if v, ok := raw["hostname"]; !ok || v == nil { + return fmt.Errorf("field hostname in DatabaseConfig: required") + } + if v, ok := raw["name"]; !ok || v == nil { + return fmt.Errorf("field name in DatabaseConfig: required") + } + if v, ok := raw["password"]; !ok || v == nil { + return fmt.Errorf("field password in DatabaseConfig: required") + } + if v, ok := raw["port"]; !ok || v == nil { + return fmt.Errorf("field port in DatabaseConfig: required") + } + if v, ok := raw["sslMode"]; !ok || v == nil { + return fmt.Errorf("field sslMode in DatabaseConfig: required") + } + if v, ok := raw["username"]; !ok || v == nil { + return fmt.Errorf("field username in DatabaseConfig: required") + } + type Plain DatabaseConfig + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = DatabaseConfig(plain) + return nil } // UnmarshalJSON implements json.Unmarshaler. @@ -696,13 +697,13 @@ func (j *AppConfig) UnmarshalJSON(b []byte) error { return err } if v, ok := raw["logging"]; !ok || v == nil { - return fmt.Errorf("field logging: required") + return fmt.Errorf("field logging in AppConfig: required") } if v, ok := raw["metricsPath"]; !ok || v == nil { - return fmt.Errorf("field metricsPath: required") + return fmt.Errorf("field metricsPath in AppConfig: required") } if v, ok := raw["metricsPort"]; !ok || v == nil { - return fmt.Errorf("field metricsPort: required") + return fmt.Errorf("field metricsPort in AppConfig: required") } type Plain AppConfig var plain Plain From 6426932d25baba8cb739341bb417cfef1141f90b Mon Sep 17 00:00:00 2001 From: Viliam Krizan Date: Wed, 4 Dec 2024 14:56:56 +0100 Subject: [PATCH 2/6] refactor(providers): const provutils.DefaultPGPort --- controllers/cloud.redhat.com/providers/database/localdb.go | 2 +- controllers/cloud.redhat.com/providers/database/shareddb.go | 4 ++-- .../providers/featureflags/localfeatureflags.go | 2 +- controllers/cloud.redhat.com/providers/utils/utils.go | 1 + .../cloud.redhat.com/providers/web/resources_keycloak.go | 4 ++-- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/controllers/cloud.redhat.com/providers/database/localdb.go b/controllers/cloud.redhat.com/providers/database/localdb.go index 598d48dc7..d7d160805 100644 --- a/controllers/cloud.redhat.com/providers/database/localdb.go +++ b/controllers/cloud.redhat.com/providers/database/localdb.go @@ -88,7 +88,7 @@ func (db *localDbProvider) Provide(app *crd.ClowdApp) error { dataInit := func() map[string]string { hostname := fmt.Sprintf("%v.%v.svc", nn.Name, nn.Namespace) - port := "5432" + port := provutils.DefaultPGPort username := utils.RandString(16) name := app.Spec.Database.Name diff --git a/controllers/cloud.redhat.com/providers/database/shareddb.go b/controllers/cloud.redhat.com/providers/database/shareddb.go index 7e9bab504..6b19ea0b7 100644 --- a/controllers/cloud.redhat.com/providers/database/shareddb.go +++ b/controllers/cloud.redhat.com/providers/database/shareddb.go @@ -86,7 +86,7 @@ func createVersionedDatabase(p *providers.Provider, version int32) (*config.Data dataInit := func() map[string]string { return map[string]string{ "hostname": fmt.Sprintf("%v.%v.svc", nn.Name, nn.Namespace), - "port": "5432", + "port": provutils.DefaultPGPort, "username": utils.RandString(16), "password": password, "pgPass": pgPassword, @@ -308,7 +308,7 @@ func (db *sharedDbProvider) Provide(app *crd.ClowdApp) error { secret.StringData = map[string]string{ "hostname": host, - "port": "5432", + "port": provutils.DefaultPGPort, "username": dbCfg.Username, "password": dbCfg.Password, "pgPass": dbCfg.AdminPassword, diff --git a/controllers/cloud.redhat.com/providers/featureflags/localfeatureflags.go b/controllers/cloud.redhat.com/providers/featureflags/localfeatureflags.go index 9f3f872a2..ca2fc659d 100644 --- a/controllers/cloud.redhat.com/providers/featureflags/localfeatureflags.go +++ b/controllers/cloud.redhat.com/providers/featureflags/localfeatureflags.go @@ -115,7 +115,7 @@ func (ff *localFeatureFlagsProvider) EnvProvide() error { return map[string]string{ "hostname": hostname, - "port": "5432", + "port": provutils.DefaultPGPort, "username": username, "password": password, "pgPass": pgPassword, diff --git a/controllers/cloud.redhat.com/providers/utils/utils.go b/controllers/cloud.redhat.com/providers/utils/utils.go index d8816281c..309aedac3 100644 --- a/controllers/cloud.redhat.com/providers/utils/utils.go +++ b/controllers/cloud.redhat.com/providers/utils/utils.go @@ -34,6 +34,7 @@ var DefaultImageDatabasePG14 = "quay.io/cloudservices/postgresql-rds:14-2318dee" var DefaultImageDatabasePG15 = "quay.io/cloudservices/postgresql-rds:15-2318dee" var DefaultImageDatabasePG16 = "quay.io/cloudservices/postgresql-rds:16-759c25d" var DefaultImageInMemoryDB = "registry.redhat.io/rhel9/redis-6:1-199.1726663404" +var DefaultPGPort = "5432" // MakeLocalDB populates the given deployment object with the local DB struct. func MakeLocalDB(dd *apps.Deployment, nn types.NamespacedName, baseResource obj.ClowdObject, extraLabels *map[string]string, cfg *config.DatabaseConfig, image string, usePVC bool, dbName string, res *core.ResourceRequirements) { diff --git a/controllers/cloud.redhat.com/providers/web/resources_keycloak.go b/controllers/cloud.redhat.com/providers/web/resources_keycloak.go index c528fd96f..fcd7d63a1 100644 --- a/controllers/cloud.redhat.com/providers/web/resources_keycloak.go +++ b/controllers/cloud.redhat.com/providers/web/resources_keycloak.go @@ -82,7 +82,7 @@ func configureKeycloakDB(web *localWebProvider) error { return map[string]string{ "hostname": hostname, - "port": "5432", + "port": provutils.DefaultPGPort, "username": username, "password": password, "pgPass": pgPassword, @@ -273,7 +273,7 @@ func makeKeycloak(o obj.ClowdObject, objMap providers.ObjectMap, _ bool, nodePor }, { Name: "KC_DB_URL_PORT", - Value: "5432", + Value: provutils.DefaultPGPort, }, { Name: "PROXY_ADDRESS_FORWARDING", From bbca87b1e002c9c82af5417ea05b84a5ad4b13d9 Mon Sep 17 00:00:00 2001 From: Viliam Krizan Date: Wed, 4 Dec 2024 17:19:45 +0100 Subject: [PATCH 3/6] refactor(providers): const provutils.DefaultPGAdminUsername --- controllers/cloud.redhat.com/providers/database/localdb.go | 4 ++-- controllers/cloud.redhat.com/providers/database/shareddb.go | 6 +++--- controllers/cloud.redhat.com/providers/utils/utils.go | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/controllers/cloud.redhat.com/providers/database/localdb.go b/controllers/cloud.redhat.com/providers/database/localdb.go index d7d160805..11fadd021 100644 --- a/controllers/cloud.redhat.com/providers/database/localdb.go +++ b/controllers/cloud.redhat.com/providers/database/localdb.go @@ -116,7 +116,7 @@ func (db *localDbProvider) Provide(app *crd.ClowdApp) error { if err != nil { return errors.Wrap("couldn't convert to int", err) } - dbCfg.AdminUsername = "postgres" + dbCfg.AdminUsername = provutils.DefaultPGAdminUsername dbCfg.SslMode = "disable" var image string @@ -215,7 +215,7 @@ func (db *localDbProvider) processSharedDB(app *crd.ClowdApp) error { if err != nil { return errors.Wrap("couldn't convert to int", err) } - dbCfg.AdminUsername = "postgres" + dbCfg.AdminUsername = provutils.DefaultPGAdminUsername db.Config.Database = &dbCfg diff --git a/controllers/cloud.redhat.com/providers/database/shareddb.go b/controllers/cloud.redhat.com/providers/database/shareddb.go index 6b19ea0b7..c5d203483 100644 --- a/controllers/cloud.redhat.com/providers/database/shareddb.go +++ b/controllers/cloud.redhat.com/providers/database/shareddb.go @@ -103,7 +103,7 @@ func createVersionedDatabase(p *providers.Provider, version int32) (*config.Data if err != nil { return nil, errors.Wrap("couldn't convert to int", err) } - dbCfg.AdminUsername = "postgres" + dbCfg.AdminUsername = provutils.DefaultPGAdminUsername dbCfg.SslMode = "disable" var image string @@ -243,7 +243,7 @@ func (db *sharedDbProvider) Provide(app *crd.ClowdApp) error { return err } - dbCfg.AdminUsername = "postgres" + dbCfg.AdminUsername = provutils.DefaultPGAdminUsername dbCfg.AdminPassword = string(vSec.Data["pgPass"]) dbCfg.Hostname = string(vSec.Data["hostname"]) dbCfg.Name = app.Spec.Database.Name @@ -369,7 +369,7 @@ func (db *sharedDbProvider) processSharedDB(app *crd.ClowdApp) error { if err != nil { return errors.Wrap("couldn't convert to int", err) } - dbCfg.AdminUsername = "postgres" + dbCfg.AdminUsername = provutils.DefaultPGAdminUsername db.Config.Database = &dbCfg diff --git a/controllers/cloud.redhat.com/providers/utils/utils.go b/controllers/cloud.redhat.com/providers/utils/utils.go index 309aedac3..3a4792bc5 100644 --- a/controllers/cloud.redhat.com/providers/utils/utils.go +++ b/controllers/cloud.redhat.com/providers/utils/utils.go @@ -35,6 +35,7 @@ var DefaultImageDatabasePG15 = "quay.io/cloudservices/postgresql-rds:15-2318dee" var DefaultImageDatabasePG16 = "quay.io/cloudservices/postgresql-rds:16-759c25d" var DefaultImageInMemoryDB = "registry.redhat.io/rhel9/redis-6:1-199.1726663404" var DefaultPGPort = "5432" +var DefaultPGAdminUsername = "postgres" // MakeLocalDB populates the given deployment object with the local DB struct. func MakeLocalDB(dd *apps.Deployment, nn types.NamespacedName, baseResource obj.ClowdObject, extraLabels *map[string]string, cfg *config.DatabaseConfig, image string, usePVC bool, dbName string, res *core.ResourceRequirements) { From c4ed3ca0fa1bee1e55b58d43de0c52586eb3ac0b Mon Sep 17 00:00:00 2001 From: Viliam Krizan Date: Fri, 6 Dec 2024 15:01:12 +0100 Subject: [PATCH 4/6] refactor(providers): introduce provutils.PGAdminConnectionStr --- .../cloud.redhat.com/providers/database/shareddb.go | 11 +++-------- .../cloud.redhat.com/providers/utils/utils.go | 13 +++++++++++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/controllers/cloud.redhat.com/providers/database/shareddb.go b/controllers/cloud.redhat.com/providers/database/shareddb.go index c5d203483..672b53294 100644 --- a/controllers/cloud.redhat.com/providers/database/shareddb.go +++ b/controllers/cloud.redhat.com/providers/database/shareddb.go @@ -252,12 +252,8 @@ func (db *sharedDbProvider) Provide(app *crd.ClowdApp) error { dbCfg.Port = int(port) dbCfg.SslMode = "disable" - host := dbCfg.Hostname - user := dbCfg.AdminUsername - password := dbCfg.AdminPassword dbname := app.Spec.Database.Name - - appSQLConnectionString := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbname) + appSQLConnectionString := provutils.PGAdminConnectionStr(&dbCfg, dbname) ctx, cancel := context.WithTimeout(db.Ctx, 5*time.Second) defer cancel() @@ -273,8 +269,7 @@ func (db *sharedDbProvider) Provide(app *crd.ClowdApp) error { if pErr != nil { if strings.Contains(pErr.Error(), fmt.Sprintf("database \"%s\" does not exist", app.Spec.Database.Name)) { - envSQLConnectionString := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, db.Env.Name) - + envSQLConnectionString := provutils.PGAdminConnectionStr(&dbCfg, db.Env.Name) envDbClient, envErr := sql.Open("postgres", envSQLConnectionString) if envErr != nil { return envErr @@ -307,7 +302,7 @@ func (db *sharedDbProvider) Provide(app *crd.ClowdApp) error { } secret.StringData = map[string]string{ - "hostname": host, + "hostname": dbCfg.Hostname, "port": provutils.DefaultPGPort, "username": dbCfg.Username, "password": dbCfg.Password, diff --git a/controllers/cloud.redhat.com/providers/utils/utils.go b/controllers/cloud.redhat.com/providers/utils/utils.go index 3a4792bc5..842520882 100644 --- a/controllers/cloud.redhat.com/providers/utils/utils.go +++ b/controllers/cloud.redhat.com/providers/utils/utils.go @@ -11,6 +11,7 @@ import ( "github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/providers" "github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/providers/sizing" "github.com/go-logr/logr" + "github.com/lib/pq" apps "k8s.io/api/apps/v1" core "k8s.io/api/core/v1" @@ -175,6 +176,18 @@ func MakeLocalDBPVC(pvc *core.PersistentVolumeClaim, nn types.NamespacedName, ba utils.MakePVC(pvc, nn, providers.Labels{"service": "db", "app": baseResource.GetClowdName()}, capacity, baseResource) } +func PGAdminConnectionStr(cfg *config.DatabaseConfig, dbname string) string { + return fmt.Sprintf( + "host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", + pq.QuoteLiteral(cfg.Hostname), + cfg.Port, + pq.QuoteLiteral(cfg.AdminUsername), + pq.QuoteLiteral(cfg.AdminPassword), + pq.QuoteLiteral(dbname), + pq.QuoteLiteral(cfg.SslMode), + ) +} + // GetCaddyImage returns the caddy image to use in a given environment func GetCaddyGatewayImage(env *crd.ClowdEnvironment) string { if env.Spec.Providers.Web.Images.CaddyGateway != "" { From 120878e5baa4cf9a0347b3f1c66c5bd77e664e78 Mon Sep 17 00:00:00 2001 From: Viliam Krizan Date: Mon, 9 Dec 2024 16:11:09 +0100 Subject: [PATCH 5/6] refactor(providers): introduce provutils.ReadDbConfigFromSecret --- .../providers/database/localdb.go | 24 +++----------- .../providers/database/shareddb.go | 23 +++----------- .../cloud.redhat.com/providers/utils/utils.go | 31 +++++++++++++++++++ 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/controllers/cloud.redhat.com/providers/database/localdb.go b/controllers/cloud.redhat.com/providers/database/localdb.go index 11fadd021..03026893d 100644 --- a/controllers/cloud.redhat.com/providers/database/localdb.go +++ b/controllers/cloud.redhat.com/providers/database/localdb.go @@ -183,39 +183,23 @@ func (db *localDbProvider) processSharedDB(app *crd.ClowdApp) error { return err } - dbCfg := config.DatabaseConfig{} - dbCfg.SslMode = "disable" - refApp, err := crd.GetAppForDBInSameEnv(db.Ctx, db.Client, app) - if err != nil { return err } - secret := core.Secret{} - + dbCfg := config.DatabaseConfig{} inn := types.NamespacedName{ Name: fmt.Sprintf("%s-db", refApp.Name), Namespace: refApp.Namespace, } // This is a REAL call here, not a cached call as the reconciliation must have been processed - // for the app we depend on. - if err = db.Client.Get(db.Ctx, inn, &secret); err != nil { - return errors.Wrap("Couldn't set/get secret", err) - } - - secMap := make(map[string]string) - - for k, v := range secret.Data { - (secMap)[k] = string(v) - } - - err = dbCfg.Populate(&secMap) + // for the app we depend on, hence the nil for the ident. + err = provutils.ReadDbConfigFromSecret(db.Provider, nil, &dbCfg, inn) if err != nil { - return errors.Wrap("couldn't convert to int", err) + return err } - dbCfg.AdminUsername = provutils.DefaultPGAdminUsername db.Config.Database = &dbCfg diff --git a/controllers/cloud.redhat.com/providers/database/shareddb.go b/controllers/cloud.redhat.com/providers/database/shareddb.go index 672b53294..52e16e95f 100644 --- a/controllers/cloud.redhat.com/providers/database/shareddb.go +++ b/controllers/cloud.redhat.com/providers/database/shareddb.go @@ -332,39 +332,24 @@ func (db *sharedDbProvider) processSharedDB(app *crd.ClowdApp) error { return err } - dbCfg := config.DatabaseConfig{} - dbCfg.SslMode = "disable" - refApp, err := crd.GetAppForDBInSameEnv(db.Ctx, db.Client, app) if err != nil { return err } - secret := core.Secret{} - + dbCfg := config.DatabaseConfig{} inn := types.NamespacedName{ Name: fmt.Sprintf("%s-db", refApp.Name), Namespace: refApp.Namespace, } // This is a REAL call here, not a cached call as the reconciliation must have been processed - // for the app we depend on. - if err = db.Client.Get(db.Ctx, inn, &secret); err != nil { - return errors.Wrap("Couldn't set/get secret", err) - } - - secMap := make(map[string]string) - - for k, v := range secret.Data { - (secMap)[k] = string(v) - } - - err = dbCfg.Populate(&secMap) + // for the app we depend on, hence the nil for the ident. + err = provutils.ReadDbConfigFromSecret(db.Provider, nil, &dbCfg, inn) if err != nil { - return errors.Wrap("couldn't convert to int", err) + return err } - dbCfg.AdminUsername = provutils.DefaultPGAdminUsername db.Config.Database = &dbCfg diff --git a/controllers/cloud.redhat.com/providers/utils/utils.go b/controllers/cloud.redhat.com/providers/utils/utils.go index 842520882..34ccaa039 100644 --- a/controllers/cloud.redhat.com/providers/utils/utils.go +++ b/controllers/cloud.redhat.com/providers/utils/utils.go @@ -7,6 +7,7 @@ import ( crd "github.com/RedHatInsights/clowder/apis/cloud.redhat.com/v1alpha1" "github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/clowderconfig" "github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/config" + "github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/errors" obj "github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/object" "github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/providers" "github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/providers/sizing" @@ -19,6 +20,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + rc "github.com/RedHatInsights/rhc-osdk-utils/resourceCache" "github.com/RedHatInsights/rhc-osdk-utils/utils" ) @@ -188,6 +190,35 @@ func PGAdminConnectionStr(cfg *config.DatabaseConfig, dbname string) string { ) } +func ReadDbConfigFromSecret(p providers.Provider, resourceIdent rc.ResourceIdent, dbCfg *config.DatabaseConfig, nn types.NamespacedName) error { + secret := &core.Secret{} + + var err error + if resourceIdent != nil { + err = p.Cache.Get(resourceIdent, secret, nn) + } else { + err = fmt.Errorf("no cache") + } + if err != nil { + if err := p.Client.Get(p.Ctx, nn, secret); err != nil { + return errors.Wrap("couldn't get db secret", err) + } + } + + secMap := make(map[string]string) + for k, v := range secret.Data { + (secMap)[k] = string(v) + } + + if err := dbCfg.Populate(&secMap); err != nil { + return errors.Wrap("couldn't convert to int", err) + } + dbCfg.AdminUsername = DefaultPGAdminUsername + dbCfg.SslMode = "disable" + + return nil +} + // GetCaddyImage returns the caddy image to use in a given environment func GetCaddyGatewayImage(env *crd.ClowdEnvironment) string { if env.Spec.Providers.Web.Images.CaddyGateway != "" { From e270e2af4182a0da6490fa161cc054da55ba6bb6 Mon Sep 17 00:00:00 2001 From: Viliam Krizan Date: Fri, 6 Dec 2024 19:32:30 +0100 Subject: [PATCH 6/6] feat(providers): single database mode Creates a new database provider mode named `single`. The aim of the mode is to create a single database instance and share it between the apps by separating them by Posgres schemas. Each app gets a schema created by the name of the requested database. Schemas are created under environment's database, with the same name as the ClowdEnv. RHINENG-14526 --- .../v1alpha1/clowdenvironment_types.go | 9 +- .../cloud.redhat.com_clowdenvironments.yaml | 13 +- .../providers/database/provider.go | 2 + .../providers/database/single.go | 302 ++++++++++++++++++ .../cloud.redhat.com/providers/utils/utils.go | 1 + deploy-mutate.yml | 7 +- deploy.yml | 7 +- docs/api_reference.md | 4 +- docs/providers/database.md | 13 +- 9 files changed, 343 insertions(+), 15 deletions(-) create mode 100644 controllers/cloud.redhat.com/providers/database/single.go diff --git a/apis/cloud.redhat.com/v1alpha1/clowdenvironment_types.go b/apis/cloud.redhat.com/v1alpha1/clowdenvironment_types.go index c6f7ce013..b2d16ca98 100644 --- a/apis/cloud.redhat.com/v1alpha1/clowdenvironment_types.go +++ b/apis/cloud.redhat.com/v1alpha1/clowdenvironment_types.go @@ -284,7 +284,7 @@ type KafkaConfig struct { } // DatabaseMode details the mode of operation of the Clowder Database Provider -// +kubebuilder:validation:Enum=shared;app-interface;local;none +// +kubebuilder:validation:Enum=single;shared;app-interface;local;none type DatabaseMode string // DatabaseConfig configures the Clowder provider controlling the creation of @@ -292,8 +292,11 @@ type DatabaseMode string type DatabaseConfig struct { // The mode of operation of the Clowder Database Provider. Valid options are: // (*_app-interface_*) where the provider will pass through database credentials - // found in the secret defined by the database name in the ClowdApp, and (*_local_*) - // where the provider will spin up a local instance of the database. + // found in the secret defined by the database name in the ClowdApp, (*_local_*) + // where the provider will spin up a local instance of the database, (*_shared_*) + // where the provider will spin up only one shared instance per requested + // PostgreSQL version, and (*_single_*) that is similar to local and shared modes + // but keeps only one database deployment and isolates apps by PG schemas. Mode DatabaseMode `json:"mode"` // Indicates where Clowder will fetch the database CA certificate bundle from. Currently only used in diff --git a/config/crd/bases/cloud.redhat.com_clowdenvironments.yaml b/config/crd/bases/cloud.redhat.com_clowdenvironments.yaml index 1f38f3bdd..c8b303ec4 100644 --- a/config/crd/bases/cloud.redhat.com_clowdenvironments.yaml +++ b/config/crd/bases/cloud.redhat.com_clowdenvironments.yaml @@ -81,12 +81,15 @@ spec: type: string mode: description: 'The mode of operation of the Clowder Database - Provider. Valid options are: (*_app-interface_*) where the - provider will pass through database credentials found in - the secret defined by the database name in the ClowdApp, - and (*_local_*) where the provider will spin up a local - instance of the database.' + Provider. Valid options are: (*_app-interface_*) where + the provider will pass through database credentials found + in the secret defined by the database name in the + ClowdApp, (*_local_*) where the provider will spin up a + local instance of the database, and (*_single_*) that is + similar to local but keeps only one database deployment + and isolates apps by PG schemas.' enum: + - single - shared - app-interface - local diff --git a/controllers/cloud.redhat.com/providers/database/provider.go b/controllers/cloud.redhat.com/providers/database/provider.go index 7123a2013..b1ec14eaf 100644 --- a/controllers/cloud.redhat.com/providers/database/provider.go +++ b/controllers/cloud.redhat.com/providers/database/provider.go @@ -18,6 +18,8 @@ var imageList map[int32]string func GetDatabase(c *p.Provider) (p.ClowderProvider, error) { dbMode := c.Env.Spec.Providers.Database.Mode switch dbMode { + case "single": + return NewSingleDBProvider(c) case "shared": return NewSharedDBProvider(c) case "local": diff --git a/controllers/cloud.redhat.com/providers/database/single.go b/controllers/cloud.redhat.com/providers/database/single.go new file mode 100644 index 000000000..6a36a4e75 --- /dev/null +++ b/controllers/cloud.redhat.com/providers/database/single.go @@ -0,0 +1,302 @@ +package database + +import ( + "context" + "database/sql" + "fmt" + "time" + + "github.com/lib/pq" + + crd "github.com/RedHatInsights/clowder/apis/cloud.redhat.com/v1alpha1" + "github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/config" + "github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/errors" + obj "github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/object" + "github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/providers" + "github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/providers/sizing" + "github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/providers/sizing/sizingconfig" + provutils "github.com/RedHatInsights/clowder/controllers/cloud.redhat.com/providers/utils" + + apps "k8s.io/api/apps/v1" + core "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + rc "github.com/RedHatInsights/rhc-osdk-utils/resourceCache" + "github.com/RedHatInsights/rhc-osdk-utils/utils" +) + +// SingleDBDeployment is the ident referring to the single local DB deployment object +var SingleDBDeployment = rc.NewSingleResourceIdent(ProvName, "single_db_deployment", &apps.Deployment{}) + +// SingleDBScret is the ident referring to the single local DB secret object +var SingleDBSecret = rc.NewMultiResourceIdent(ProvName, "single_db_secret", &core.Secret{}) + +// SingleDBPVC is the ident referring to the single local DB PersitentVolumeClaim +var SingleDBPVC = rc.NewSingleResourceIdent(ProvName, "single_db_pvc", &core.PersistentVolumeClaim{}) + +// SingleDBService is the ident referring to the single local DB service object. +var SingleDBService = rc.NewMultiResourceIdent(ProvName, "single_db_service", &core.Service{}) + +func NewSingleDBProvider(p *providers.Provider) (providers.ClowderProvider, error) { + p.Cache.AddPossibleGVKFromIdent( + SingleDBDeployment, + SingleDBSecret, + SingleDBPVC, + SingleDBService, + ) + return &singleDbProvider{Provider: *p}, nil +} + +type singleDbProvider struct { + providers.Provider +} + +func (db *singleDbProvider) DBNamespacedName() types.NamespacedName { + return types.NamespacedName{ + Name: fmt.Sprintf("%s-db-single", db.Env.Name), + Namespace: db.Env.Status.TargetNamespace, + } +} + +func (db *singleDbProvider) EnvProvide() error { + appList, err := db.Env.GetAppsInEnv(db.Ctx, db.Client) + if err != nil { + return err + } + + needsDb := false + for _, app := range appList.Items { + if app.Spec.Database.Name != "" { + needsDb = true + break + } + } + + if needsDb { + if _, err := db.createDatabaseDeployment(); err != nil { + return err + } + if db.Env.Spec.Providers.Database.PVC { + if err := db.createDatabasePVC(); err != nil { + return err + } + } + } + return nil +} + +// Creates a single database deployment locked to one version with a main secret +func (db *singleDbProvider) createDatabaseDeployment() (*config.DatabaseConfig, error) { + nn := db.DBNamespacedName() + + dd := &apps.Deployment{} + ownerrefs := []metav1.OwnerReference{db.Env.MakeOwnerReference()} + dd.ObjectMeta.OwnerReferences = ownerrefs + err := db.Cache.Create(SingleDBDeployment, nn, dd) + if err != nil { + return nil, err + } + + dbName := db.Env.Name + dbCfg, err := db.createOrReadDbConfig(db.Env, nn, dbName, true) + if err != nil { + return nil, err + } + + labels := &map[string]string{ + "sub": "single_db", + } + usePVC := db.Env.Spec.Providers.Database.PVC + provutils.MakeLocalDB(dd, nn, db.Env, labels, dbCfg, provutils.DefaultImageDatabasePG, usePVC, dbName, nil) + if err := db.Cache.Update(SingleDBDeployment, dd); err != nil { + return dbCfg, err + } + + svc := &core.Service{} + svc.ObjectMeta.OwnerReferences = ownerrefs + if err := db.Cache.Create(SingleDBService, nn, svc); err != nil { + return dbCfg, err + } + + provutils.MakeLocalDBService(svc, nn, db.Env, labels) + + if err = db.Cache.Update(SingleDBService, svc); err != nil { + return dbCfg, err + } + + return dbCfg, nil +} + +func (db *singleDbProvider) createDatabasePVC() error { + nn := db.DBNamespacedName() + + pvc := &core.PersistentVolumeClaim{} + if err := db.Cache.Create(SingleDBPVC, nn, pvc); err != nil { + return err + } + + // TODO handle volume capacity + capacity := sizing.GetVolCapacityForSize(sizingconfig.SizeLarge) + provutils.MakeLocalDBPVC(pvc, nn, db.Env, capacity) + if err := db.Cache.Update(SingleDBPVC, pvc); err != nil { + return err + } + return nil +} + +func (db *singleDbProvider) Provide(app *crd.ClowdApp) error { + if app.Spec.Database.SharedDBAppName != "" { + return db.processSharedDB(app) + } else if app.Spec.Database.Name != "" { + return db.provideAppDB(app) + } + return nil +} + +func (db *singleDbProvider) provideAppDB(app *crd.ClowdApp) error { + dbnn := db.DBNamespacedName() + dbCfg := &config.DatabaseConfig{} + + if err := provutils.ReadDbConfigFromSecret(db.Provider, SingleDBSecret, dbCfg, dbnn); err != nil { + return err + } + + // Create database config and secret for the app, + // without the admin credentials. + appnn := types.NamespacedName{ + Name: fmt.Sprintf("%v-db", app.Name), + Namespace: app.Namespace, + } + appDbCfg, err := db.createOrReadDbConfig(app, appnn, app.Name, false) + if err != nil { + return err + } + db.Config.Database = appDbCfg + + // reconcile access + if err := db.reconcileDBAppAccess(dbCfg, appDbCfg); err != nil { + return err + } + + return nil +} + +func (db *singleDbProvider) processSharedDB(app *crd.ClowdApp) error { + refApp, err := crd.GetAppForDBInSameEnv(db.Ctx, db.Client, app) + if err != nil { + return err + } + + nn := types.NamespacedName{ + Name: fmt.Sprintf("%v-db", refApp.Name), + Namespace: refApp.Namespace, + } + dbCfg := &config.DatabaseConfig{} + if err := provutils.ReadDbConfigFromSecret(db.Provider, SingleDBSecret, dbCfg, nn); err != nil { + return errors.Wrap(fmt.Sprintf("sharedDBApp %s", refApp.Name), err) + } + + db.Config.Database = dbCfg + return nil +} + +func (db *singleDbProvider) Hostname() string { + nn := db.DBNamespacedName() + return fmt.Sprintf("%v.%v.svc", nn.Name, nn.Namespace) +} + +func (db *singleDbProvider) createOrReadDbConfig(obj obj.ClowdObject, nn types.NamespacedName, username string, setAdmin bool) (cfg *config.DatabaseConfig, err error) { + cfg = &config.DatabaseConfig{} + password, err := utils.RandPassword(16, provutils.RCharSet) + if err != nil { + return cfg, errors.Wrap("password generate failed", err) + } + + var pgPassword string + if setAdmin { + pgPassword, err = utils.RandPassword(16, provutils.RCharSet) + if err != nil { + return cfg, errors.Wrap("pgPassword generate failed", err) + } + } + + dataInit := func() map[string]string { + return map[string]string{ + "hostname": db.Hostname(), + "port": provutils.DefaultPGPort, + "name": db.Env.Name, + "username": username, + "password": password, + "pgPass": pgPassword, + } + } + + var secMap *map[string]string + secMap, err = providers.MakeOrGetSecret(obj, db.Cache, SingleDBSecret, nn, dataInit) + if err != nil { + return cfg, errors.Wrap("couldn't set/get secret", err) + } + + err = cfg.Populate(secMap) + if err != nil { + return cfg, errors.Wrap("couldn't populate db config from secret", err) + } + if setAdmin { + cfg.AdminUsername = provutils.DefaultPGAdminUsername + } + cfg.SslMode = "disable" + return +} + +func (db *singleDbProvider) reconcileDBAppAccess(envCfg *config.DatabaseConfig, appCfg *config.DatabaseConfig) error { + appSQLConnectionString := provutils.PGAdminConnectionStr(envCfg, envCfg.Name) + + ctx, cancel := context.WithTimeout(db.Ctx, 5*time.Second) + defer cancel() + + dbClient, err := sql.Open("postgres", appSQLConnectionString) + if err != nil { + return errors.Wrap("unable ,to connect to db", err) + } + defer dbClient.Close() + + if err := dbClient.PingContext(ctx); err != nil { + return err + } + + username := appCfg.Username + password := appCfg.Password + rows, err := dbClient.QueryContext(ctx, "SELECT TRUE FROM pg_roles WHERE rolname = $1;", username) + if err != nil { + return errors.Wrap("unable to query for roles", err) + } + + var roleExists = rows.Next() + rows.Close() + + if roleExists { + _, err = dbClient.ExecContext(ctx, + fmt.Sprintf("ALTER ROLE %s WITH LOGIN ENCRYPTED PASSWORD %s;", + pq.QuoteIdentifier(username), pq.QuoteLiteral(password), + )) + } else { + _, err = dbClient.ExecContext(ctx, + fmt.Sprintf("CREATE ROLE %s WITH LOGIN ENCRYPTED PASSWORD %s;", + pq.QuoteIdentifier(username), pq.QuoteLiteral(password), + )) + } + if err != nil { + return errors.Wrap("unable to create/alter role", err) + } + + _, err = dbClient.ExecContext(ctx, + fmt.Sprintf("CREATE SCHEMA IF NOT EXISTS %s AUTHORIZATION %s;", + pq.QuoteIdentifier(username), pq.QuoteIdentifier(username), + )) + if err != nil { + return errors.Wrap("unable to create db schema for the app", err) + } + + return nil +} diff --git a/controllers/cloud.redhat.com/providers/utils/utils.go b/controllers/cloud.redhat.com/providers/utils/utils.go index 34ccaa039..5ba4b5d67 100644 --- a/controllers/cloud.redhat.com/providers/utils/utils.go +++ b/controllers/cloud.redhat.com/providers/utils/utils.go @@ -36,6 +36,7 @@ var DefaultImageDatabasePG13 = "quay.io/cloudservices/postgresql-rds:13-2318dee" var DefaultImageDatabasePG14 = "quay.io/cloudservices/postgresql-rds:14-2318dee" var DefaultImageDatabasePG15 = "quay.io/cloudservices/postgresql-rds:15-2318dee" var DefaultImageDatabasePG16 = "quay.io/cloudservices/postgresql-rds:16-759c25d" +var DefaultImageDatabasePG = DefaultImageDatabasePG16 var DefaultImageInMemoryDB = "registry.redhat.io/rhel9/redis-6:1-199.1726663404" var DefaultPGPort = "5432" var DefaultPGAdminUsername = "postgres" diff --git a/deploy-mutate.yml b/deploy-mutate.yml index bbcc6c7ab..fcf982968 100644 --- a/deploy-mutate.yml +++ b/deploy-mutate.yml @@ -6592,9 +6592,12 @@ objects: Provider. Valid options are: (*_app-interface_*) where the provider will pass through database credentials found in the secret defined by the database name in the ClowdApp, - and (*_local_*) where the provider will spin up a local - instance of the database.' + (*_local_*) where the provider will spin up a local instance + of the database, and (*_single_*) that is similar to local + but keeps only one database deployment and isolates apps + by PG schemas.' enum: + - single - shared - app-interface - local diff --git a/deploy.yml b/deploy.yml index 7cb8cd9d8..af022370d 100644 --- a/deploy.yml +++ b/deploy.yml @@ -6592,9 +6592,12 @@ objects: Provider. Valid options are: (*_app-interface_*) where the provider will pass through database credentials found in the secret defined by the database name in the ClowdApp, - and (*_local_*) where the provider will spin up a local - instance of the database.' + (*_local_*) where the provider will spin up a local instance + of the database, and (*_single_*) that is similar to local + but keeps only one database deployment and isolates apps + by PG schemas.' enum: + - single - shared - app-interface - local diff --git a/docs/api_reference.md b/docs/api_reference.md index f9fe2ce71..a367f2921 100644 --- a/docs/api_reference.md +++ b/docs/api_reference.md @@ -387,7 +387,7 @@ _Appears in:_ | Field | Description | Default | Validation | | --- | --- | --- | --- | -| `mode` _[DatabaseMode](#databasemode)_ | The mode of operation of the Clowder Database Provider. Valid options are:
(*_app-interface_*) where the provider will pass through database credentials
found in the secret defined by the database name in the ClowdApp, and (*_local_*)
where the provider will spin up a local instance of the database. | | Enum: [shared app-interface local none]
| +| `mode` _[DatabaseMode](#databasemode)_ | The mode of operation of the Clowder Database Provider. Valid options are:
(*_app-interface_*) where the provider will pass through database credentials
found in the secret defined by the database name in the ClowdApp, (*_local_*)
where the provider will spin up a local instance of the database, (*_shared_*)
where the provider will spin up only one shared instance per requested
PostgreSQL version, and (*_single_*) that is similar to local and shared modes
but keeps only one database deployment and isolates apps by PG schemas. | | Enum: [single shared app-interface local none]
| | `caBundleURL` _string_ | Indicates where Clowder will fetch the database CA certificate bundle from. Currently only used in
(*_app-interface_*) mode. If none is specified, the AWS RDS combined CA bundle is used. | | Pattern: `^https?:\/\/.+$`
| | `pvc` _boolean_ | If using the (*_local_*) mode and PVC is set to true, this instructs the local
Database instance to use a PVC instead of emptyDir for its volumes. | | | @@ -399,7 +399,7 @@ _Underlying type:_ _string_ DatabaseMode details the mode of operation of the Clowder Database Provider _Validation:_ -- Enum: [shared app-interface local none] +- Enum: [single shared app-interface local none] _Appears in:_ - [DatabaseConfig](#databaseconfig) diff --git a/docs/providers/database.md b/docs/providers/database.md index 626db4b0f..d558a2fd7 100644 --- a/docs/providers/database.md +++ b/docs/providers/database.md @@ -22,7 +22,7 @@ spec: ### Using a Shared Database across multiple ClowdApps -To share a database from one ClowdApp to another Clowder supports sharing a database +To share a database from one ClowdApp to another Clowder supports sharing a database declared in one ClowdApp with many others. Request a database like above, but then in your dependent ClowdApp resource set up @@ -64,6 +64,17 @@ ClowdEnv Config options available: - `pvc` +#### single + +(*EXPERIMENTAL*) + +In the single mode, the the **Database Provider** will provision a single node +PostgreSQL instance, of latest supported version, for the whole ClowdEnvironment. +Each ClowdApp requesting a database would get a separate user and +a [PostgreSQL schema](https://www.postgresql.org/docs/current/ddl-schemas.html) +with the same name as the user. The user has only access to create objects +only within its schema. Admin credentials are (currently) not provided. + #### shared In shared mode, the **Database Provider** will provision a single node PostgreSQL