diff --git a/api/v1alpha1/trillian_types.go b/api/v1alpha1/trillian_types.go index e65137978..e6d17706c 100644 --- a/api/v1alpha1/trillian_types.go +++ b/api/v1alpha1/trillian_types.go @@ -60,13 +60,24 @@ type TrillianDB struct { //+kubebuilder:default:=true //+kubebuilder:validation:XValidation:rule=(self == oldSelf),message=Field is immutable Create *bool `json:"create"` - // Secret with values to be used to connect to an existing DB or to be used with the creation of a new DB + // DB provider. Supported are mysql and postgresql. + //+kubebuilder:validation:Enum={postgresql,mysql} + //+kubebuilder:default:=mysql + Provider string `json:"provider,omitempty"` + // Secret with values to be used to connect to an existing DB or to be used with the creation of a new MySQL DB + // For MySQL, the secret must contain the following keys: // mysql-host: The host of the MySQL server // mysql-port: The port of the MySQL server // mysql-user: The user to connect to the MySQL server // mysql-password: The password to connect to the MySQL server // mysql-database: The database to connect to - //+optional + // For PostgreSQL, only connection is supported, the secret must contain: + // postgresql-host: The host of the PostgreSQL server + // postgresql-port: The port of the PostgreSQL server + // postgresql-user: The user to connect to the PostgreSQL server + // postgresql-password: The password to connect to the PostgreSQL server + // postgresql-database: The database to connect to + // +optional DatabaseSecretRef *LocalObjectReference `json:"databaseSecretRef,omitempty"` // PVC configuration //+kubebuilder:default:={size: "5Gi", retain: true} diff --git a/api/v1alpha1/trillian_types_test.go b/api/v1alpha1/trillian_types_test.go index fbe633574..88b539d78 100644 --- a/api/v1alpha1/trillian_types_test.go +++ b/api/v1alpha1/trillian_types_test.go @@ -14,6 +14,7 @@ import ( ) var _ = Describe("Trillian", func() { + const mysqlProvider = "mysql" Context("TrillianSpec", func() { It("can be created", func() { @@ -201,6 +202,7 @@ var _ = Describe("Trillian", func() { Namespace: "default", }, } + expectedTrillianInstance.Spec.Db.Provider = mysqlProvider Expect(k8sClient.Create(context.Background(), &trillianInstance)).To(Succeed()) fetched := &Trillian{} @@ -262,6 +264,7 @@ var _ = Describe("Trillian", func() { expectedTrillianInstance.Spec.Db.DatabaseSecretRef = &LocalObjectReference{ Name: "secret", } + expectedTrillianInstance.Spec.Db.Provider = mysqlProvider Expect(k8sClient.Create(context.Background(), &trillianInstance)).To(Succeed()) fetchedTrillian := &Trillian{} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 703888a57..4f650c13c 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -619,6 +619,7 @@ func (in *MonitoringWithTLogConfig) DeepCopyInto(out *MonitoringWithTLogConfig) *out = *in out.MonitoringConfig = in.MonitoringConfig out.TLog = in.TLog + in.Tuf.DeepCopyInto(&out.Tuf) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MonitoringWithTLogConfig. @@ -920,9 +921,8 @@ func (in *RekorSpec) DeepCopyInto(out *RekorSpec) { **out = **in } in.Trillian.DeepCopyInto(&out.Trillian) - in.Monitoring.Tuf.DeepCopyInto(&out.Monitoring.Tuf) in.ExternalAccess.DeepCopyInto(&out.ExternalAccess) - out.Monitoring = in.Monitoring + in.Monitoring.DeepCopyInto(&out.Monitoring) in.RekorSearchUI.DeepCopyInto(&out.RekorSearchUI) in.Signer.DeepCopyInto(&out.Signer) in.Attestations.DeepCopyInto(&out.Attestations) diff --git a/config/crd/bases/rhtas.redhat.com_securesigns.yaml b/config/crd/bases/rhtas.redhat.com_securesigns.yaml index d0a8c1963..c4435740f 100644 --- a/config/crd/bases/rhtas.redhat.com_securesigns.yaml +++ b/config/crd/bases/rhtas.redhat.com_securesigns.yaml @@ -5362,12 +5362,19 @@ spec: rule: (self == oldSelf) databaseSecretRef: description: |- - Secret with values to be used to connect to an existing DB or to be used with the creation of a new DB + Secret with values to be used to connect to an existing DB or to be used with the creation of a new MySQL DB + For MySQL, the secret must contain the following keys: mysql-host: The host of the MySQL server mysql-port: The port of the MySQL server mysql-user: The user to connect to the MySQL server mysql-password: The password to connect to the MySQL server mysql-database: The database to connect to + For PostgreSQL, only connection is supported, the secret must contain: + postgresql-host: The host of the PostgreSQL server + postgresql-port: The port of the PostgreSQL server + postgresql-user: The user to connect to the PostgreSQL server + postgresql-password: The password to connect to the PostgreSQL server + postgresql-database: The database to connect to properties: name: description: |- @@ -5378,6 +5385,13 @@ spec: - name type: object x-kubernetes-map-type: atomic + provider: + default: mysql + description: DB provider. Supported are mysql and postgresql. + enum: + - postgresql + - mysql + type: string pvc: default: retain: true diff --git a/config/crd/bases/rhtas.redhat.com_trillians.yaml b/config/crd/bases/rhtas.redhat.com_trillians.yaml index a24a71657..8f843f862 100644 --- a/config/crd/bases/rhtas.redhat.com_trillians.yaml +++ b/config/crd/bases/rhtas.redhat.com_trillians.yaml @@ -64,12 +64,19 @@ spec: rule: (self == oldSelf) databaseSecretRef: description: |- - Secret with values to be used to connect to an existing DB or to be used with the creation of a new DB + Secret with values to be used to connect to an existing DB or to be used with the creation of a new MySQL DB + For MySQL, the secret must contain the following keys: mysql-host: The host of the MySQL server mysql-port: The port of the MySQL server mysql-user: The user to connect to the MySQL server mysql-password: The password to connect to the MySQL server mysql-database: The database to connect to + For PostgreSQL, only connection is supported, the secret must contain: + postgresql-host: The host of the PostgreSQL server + postgresql-port: The port of the PostgreSQL server + postgresql-user: The user to connect to the PostgreSQL server + postgresql-password: The password to connect to the PostgreSQL server + postgresql-database: The database to connect to properties: name: description: |- @@ -80,6 +87,13 @@ spec: - name type: object x-kubernetes-map-type: atomic + provider: + default: mysql + description: DB provider. Supported are mysql and postgresql. + enum: + - postgresql + - mysql + type: string pvc: default: retain: true @@ -2441,12 +2455,19 @@ spec: rule: (self == oldSelf) databaseSecretRef: description: |- - Secret with values to be used to connect to an existing DB or to be used with the creation of a new DB + Secret with values to be used to connect to an existing DB or to be used with the creation of a new MySQL DB + For MySQL, the secret must contain the following keys: mysql-host: The host of the MySQL server mysql-port: The port of the MySQL server mysql-user: The user to connect to the MySQL server mysql-password: The password to connect to the MySQL server mysql-database: The database to connect to + For PostgreSQL, only connection is supported, the secret must contain: + postgresql-host: The host of the PostgreSQL server + postgresql-port: The port of the PostgreSQL server + postgresql-user: The user to connect to the PostgreSQL server + postgresql-password: The password to connect to the PostgreSQL server + postgresql-database: The database to connect to properties: name: description: |- @@ -2457,6 +2478,13 @@ spec: - name type: object x-kubernetes-map-type: atomic + provider: + default: mysql + description: DB provider. Supported are mysql and postgresql. + enum: + - postgresql + - mysql + type: string pvc: default: retain: true diff --git a/config/default/images.env b/config/default/images.env index 7e94faeb5..ebbbc42ed 100644 --- a/config/default/images.env +++ b/config/default/images.env @@ -1,5 +1,5 @@ -RELATED_IMAGE_TRILLIAN_LOG_SIGNER=registry.redhat.io/rhtas/trillian-logsigner-rhel9@sha256:358d52e57faf07d7876f53902496e6926c39a9ac1f52a3b7dc1fab0d9d6d97c0 -RELATED_IMAGE_TRILLIAN_LOG_SERVER=registry.redhat.io/rhtas/trillian-logserver-rhel9@sha256:9ecb8cbb0e1a1d3043a377992ad1795f01ae7491b04ea8e9914263361fc9d51c +RELATED_IMAGE_TRILLIAN_LOG_SIGNER=registry.redhat.io/rhtas/trillian-logsigner-rhel9@sha256:7aee902e70f430f7502b99efc2b7bea8786879e9842d1b4d8d24795f7ff4a143 +RELATED_IMAGE_TRILLIAN_LOG_SERVER=registry.redhat.io/rhtas/trillian-logserver-rhel9@sha256:729f2fc77d37d1c45811daddbd1bddd2e5ccf4b9598489a5f728727f84bc2ef7 RELATED_IMAGE_TRILLIAN_DB=registry.redhat.io/rhtas/trillian-database-rhel9@sha256:1295d965ba4f2415742e5b1f858abcac8b03d45708051bc51f28a0e70ce1d417 RELATED_IMAGE_TRILLIAN_NETCAT=registry.redhat.io/openshift4/ose-tools-rhel9@sha256:47eec19e875c3db11a31ccf4c199ef52cf0d2df3b7c424868f55f9e0d0dd43df RELATED_IMAGE_CREATETREE=registry.redhat.io/rhtas/createtree-rhel9@sha256:bcfc0d077428a5587c8f0cbb4e066684db1bceab06476ffcb5017d153d117269 diff --git a/internal/controller/trillian/actions/constants.go b/internal/controller/trillian/actions/constants.go index 9ca9ec4df..f19463a74 100644 --- a/internal/controller/trillian/actions/constants.go +++ b/internal/controller/trillian/actions/constants.go @@ -29,10 +29,18 @@ const ( MetricsPort = 8090 MetricsPortName = "metrics" + // MySQL SecretRootPassword = "mysql-root-password" SecretPassword = "mysql-password" SecretDatabaseName = "mysql-database" SecretUser = "mysql-user" SecretPort = "mysql-port" SecretHost = "mysql-host" + + // PostgreSQL + PgSecretPassword = "postgresql-password" + PgSecretDatabaseName = "postgresql-database" + PgSecretUser = "postgresql-user" + PgSecretPort = "postgresql-port" + PgSecretHost = "postgresql-host" ) diff --git a/internal/controller/trillian/utils/server-deployment.go b/internal/controller/trillian/utils/server-deployment.go index cf2b79d81..eb8093e4e 100644 --- a/internal/controller/trillian/utils/server-deployment.go +++ b/internal/controller/trillian/utils/server-deployment.go @@ -17,6 +17,31 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ) +type dbKeys struct { + User string + Password string + Host string + Port string + Database string +} + +var dbProviderKeys = map[string]dbKeys{ + "mysql": { + User: actions.SecretUser, + Password: actions.SecretPassword, + Host: actions.SecretHost, + Port: actions.SecretPort, + Database: actions.SecretDatabaseName, + }, + "postgresql": { + User: actions.PgSecretUser, + Password: actions.PgSecretPassword, + Host: actions.PgSecretHost, + Port: actions.PgSecretPort, + Database: actions.PgSecretDatabaseName, + }, +} + func EnsureServerDeployment(instance *v1alpha1.Trillian, labels map[string]string) []func(*apps.Deployment) error { return []func(deployment *apps.Deployment) error{ ensureDeployment(instance, @@ -53,20 +78,25 @@ func ensureInitContainer(instance *v1alpha1.Trillian) func(*apps.Deployment) err initContainer := kubernetes.FindInitContainerByNameOrCreate(&dp.Spec.Template.Spec, "wait-for-trillian-db") initContainer.Image = images.Registry.Get(images.TrillianNetcat) - hostnameEnv := kubernetes.FindEnvByNameOrCreate(initContainer, "MYSQL_HOSTNAME") + keys, ok := dbProviderKeys[instance.Spec.Db.Provider] + if !ok { + return fmt.Errorf("unsupported database provider %s", instance.Spec.Db.Provider) + } + + hostnameEnv := kubernetes.FindEnvByNameOrCreate(initContainer, "DB_HOSTNAME") hostnameEnv.ValueFrom = &core.EnvVarSource{ SecretKeyRef: &core.SecretKeySelector{ - Key: actions.SecretHost, + Key: keys.Host, LocalObjectReference: core.LocalObjectReference{ Name: instance.Status.Db.DatabaseSecretRef.Name, }, }, } - portEnv := kubernetes.FindEnvByNameOrCreate(initContainer, "MYSQL_PORT") + portEnv := kubernetes.FindEnvByNameOrCreate(initContainer, "DB_PORT") portEnv.ValueFrom = &core.EnvVarSource{ SecretKeyRef: &core.SecretKeySelector{ - Key: actions.SecretPort, + Key: keys.Port, LocalObjectReference: core.LocalObjectReference{ Name: instance.Status.Db.DatabaseSecretRef.Name, }, @@ -75,7 +105,7 @@ func ensureInitContainer(instance *v1alpha1.Trillian) func(*apps.Deployment) err initContainer.Command = []string{ "sh", "-c", - "until nc -z -v -w30 $MYSQL_HOSTNAME $MYSQL_PORT; do echo \"Waiting for MySQL to start\"; sleep 5; done;", + "until nc -z -v -w30 $DB_HOSTNAME $DB_PORT; do echo \"Waiting for database to start\"; sleep 5; done;", } return nil @@ -126,16 +156,30 @@ func ensureDeployment(instance *v1alpha1.Trillian, image string, name string, sa container := kubernetes.FindContainerByNameOrCreate(&template.Spec, name) container.Image = image - container.Args = append([]string{ - "--storage_system=mysql", - "--quota_system=mysql", - "--mysql_uri=$(MYSQL_USER):$(MYSQL_PASSWORD)@tcp($(MYSQL_HOSTNAME):$(MYSQL_PORT))/$(MYSQL_DATABASE)", - "--mysql_max_conns=30", - "--mysql_max_idle_conns=10", + switch instance.Spec.Db.Provider { + case "postgresql": + container.Args = append([]string{ + "--storage_system=postgresql", + "--quota_system=postgresql", + "--postgresql_uri=postgresql://$(DB_USER):$(DB_PASSWORD)@$(DB_HOSTNAME):$(DB_PORT)/$(DB_NAME)", + }, args...) + case "mysql": + container.Args = append([]string{ + "--storage_system=mysql", + "--quota_system=mysql", + "--mysql_uri=$(DB_USER):$(DB_PASSWORD)@tcp($(DB_HOSTNAME):$(DB_PORT))/$(DB_NAME)", + "--mysql_max_conns=30", + "--mysql_max_idle_conns=10", + }, args...) + default: + return fmt.Errorf("unsupported database provider %s", instance.Spec.Db.Provider) + } + + container.Args = append(container.Args, []string{ "--rpc_endpoint=0.0.0.0:" + strconv.Itoa(int(actions.ServerPort)), "--http_endpoint=0.0.0.0:" + strconv.Itoa(int(actions.MetricsPort)), "--alsologtostderr", - }, args...) + }...) if instance.Spec.MaxRecvMessageSize != nil { container.Args = append(container.Args, "--max_msg_size_bytes", fmt.Sprintf("%d", *instance.Spec.MaxRecvMessageSize)) @@ -143,50 +187,55 @@ func ensureDeployment(instance *v1alpha1.Trillian, image string, name string, sa //Ports = containerPorts // Env variables from secret trillian-mysql - userEnv := kubernetes.FindEnvByNameOrCreate(container, "MYSQL_USER") + keys, ok := dbProviderKeys[instance.Spec.Db.Provider] + if !ok { + return fmt.Errorf("unsupported database provider %s", instance.Spec.Db.Provider) + } + + userEnv := kubernetes.FindEnvByNameOrCreate(container, "DB_USER") userEnv.ValueFrom = &core.EnvVarSource{ SecretKeyRef: &core.SecretKeySelector{ - Key: actions.SecretUser, + Key: keys.User, LocalObjectReference: core.LocalObjectReference{ Name: instance.Status.Db.DatabaseSecretRef.Name, }, }, } - passwordEnv := kubernetes.FindEnvByNameOrCreate(container, "MYSQL_PASSWORD") + passwordEnv := kubernetes.FindEnvByNameOrCreate(container, "DB_PASSWORD") passwordEnv.ValueFrom = &core.EnvVarSource{ SecretKeyRef: &core.SecretKeySelector{ - Key: actions.SecretPassword, + Key: keys.Password, LocalObjectReference: core.LocalObjectReference{ Name: instance.Status.Db.DatabaseSecretRef.Name, }, }, } - hostEnv := kubernetes.FindEnvByNameOrCreate(container, "MYSQL_HOSTNAME") + hostEnv := kubernetes.FindEnvByNameOrCreate(container, "DB_HOSTNAME") hostEnv.ValueFrom = &core.EnvVarSource{ SecretKeyRef: &core.SecretKeySelector{ - Key: actions.SecretHost, + Key: keys.Host, LocalObjectReference: core.LocalObjectReference{ Name: instance.Status.Db.DatabaseSecretRef.Name, }, }, } - containerPortEnv := kubernetes.FindEnvByNameOrCreate(container, "MYSQL_PORT") + containerPortEnv := kubernetes.FindEnvByNameOrCreate(container, "DB_PORT") containerPortEnv.ValueFrom = &core.EnvVarSource{ SecretKeyRef: &core.SecretKeySelector{ - Key: actions.SecretPort, + Key: keys.Port, LocalObjectReference: core.LocalObjectReference{ Name: instance.Status.Db.DatabaseSecretRef.Name, }, }, } - dbEnv := kubernetes.FindEnvByNameOrCreate(container, "MYSQL_DATABASE") + dbEnv := kubernetes.FindEnvByNameOrCreate(container, "DB_NAME") dbEnv.ValueFrom = &core.EnvVarSource{ SecretKeyRef: &core.SecretKeySelector{ - Key: actions.SecretDatabaseName, + Key: keys.Database, LocalObjectReference: core.LocalObjectReference{ Name: instance.Status.Db.DatabaseSecretRef.Name, }, @@ -225,13 +274,20 @@ func ensureDeployment(instance *v1alpha1.Trillian, image string, name string, sa func WithTlsDB(instance *v1alpha1.Trillian, caPath string, name string) func(*apps.Deployment) error { return func(dp *apps.Deployment) error { c := kubernetes.FindContainerByNameOrCreate(&dp.Spec.Template.Spec, name) - c.Args = append(c.Args, "--mysql_tls_ca", caPath) - - mysqlServerName := "$(MYSQL_HOSTNAME)." + instance.Namespace + ".svc" - if !*instance.Spec.Db.Create { - mysqlServerName = "$(MYSQL_HOSTNAME)" + switch instance.Spec.Db.Provider { + case "postgresql": + c.Args = append(c.Args, "--postgresql_tls_ca", caPath) + case "mysql": + c.Args = append(c.Args, "--mysql_tls_ca", caPath) + + mysqlServerName := "$(DB_HOSTNAME)." + instance.Namespace + ".svc" + if !*instance.Spec.Db.Create { + mysqlServerName = "$(DB_HOSTNAME)" + } + c.Args = append(c.Args, "--mysql_server_name", mysqlServerName) + default: + return fmt.Errorf("unsupported database provider %s", instance.Spec.Db.Provider) } - c.Args = append(c.Args, "--mysql_server_name", mysqlServerName) return nil } }