diff --git a/apis/database/v1beta1/cloudsql_instance_types.go b/apis/database/v1beta1/cloudsql_instance_types.go index b91b5044f..8392af761 100644 --- a/apis/database/v1beta1/cloudsql_instance_types.go +++ b/apis/database/v1beta1/cloudsql_instance_types.go @@ -135,6 +135,10 @@ type CloudSQLInstanceParameters struct { // the suspension. // +optional SuspensionReason []string `json:"suspensionReason,omitempty"` + + // Read-replica configuration for connecting to the primary instance. + // +optional + ReplicaConfiguration *ReplicaConfiguration `json:"replicaConfiguration,omitempty"` } // Settings is Cloud SQL database instance settings. @@ -408,6 +412,86 @@ type OnPremisesConfiguration struct { HostPort string `json:"hostPort"` } +// ReplicaConfiguration Read-replica configuration for connecting to the primary instance. +type ReplicaConfiguration struct { + // FailoverTarget: Specifies if the replica is the failover target. If + // the field is set to *true* the replica will be designated as a + // failover replica. + // +optional + FailoverTarget *bool `json:"failoverTarget,omitempty"` + + // MysqlReplicaConfiguration: MySQL specific configuration when + // replicating from a MySQL on-premises primary instance. Replication + // configuration information such as the username, password, + // certificates, and keys are not stored in the instance metadata. The + // configuration information is used only to set up the replication + // connection and is stored by MySQL in a file named **master.info** in + // the data directory. + // +optional + MysqlReplicaConfiguration *MySqlReplicaConfiguration `json:"mysqlReplicaConfiguration,omitempty"` +} + +// MySqlReplicaConfiguration: Read-replica configuration specific to +// MySQL databases. https://cloud.google.com/sql/docs/mysql/admin-api/rest/v1/instances#ReplicaConfiguration +type MySqlReplicaConfiguration struct { + + // SecretRef: Kubernetes Secret containing all credentials for MySqlReplicaConfiguration + // +optional + SecretRef *xpv1.SecretReference `json:"secretRef,omitempty"` + + // CaCertificateKey: key in the secret representing PEM representation of the trusted CA's x509 + // certificate. + // +optional + CaCertificateKey *string `json:"caCertificateKey,omitempty"` + + // ClientCertificateKey: key in the secret representing PEM representation of the replica's x509 + // certificate. + // +optional + ClientCertificateKey *string `json:"clientCertificateKey,omitempty"` + + // ClientKey: key in the secret representing PEM representation of the replica's private key. The + // corresponsing public key is encoded in the client's certificate. + // +optional + ClientKey *string `json:"clientKey,omitempty"` + + // ConnectRetryInterval: Seconds to wait between connect retries. + // MySQL's default is 60 seconds. + // +optional + ConnectRetryInterval *int64 `json:"connectRetryInterval,omitempty"` + + // DumpFilePath: Path to a SQL dump file in Google Cloud Storage from + // which the replica instance is to be created. The URI is in the form + // gs://bucketName/fileName. Compressed gzip files (.gz) are also + // supported. Dumps have the binlog co-ordinates from which replication + // begins. This can be accomplished by setting --master-data to 1 when + // using mysqldump. + // +optional + DumpFilePath *string `json:"dumpFilePath,omitempty"` + + // MasterHeartbeatPeriod: Interval in milliseconds between replication + // heartbeats. + // +optional + MasterHeartbeatPeriod *int64 `json:"masterHeartbeatPeriod,omitempty"` + + // Password: key in the secret representing the password for the replication connection. + // +optional + PasswordKey *string `json:"passwordKey,omitempty"` + + // SslCipher: A list of permissible ciphers to use for SSL encryption. + // +optional + SslCipher *string `json:"sslCipher,omitempty"` + + // Username: key in the secret representing the username for the replication connection. + // +optional + UsernameKey *string `json:"usernameKey,omitempty"` + + // VerifyServerCertificate: Whether or not to check the primary + // instance's Common Name value in the certificate that it sends during + // the SSL handshake. + // +optional + VerifyServerCertificate *bool `json:"verifyServerCertificate,omitempty"` +} + // CloudSQLInstanceObservation is used to show the observed state of the Cloud SQL resource on GCP. type CloudSQLInstanceObservation struct { // BackendType: FIRST_GEN: First Generation instance. MySQL diff --git a/examples/database/cloudsql_mysql.yaml b/examples/database/cloudsql_mysql.yaml new file mode 100644 index 000000000..0e9c6f26a --- /dev/null +++ b/examples/database/cloudsql_mysql.yaml @@ -0,0 +1,21 @@ +apiVersion: database.gcp.crossplane.io/v1beta1 +kind: CloudSQLInstance +metadata: + name: mysql-instance +spec: + forProvider: + databaseVersion: MYSQL_5_7 + region: us-west2 + replicaNames: + - "mysql-replica" + settings: + tier: db-custom-1-3840 + dataDiskSizeGb: 20 + backupConfiguration: + binaryLogEnabled: true + enabled: true + providerConfigRef: + name: default + writeConnectionSecretToRef: + name: example-cloudsql-connection-details + namespace: crossplane-system diff --git a/examples/database/cloudsql_mysql_replica.yaml b/examples/database/cloudsql_mysql_replica.yaml new file mode 100644 index 000000000..daa69332f --- /dev/null +++ b/examples/database/cloudsql_mysql_replica.yaml @@ -0,0 +1,25 @@ +apiVersion: database.gcp.crossplane.io/v1beta1 +kind: CloudSQLInstance +metadata: + name: mysql-replica +spec: + forProvider: + databaseVersion: MYSQL_5_7 + region: us-west2 + settings: + tier: db-custom-1-3840 + dataDiskSizeGb: 20 + masterInstanceName: mysql-instance + replicaConfiguration: + failoverTarget: false + mysqlReplicaConfiguration: + secretRef: + name: cloudsql-replica-creds + namespace: default + usernameKey: "username" + passwordKey: "password" + providerConfigRef: + name: default + writeConnectionSecretToRef: + name: example-cloudsql-replica-connection-details + namespace: crossplane-system diff --git a/package/crds/database.gcp.crossplane.io_cloudsqlinstances.yaml b/package/crds/database.gcp.crossplane.io_cloudsqlinstances.yaml index 26f06f703..a8c144f53 100644 --- a/package/crds/database.gcp.crossplane.io_cloudsqlinstances.yaml +++ b/package/crds/database.gcp.crossplane.io_cloudsqlinstances.yaml @@ -142,6 +142,91 @@ spec: or Second Generation). The region can not be changed after instance creation.' type: string + replicaConfiguration: + description: Read-replica configuration for connecting to the + primary instance. + properties: + failoverTarget: + description: 'FailoverTarget: Specifies if the replica is + the failover target. If the field is set to *true* the replica + will be designated as a failover replica.' + type: boolean + mysqlReplicaConfiguration: + description: 'MysqlReplicaConfiguration: MySQL specific configuration + when replicating from a MySQL on-premises primary instance. + Replication configuration information such as the username, + password, certificates, and keys are not stored in the instance + metadata. The configuration information is used only to + set up the replication connection and is stored by MySQL + in a file named **master.info** in the data directory.' + properties: + caCertificateKey: + description: 'CaCertificateKey: key in the secret representing + PEM representation of the trusted CA''s x509 certificate.' + type: string + clientCertificateKey: + description: 'ClientCertificateKey: key in the secret + representing PEM representation of the replica''s x509 + certificate.' + type: string + clientKey: + description: 'ClientKey: key in the secret representing + PEM representation of the replica''s private key. The + corresponsing public key is encoded in the client''s + certificate.' + type: string + connectRetryInterval: + description: 'ConnectRetryInterval: Seconds to wait between + connect retries. MySQL''s default is 60 seconds.' + format: int64 + type: integer + dumpFilePath: + description: 'DumpFilePath: Path to a SQL dump file in + Google Cloud Storage from which the replica instance + is to be created. The URI is in the form gs://bucketName/fileName. + Compressed gzip files (.gz) are also supported. Dumps + have the binlog co-ordinates from which replication + begins. This can be accomplished by setting --master-data + to 1 when using mysqldump.' + type: string + masterHeartbeatPeriod: + description: 'MasterHeartbeatPeriod: Interval in milliseconds + between replication heartbeats.' + format: int64 + type: integer + passwordKey: + description: 'Password: key in the secret representing + the password for the replication connection.' + type: string + secretRef: + description: 'SecretRef: Kubernetes Secret containing + all credentials for MySqlReplicaConfiguration' + properties: + name: + description: Name of the secret. + type: string + namespace: + description: Namespace of the secret. + type: string + required: + - name + - namespace + type: object + sslCipher: + description: 'SslCipher: A list of permissible ciphers + to use for SSL encryption.' + type: string + usernameKey: + description: 'Username: key in the secret representing + the username for the replication connection.' + type: string + verifyServerCertificate: + description: 'VerifyServerCertificate: Whether or not + to check the primary instance''s Common Name value in + the certificate that it sends during the SSL handshake.' + type: boolean + type: object + type: object replicaNames: description: 'ReplicaNames: The replicas of the instance.' items: diff --git a/pkg/clients/cloudsql/cloudsql.go b/pkg/clients/cloudsql/cloudsql.go index 062fd113b..27c12485c 100644 --- a/pkg/clients/cloudsql/cloudsql.go +++ b/pkg/clients/cloudsql/cloudsql.go @@ -27,86 +27,96 @@ import ( "github.com/crossplane/provider-gcp/apis/database/v1beta1" gcp "github.com/crossplane/provider-gcp/pkg/clients" + corev1 "k8s.io/api/core/v1" ) -const errCheckUpToDate = "unable to determine if external resource is up to date" - +const ( + errCheckUpToDate = "unable to determine if external resource is up to date" + errSecretKey = "unable to determine secret defined in secretRef: %v" +) +// CloudSQLOptions is wrapper type that holds all potentially passed params to the client +type CloudSQLOptions struct { + Name string + Spec *v1beta1.CloudSQLInstanceParameters + Instance *sqladmin.DatabaseInstance + Secret *corev1.Secret +} // Cyclomatic complexity test is disabled for translation methods // because all they do is simple comparison & assignment without // real logic. But every if statement increases the cyclomatic // complexity rate. // GenerateDatabaseInstance generates *sqladmin.DatabaseInstance instance from CloudSQLInstanceParameters. -func GenerateDatabaseInstance(name string, in v1beta1.CloudSQLInstanceParameters, db *sqladmin.DatabaseInstance) { // nolint:gocyclo - db.DatabaseVersion = gcp.StringValue(in.DatabaseVersion) - db.GceZone = gcp.StringValue(in.GceZone) - db.InstanceType = gcp.StringValue(in.InstanceType) - db.MasterInstanceName = gcp.StringValue(in.MasterInstanceName) - db.MaxDiskSize = gcp.Int64Value(in.MaxDiskSize) - db.Name = name - db.Region = in.Region - db.ReplicaNames = in.ReplicaNames - db.SuspensionReason = in.SuspensionReason - if in.DiskEncryptionConfiguration != nil { - if db.DiskEncryptionConfiguration == nil { - db.DiskEncryptionConfiguration = &sqladmin.DiskEncryptionConfiguration{} +func GenerateDatabaseInstance(opts CloudSQLOptions) error { // nolint:gocyclo + opts.Instance.DatabaseVersion = gcp.StringValue(opts.Spec.DatabaseVersion) + opts.Instance.GceZone = gcp.StringValue(opts.Spec.GceZone) + opts.Instance.InstanceType = gcp.StringValue(opts.Spec.InstanceType) + opts.Instance.MasterInstanceName = gcp.StringValue(opts.Spec.MasterInstanceName) + opts.Instance.MaxDiskSize = gcp.Int64Value(opts.Spec.MaxDiskSize) + opts.Instance.Name = opts.Name + opts.Instance.Region = opts.Spec.Region + opts.Instance.ReplicaNames = opts.Spec.ReplicaNames + opts.Instance.SuspensionReason = opts.Spec.SuspensionReason + if opts.Spec.DiskEncryptionConfiguration != nil { + if opts.Instance.DiskEncryptionConfiguration == nil { + opts.Instance.DiskEncryptionConfiguration = &sqladmin.DiskEncryptionConfiguration{} } - db.DiskEncryptionConfiguration.KmsKeyName = in.DiskEncryptionConfiguration.KmsKeyName + opts.Instance.DiskEncryptionConfiguration.KmsKeyName = opts.Spec.DiskEncryptionConfiguration.KmsKeyName } - if in.FailoverReplica != nil { - if db.FailoverReplica == nil { - db.FailoverReplica = &sqladmin.DatabaseInstanceFailoverReplica{} + if opts.Spec.FailoverReplica != nil { + if opts.Instance.FailoverReplica == nil { + opts.Instance.FailoverReplica = &sqladmin.DatabaseInstanceFailoverReplica{} } - db.FailoverReplica.Name = in.FailoverReplica.Name + opts.Instance.FailoverReplica.Name = opts.Spec.FailoverReplica.Name } - if in.OnPremisesConfiguration != nil { - if db.OnPremisesConfiguration == nil { - db.OnPremisesConfiguration = &sqladmin.OnPremisesConfiguration{} + if opts.Spec.OnPremisesConfiguration != nil { + if opts.Instance.OnPremisesConfiguration == nil { + opts.Instance.OnPremisesConfiguration = &sqladmin.OnPremisesConfiguration{} } - db.OnPremisesConfiguration.HostPort = in.OnPremisesConfiguration.HostPort - } - if db.Settings == nil { - db.Settings = &sqladmin.Settings{} - } - db.Settings.ActivationPolicy = gcp.StringValue(in.Settings.ActivationPolicy) - db.Settings.AuthorizedGaeApplications = in.Settings.AuthorizedGaeApplications - db.Settings.AvailabilityType = gcp.StringValue(in.Settings.AvailabilityType) - db.Settings.CrashSafeReplicationEnabled = gcp.BoolValue(in.Settings.CrashSafeReplicationEnabled) - db.Settings.DataDiskSizeGb = gcp.Int64Value(in.Settings.DataDiskSizeGb) - db.Settings.DataDiskType = gcp.StringValue(in.Settings.DataDiskType) - db.Settings.DatabaseReplicationEnabled = gcp.BoolValue(in.Settings.DatabaseReplicationEnabled) - db.Settings.PricingPlan = gcp.StringValue(in.Settings.PricingPlan) - db.Settings.ReplicationType = gcp.StringValue(in.Settings.ReplicationType) - db.Settings.StorageAutoResize = in.Settings.StorageAutoResize - db.Settings.StorageAutoResizeLimit = gcp.Int64Value(in.Settings.StorageAutoResizeLimit) - db.Settings.Tier = in.Settings.Tier - db.Settings.UserLabels = in.Settings.UserLabels - - if in.Settings.BackupConfiguration != nil { - if db.Settings.BackupConfiguration == nil { - db.Settings.BackupConfiguration = &sqladmin.BackupConfiguration{} + opts.Instance.OnPremisesConfiguration.HostPort = opts.Spec.OnPremisesConfiguration.HostPort + } + if opts.Instance.Settings == nil { + opts.Instance.Settings = &sqladmin.Settings{} + } + opts.Instance.Settings.ActivationPolicy = gcp.StringValue(opts.Spec.Settings.ActivationPolicy) + opts.Instance.Settings.AuthorizedGaeApplications = opts.Spec.Settings.AuthorizedGaeApplications + opts.Instance.Settings.AvailabilityType = gcp.StringValue(opts.Spec.Settings.AvailabilityType) + opts.Instance.Settings.CrashSafeReplicationEnabled = gcp.BoolValue(opts.Spec.Settings.CrashSafeReplicationEnabled) + opts.Instance.Settings.DataDiskSizeGb = gcp.Int64Value(opts.Spec.Settings.DataDiskSizeGb) + opts.Instance.Settings.DataDiskType = gcp.StringValue(opts.Spec.Settings.DataDiskType) + opts.Instance.Settings.DatabaseReplicationEnabled = gcp.BoolValue(opts.Spec.Settings.DatabaseReplicationEnabled) + opts.Instance.Settings.PricingPlan = gcp.StringValue(opts.Spec.Settings.PricingPlan) + opts.Instance.Settings.ReplicationType = gcp.StringValue(opts.Spec.Settings.ReplicationType) + opts.Instance.Settings.StorageAutoResize = opts.Spec.Settings.StorageAutoResize + opts.Instance.Settings.StorageAutoResizeLimit = gcp.Int64Value(opts.Spec.Settings.StorageAutoResizeLimit) + opts.Instance.Settings.Tier = opts.Spec.Settings.Tier + opts.Instance.Settings.UserLabels = opts.Spec.Settings.UserLabels + + if opts.Spec.Settings.BackupConfiguration != nil { + if opts.Instance.Settings.BackupConfiguration == nil { + opts.Instance.Settings.BackupConfiguration = &sqladmin.BackupConfiguration{} } - db.Settings.BackupConfiguration.BinaryLogEnabled = gcp.BoolValue(in.Settings.BackupConfiguration.BinaryLogEnabled) - db.Settings.BackupConfiguration.Enabled = gcp.BoolValue(in.Settings.BackupConfiguration.Enabled) - db.Settings.BackupConfiguration.Location = gcp.StringValue(in.Settings.BackupConfiguration.Location) - db.Settings.BackupConfiguration.ReplicationLogArchivingEnabled = gcp.BoolValue(in.Settings.BackupConfiguration.ReplicationLogArchivingEnabled) - db.Settings.BackupConfiguration.StartTime = gcp.StringValue(in.Settings.BackupConfiguration.StartTime) - db.Settings.BackupConfiguration.PointInTimeRecoveryEnabled = gcp.BoolValue(in.Settings.BackupConfiguration.PointInTimeRecoveryEnabled) - } - if in.Settings.IPConfiguration != nil { - if db.Settings.IpConfiguration == nil { - db.Settings.IpConfiguration = &sqladmin.IpConfiguration{} + opts.Instance.Settings.BackupConfiguration.BinaryLogEnabled = gcp.BoolValue(opts.Spec.Settings.BackupConfiguration.BinaryLogEnabled) + opts.Instance.Settings.BackupConfiguration.Enabled = gcp.BoolValue(opts.Spec.Settings.BackupConfiguration.Enabled) + opts.Instance.Settings.BackupConfiguration.Location = gcp.StringValue(opts.Spec.Settings.BackupConfiguration.Location) + opts.Instance.Settings.BackupConfiguration.ReplicationLogArchivingEnabled = gcp.BoolValue(opts.Spec.Settings.BackupConfiguration.ReplicationLogArchivingEnabled) + opts.Instance.Settings.BackupConfiguration.StartTime = gcp.StringValue(opts.Spec.Settings.BackupConfiguration.StartTime) + opts.Instance.Settings.BackupConfiguration.PointInTimeRecoveryEnabled = gcp.BoolValue(opts.Spec.Settings.BackupConfiguration.PointInTimeRecoveryEnabled) + } + if opts.Spec.Settings.IPConfiguration != nil { + if opts.Instance.Settings.IpConfiguration == nil { + opts.Instance.Settings.IpConfiguration = &sqladmin.IpConfiguration{} } - db.Settings.IpConfiguration.Ipv4Enabled = gcp.BoolValue(in.Settings.IPConfiguration.Ipv4Enabled) - db.Settings.IpConfiguration.PrivateNetwork = gcp.StringValue(in.Settings.IPConfiguration.PrivateNetwork) - db.Settings.IpConfiguration.RequireSsl = gcp.BoolValue(in.Settings.IPConfiguration.RequireSsl) - db.Settings.IpConfiguration.ForceSendFields = []string{"Ipv4Enabled"} + opts.Instance.Settings.IpConfiguration.Ipv4Enabled = gcp.BoolValue(opts.Spec.Settings.IPConfiguration.Ipv4Enabled) + opts.Instance.Settings.IpConfiguration.PrivateNetwork = gcp.StringValue(opts.Spec.Settings.IPConfiguration.PrivateNetwork) + opts.Instance.Settings.IpConfiguration.RequireSsl = gcp.BoolValue(opts.Spec.Settings.IPConfiguration.RequireSsl) + opts.Instance.Settings.IpConfiguration.ForceSendFields = []string{"Ipv4Enabled"} - if len(in.Settings.IPConfiguration.AuthorizedNetworks) > 0 { - db.Settings.IpConfiguration.AuthorizedNetworks = make([]*sqladmin.AclEntry, len(in.Settings.IPConfiguration.AuthorizedNetworks)) + if len(opts.Spec.Settings.IPConfiguration.AuthorizedNetworks) > 0 { + opts.Instance.Settings.IpConfiguration.AuthorizedNetworks = make([]*sqladmin.AclEntry, len(opts.Spec.Settings.IPConfiguration.AuthorizedNetworks)) } - for i, val := range in.Settings.IPConfiguration.AuthorizedNetworks { - db.Settings.IpConfiguration.AuthorizedNetworks[i] = &sqladmin.AclEntry{ + for i, val := range opts.Spec.Settings.IPConfiguration.AuthorizedNetworks { + opts.Instance.Settings.IpConfiguration.AuthorizedNetworks[i] = &sqladmin.AclEntry{ ExpirationTime: gcp.StringValue(val.ExpirationTime), Name: gcp.StringValue(val.Name), Value: gcp.StringValue(val.Value), @@ -114,30 +124,50 @@ func GenerateDatabaseInstance(name string, in v1beta1.CloudSQLInstanceParameters } } } - if in.Settings.LocationPreference != nil { - if db.Settings.LocationPreference == nil { - db.Settings.LocationPreference = &sqladmin.LocationPreference{} + if opts.Spec.Settings.LocationPreference != nil { + if opts.Instance.Settings.LocationPreference == nil { + opts.Instance.Settings.LocationPreference = &sqladmin.LocationPreference{} } - db.Settings.LocationPreference.FollowGaeApplication = gcp.StringValue(in.Settings.LocationPreference.FollowGaeApplication) - db.Settings.LocationPreference.Zone = gcp.StringValue(in.Settings.LocationPreference.Zone) + opts.Instance.Settings.LocationPreference.FollowGaeApplication = gcp.StringValue(opts.Spec.Settings.LocationPreference.FollowGaeApplication) + opts.Instance.Settings.LocationPreference.Zone = gcp.StringValue(opts.Spec.Settings.LocationPreference.Zone) } - if in.Settings.MaintenanceWindow != nil { - if db.Settings.MaintenanceWindow == nil { - db.Settings.MaintenanceWindow = &sqladmin.MaintenanceWindow{} + if opts.Spec.Settings.MaintenanceWindow != nil { + if opts.Instance.Settings.MaintenanceWindow == nil { + opts.Instance.Settings.MaintenanceWindow = &sqladmin.MaintenanceWindow{} } - db.Settings.MaintenanceWindow.Day = gcp.Int64Value(in.Settings.MaintenanceWindow.Day) - db.Settings.MaintenanceWindow.Hour = gcp.Int64Value(in.Settings.MaintenanceWindow.Hour) - db.Settings.MaintenanceWindow.UpdateTrack = gcp.StringValue(in.Settings.MaintenanceWindow.UpdateTrack) + opts.Instance.Settings.MaintenanceWindow.Day = gcp.Int64Value(opts.Spec.Settings.MaintenanceWindow.Day) + opts.Instance.Settings.MaintenanceWindow.Hour = gcp.Int64Value(opts.Spec.Settings.MaintenanceWindow.Hour) + opts.Instance.Settings.MaintenanceWindow.UpdateTrack = gcp.StringValue(opts.Spec.Settings.MaintenanceWindow.UpdateTrack) } - if len(in.Settings.DatabaseFlags) > 0 { - db.Settings.DatabaseFlags = make([]*sqladmin.DatabaseFlags, len(in.Settings.DatabaseFlags)) + if len(opts.Spec.Settings.DatabaseFlags) > 0 { + opts.Instance.Settings.DatabaseFlags = make([]*sqladmin.DatabaseFlags, len(opts.Spec.Settings.DatabaseFlags)) } - for i, val := range in.Settings.DatabaseFlags { - db.Settings.DatabaseFlags[i] = &sqladmin.DatabaseFlags{ + for i, val := range opts.Spec.Settings.DatabaseFlags { + opts.Instance.Settings.DatabaseFlags[i] = &sqladmin.DatabaseFlags{ Name: val.Name, Value: val.Value, } } + + if opts.Spec.ReplicaConfiguration != nil { + if opts.Instance.ReplicaConfiguration == nil { + opts.Instance.ReplicaConfiguration = &sqladmin.ReplicaConfiguration{ + FailoverTarget: gcp.BoolValue(opts.Spec.ReplicaConfiguration.FailoverTarget), + } + } + if opts.Spec.ReplicaConfiguration.MysqlReplicaConfiguration != nil { + if opts.Instance.ReplicaConfiguration.MysqlReplicaConfiguration == nil { + opts.Instance.ReplicaConfiguration.MysqlReplicaConfiguration = &sqladmin.MySqlReplicaConfiguration{} + } + opts.Instance.ReplicaConfiguration.MysqlReplicaConfiguration.DumpFilePath = gcp.StringValue(opts.Spec.ReplicaConfiguration.MysqlReplicaConfiguration.DumpFilePath) + opts.Instance.ReplicaConfiguration.MysqlReplicaConfiguration.ConnectRetryInterval = gcp.Int64Value(opts.Spec.ReplicaConfiguration.MysqlReplicaConfiguration.ConnectRetryInterval) + opts.Instance.ReplicaConfiguration.MysqlReplicaConfiguration.MasterHeartbeatPeriod = gcp.Int64Value(opts.Spec.ReplicaConfiguration.MysqlReplicaConfiguration.MasterHeartbeatPeriod) + opts.Instance.ReplicaConfiguration.MysqlReplicaConfiguration.VerifyServerCertificate = gcp.BoolValue(opts.Spec.ReplicaConfiguration.MysqlReplicaConfiguration.VerifyServerCertificate) + return supplyReplicaConfigurationCredentials(opts.Instance, opts.Spec.ReplicaConfiguration, opts.Secret) + + } + } + return nil } // GenerateObservation produces CloudSQLInstanceObservation object from *sqladmin.DatabaseInstance object. @@ -175,90 +205,90 @@ func GenerateObservation(in sqladmin.DatabaseInstance) v1beta1.CloudSQLInstanceO } // LateInitializeSpec fills unassigned fields with the values in sqladmin.DatabaseInstance object. -func LateInitializeSpec(spec *v1beta1.CloudSQLInstanceParameters, in sqladmin.DatabaseInstance) { // nolint:gocyclo +func LateInitializeSpec(opts CloudSQLOptions) { // nolint:gocyclo // TODO(muvaf): One can marshall both objects into json and compare them as dictionaries since // they both have the same key names but this may create performance problems as it'll happen in each // reconcile. learn code-generation to make writing this easier and performant. - if spec.Region == "" { - spec.Region = in.Region - } - spec.DatabaseVersion = gcp.LateInitializeString(spec.DatabaseVersion, in.DatabaseVersion) - spec.MasterInstanceName = gcp.LateInitializeString(spec.MasterInstanceName, in.MasterInstanceName) - spec.GceZone = gcp.LateInitializeString(spec.GceZone, in.GceZone) - spec.InstanceType = gcp.LateInitializeString(spec.InstanceType, in.InstanceType) - spec.MaxDiskSize = gcp.LateInitializeInt64(spec.MaxDiskSize, in.MaxDiskSize) - spec.ReplicaNames = gcp.LateInitializeStringSlice(spec.ReplicaNames, in.ReplicaNames) - spec.SuspensionReason = gcp.LateInitializeStringSlice(spec.SuspensionReason, in.SuspensionReason) - if in.Settings != nil { - if spec.Settings.Tier == "" { - spec.Settings.Tier = in.Settings.Tier + if opts.Spec.Region == "" { + opts.Spec.Region = opts.Instance.Region + } + opts.Spec.DatabaseVersion = gcp.LateInitializeString(opts.Spec.DatabaseVersion, opts.Instance.DatabaseVersion) + opts.Spec.MasterInstanceName = gcp.LateInitializeString(opts.Spec.MasterInstanceName, opts.Instance.MasterInstanceName) + opts.Spec.GceZone = gcp.LateInitializeString(opts.Spec.GceZone, opts.Instance.GceZone) + opts.Spec.InstanceType = gcp.LateInitializeString(opts.Spec.InstanceType, opts.Instance.InstanceType) + opts.Spec.MaxDiskSize = gcp.LateInitializeInt64(opts.Spec.MaxDiskSize, opts.Instance.MaxDiskSize) + opts.Spec.ReplicaNames = gcp.LateInitializeStringSlice(opts.Spec.ReplicaNames, opts.Instance.ReplicaNames) + opts.Spec.SuspensionReason = gcp.LateInitializeStringSlice(opts.Spec.SuspensionReason, opts.Instance.SuspensionReason) + if opts.Instance.Settings != nil { + if opts.Spec.Settings.Tier == "" { + opts.Spec.Settings.Tier = opts.Instance.Settings.Tier } - spec.Settings.ActivationPolicy = gcp.LateInitializeString(spec.Settings.ActivationPolicy, in.Settings.ActivationPolicy) - spec.Settings.AuthorizedGaeApplications = gcp.LateInitializeStringSlice(spec.Settings.AuthorizedGaeApplications, in.Settings.AuthorizedGaeApplications) - spec.Settings.AvailabilityType = gcp.LateInitializeString(spec.Settings.AvailabilityType, in.Settings.AvailabilityType) - spec.Settings.CrashSafeReplicationEnabled = gcp.LateInitializeBool(spec.Settings.CrashSafeReplicationEnabled, in.Settings.CrashSafeReplicationEnabled) - - spec.Settings.DataDiskType = gcp.LateInitializeString(spec.Settings.DataDiskType, in.Settings.DataDiskType) - spec.Settings.PricingPlan = gcp.LateInitializeString(spec.Settings.PricingPlan, in.Settings.PricingPlan) - spec.Settings.ReplicationType = gcp.LateInitializeString(spec.Settings.ReplicationType, in.Settings.ReplicationType) - spec.Settings.UserLabels = gcp.LateInitializeStringMap(spec.Settings.UserLabels, in.Settings.UserLabels) - spec.Settings.DataDiskSizeGb = gcp.LateInitializeInt64(spec.Settings.DataDiskSizeGb, in.Settings.DataDiskSizeGb) - spec.Settings.DatabaseReplicationEnabled = gcp.LateInitializeBool(spec.Settings.DatabaseReplicationEnabled, in.Settings.DatabaseReplicationEnabled) - spec.Settings.StorageAutoResizeLimit = gcp.LateInitializeInt64(spec.Settings.StorageAutoResizeLimit, in.Settings.StorageAutoResizeLimit) - if spec.Settings.StorageAutoResize == nil { - spec.Settings.StorageAutoResize = in.Settings.StorageAutoResize + opts.Spec.Settings.ActivationPolicy = gcp.LateInitializeString(opts.Spec.Settings.ActivationPolicy, opts.Instance.Settings.ActivationPolicy) + opts.Spec.Settings.AuthorizedGaeApplications = gcp.LateInitializeStringSlice(opts.Spec.Settings.AuthorizedGaeApplications, opts.Instance.Settings.AuthorizedGaeApplications) + opts.Spec.Settings.AvailabilityType = gcp.LateInitializeString(opts.Spec.Settings.AvailabilityType, opts.Instance.Settings.AvailabilityType) + opts.Spec.Settings.CrashSafeReplicationEnabled = gcp.LateInitializeBool(opts.Spec.Settings.CrashSafeReplicationEnabled, opts.Instance.Settings.CrashSafeReplicationEnabled) + + opts.Spec.Settings.DataDiskType = gcp.LateInitializeString(opts.Spec.Settings.DataDiskType, opts.Instance.Settings.DataDiskType) + opts.Spec.Settings.PricingPlan = gcp.LateInitializeString(opts.Spec.Settings.PricingPlan, opts.Instance.Settings.PricingPlan) + opts.Spec.Settings.ReplicationType = gcp.LateInitializeString(opts.Spec.Settings.ReplicationType, opts.Instance.Settings.ReplicationType) + opts.Spec.Settings.UserLabels = gcp.LateInitializeStringMap(opts.Spec.Settings.UserLabels, opts.Instance.Settings.UserLabels) + opts.Spec.Settings.DataDiskSizeGb = gcp.LateInitializeInt64(opts.Spec.Settings.DataDiskSizeGb, opts.Instance.Settings.DataDiskSizeGb) + opts.Spec.Settings.DatabaseReplicationEnabled = gcp.LateInitializeBool(opts.Spec.Settings.DatabaseReplicationEnabled, opts.Instance.Settings.DatabaseReplicationEnabled) + opts.Spec.Settings.StorageAutoResizeLimit = gcp.LateInitializeInt64(opts.Spec.Settings.StorageAutoResizeLimit, opts.Instance.Settings.StorageAutoResizeLimit) + if opts.Spec.Settings.StorageAutoResize == nil { + opts.Spec.Settings.StorageAutoResize = opts.Instance.Settings.StorageAutoResize } // If storage auto resize enabled, GCP does not allow setting a smaller // size but allows increasing it. Here, we set desired size as observed // if it is bigger than the current value which would allows us to get // in sync with the actual value but still allow us to increase it. - if gcp.BoolValue(spec.Settings.StorageAutoResize) && gcp.Int64Value(spec.Settings.DataDiskSizeGb) < in.Settings.DataDiskSizeGb { - spec.Settings.DataDiskSizeGb = gcp.Int64Ptr(in.Settings.DataDiskSizeGb) + if gcp.BoolValue(opts.Spec.Settings.StorageAutoResize) && gcp.Int64Value(opts.Spec.Settings.DataDiskSizeGb) < opts.Instance.Settings.DataDiskSizeGb { + opts.Spec.Settings.DataDiskSizeGb = gcp.Int64Ptr(opts.Instance.Settings.DataDiskSizeGb) } - if len(spec.Settings.DatabaseFlags) == 0 && len(in.Settings.DatabaseFlags) != 0 { - spec.Settings.DatabaseFlags = make([]*v1beta1.DatabaseFlags, len(in.Settings.DatabaseFlags)) - for i, val := range in.Settings.DatabaseFlags { - spec.Settings.DatabaseFlags[i] = &v1beta1.DatabaseFlags{ + if len(opts.Spec.Settings.DatabaseFlags) == 0 && len(opts.Instance.Settings.DatabaseFlags) != 0 { + opts.Spec.Settings.DatabaseFlags = make([]*v1beta1.DatabaseFlags, len(opts.Instance.Settings.DatabaseFlags)) + for i, val := range opts.Instance.Settings.DatabaseFlags { + opts.Spec.Settings.DatabaseFlags[i] = &v1beta1.DatabaseFlags{ Name: val.Name, Value: val.Value, } } } - if in.Settings.BackupConfiguration != nil { - if spec.Settings.BackupConfiguration == nil { - spec.Settings.BackupConfiguration = &v1beta1.BackupConfiguration{} + if opts.Instance.Settings.BackupConfiguration != nil { + if opts.Spec.Settings.BackupConfiguration == nil { + opts.Spec.Settings.BackupConfiguration = &v1beta1.BackupConfiguration{} } - spec.Settings.BackupConfiguration.BinaryLogEnabled = gcp.LateInitializeBool( - spec.Settings.BackupConfiguration.BinaryLogEnabled, - in.Settings.BackupConfiguration.BinaryLogEnabled) - spec.Settings.BackupConfiguration.Enabled = gcp.LateInitializeBool( - spec.Settings.BackupConfiguration.Enabled, - in.Settings.BackupConfiguration.Enabled) - spec.Settings.BackupConfiguration.Location = gcp.LateInitializeString( - spec.Settings.BackupConfiguration.Location, - in.Settings.BackupConfiguration.Location) - spec.Settings.BackupConfiguration.ReplicationLogArchivingEnabled = gcp.LateInitializeBool( - spec.Settings.BackupConfiguration.ReplicationLogArchivingEnabled, - in.Settings.BackupConfiguration.ReplicationLogArchivingEnabled) - spec.Settings.BackupConfiguration.StartTime = gcp.LateInitializeString( - spec.Settings.BackupConfiguration.StartTime, - in.Settings.BackupConfiguration.StartTime) - spec.Settings.BackupConfiguration.PointInTimeRecoveryEnabled = gcp.LateInitializeBool( - spec.Settings.BackupConfiguration.PointInTimeRecoveryEnabled, - in.Settings.BackupConfiguration.PointInTimeRecoveryEnabled) + opts.Spec.Settings.BackupConfiguration.BinaryLogEnabled = gcp.LateInitializeBool( + opts.Spec.Settings.BackupConfiguration.BinaryLogEnabled, + opts.Instance.Settings.BackupConfiguration.BinaryLogEnabled) + opts.Spec.Settings.BackupConfiguration.Enabled = gcp.LateInitializeBool( + opts.Spec.Settings.BackupConfiguration.Enabled, + opts.Instance.Settings.BackupConfiguration.Enabled) + opts.Spec.Settings.BackupConfiguration.Location = gcp.LateInitializeString( + opts.Spec.Settings.BackupConfiguration.Location, + opts.Instance.Settings.BackupConfiguration.Location) + opts.Spec.Settings.BackupConfiguration.ReplicationLogArchivingEnabled = gcp.LateInitializeBool( + opts.Spec.Settings.BackupConfiguration.ReplicationLogArchivingEnabled, + opts.Instance.Settings.BackupConfiguration.ReplicationLogArchivingEnabled) + opts.Spec.Settings.BackupConfiguration.StartTime = gcp.LateInitializeString( + opts.Spec.Settings.BackupConfiguration.StartTime, + opts.Instance.Settings.BackupConfiguration.StartTime) + opts.Spec.Settings.BackupConfiguration.PointInTimeRecoveryEnabled = gcp.LateInitializeBool( + opts.Spec.Settings.BackupConfiguration.PointInTimeRecoveryEnabled, + opts.Instance.Settings.BackupConfiguration.PointInTimeRecoveryEnabled) } - if in.Settings.IpConfiguration != nil { - if spec.Settings.IPConfiguration == nil { - spec.Settings.IPConfiguration = &v1beta1.IPConfiguration{} + if opts.Instance.Settings.IpConfiguration != nil { + if opts.Spec.Settings.IPConfiguration == nil { + opts.Spec.Settings.IPConfiguration = &v1beta1.IPConfiguration{} } - spec.Settings.IPConfiguration.Ipv4Enabled = gcp.LateInitializeBool(spec.Settings.IPConfiguration.Ipv4Enabled, in.Settings.IpConfiguration.Ipv4Enabled) - spec.Settings.IPConfiguration.PrivateNetwork = gcp.LateInitializeString(spec.Settings.IPConfiguration.PrivateNetwork, in.Settings.IpConfiguration.PrivateNetwork) - spec.Settings.IPConfiguration.RequireSsl = gcp.LateInitializeBool(spec.Settings.IPConfiguration.RequireSsl, in.Settings.IpConfiguration.RequireSsl) - if len(in.Settings.IpConfiguration.AuthorizedNetworks) != 0 && len(spec.Settings.IPConfiguration.AuthorizedNetworks) == 0 { - spec.Settings.IPConfiguration.AuthorizedNetworks = make([]*v1beta1.ACLEntry, len(in.Settings.IpConfiguration.AuthorizedNetworks)) - for i, val := range in.Settings.IpConfiguration.AuthorizedNetworks { - spec.Settings.IPConfiguration.AuthorizedNetworks[i] = &v1beta1.ACLEntry{ + opts.Spec.Settings.IPConfiguration.Ipv4Enabled = gcp.LateInitializeBool(opts.Spec.Settings.IPConfiguration.Ipv4Enabled, opts.Instance.Settings.IpConfiguration.Ipv4Enabled) + opts.Spec.Settings.IPConfiguration.PrivateNetwork = gcp.LateInitializeString(opts.Spec.Settings.IPConfiguration.PrivateNetwork, opts.Instance.Settings.IpConfiguration.PrivateNetwork) + opts.Spec.Settings.IPConfiguration.RequireSsl = gcp.LateInitializeBool(opts.Spec.Settings.IPConfiguration.RequireSsl, opts.Instance.Settings.IpConfiguration.RequireSsl) + if len(opts.Instance.Settings.IpConfiguration.AuthorizedNetworks) != 0 && len(opts.Spec.Settings.IPConfiguration.AuthorizedNetworks) == 0 { + opts.Spec.Settings.IPConfiguration.AuthorizedNetworks = make([]*v1beta1.ACLEntry, len(opts.Instance.Settings.IpConfiguration.AuthorizedNetworks)) + for i, val := range opts.Instance.Settings.IpConfiguration.AuthorizedNetworks { + opts.Spec.Settings.IPConfiguration.AuthorizedNetworks[i] = &v1beta1.ACLEntry{ ExpirationTime: &val.ExpirationTime, Name: &val.Name, Value: &val.Value, @@ -266,51 +296,71 @@ func LateInitializeSpec(spec *v1beta1.CloudSQLInstanceParameters, in sqladmin.Da } } } - if in.Settings.LocationPreference != nil { - if spec.Settings.LocationPreference == nil { - spec.Settings.LocationPreference = &v1beta1.LocationPreference{} + if opts.Instance.Settings.LocationPreference != nil { + if opts.Spec.Settings.LocationPreference == nil { + opts.Spec.Settings.LocationPreference = &v1beta1.LocationPreference{} } - spec.Settings.LocationPreference.Zone = gcp.LateInitializeString(spec.Settings.LocationPreference.Zone, in.Settings.LocationPreference.Zone) - spec.Settings.LocationPreference.FollowGaeApplication = gcp.LateInitializeString(spec.Settings.LocationPreference.FollowGaeApplication, in.Settings.LocationPreference.FollowGaeApplication) + opts.Spec.Settings.LocationPreference.Zone = gcp.LateInitializeString(opts.Spec.Settings.LocationPreference.Zone, opts.Instance.Settings.LocationPreference.Zone) + opts.Spec.Settings.LocationPreference.FollowGaeApplication = gcp.LateInitializeString(opts.Spec.Settings.LocationPreference.FollowGaeApplication, opts.Instance.Settings.LocationPreference.FollowGaeApplication) } - if in.Settings.MaintenanceWindow != nil { - if spec.Settings.MaintenanceWindow == nil { - spec.Settings.MaintenanceWindow = &v1beta1.MaintenanceWindow{} + if opts.Instance.Settings.MaintenanceWindow != nil { + if opts.Spec.Settings.MaintenanceWindow == nil { + opts.Spec.Settings.MaintenanceWindow = &v1beta1.MaintenanceWindow{} } - spec.Settings.MaintenanceWindow.UpdateTrack = gcp.LateInitializeString(spec.Settings.MaintenanceWindow.UpdateTrack, in.Settings.MaintenanceWindow.UpdateTrack) - spec.Settings.MaintenanceWindow.Day = gcp.LateInitializeInt64(spec.Settings.MaintenanceWindow.Day, in.Settings.MaintenanceWindow.Day) - spec.Settings.MaintenanceWindow.Hour = gcp.LateInitializeInt64(spec.Settings.MaintenanceWindow.Hour, in.Settings.MaintenanceWindow.Hour) + opts.Spec.Settings.MaintenanceWindow.UpdateTrack = gcp.LateInitializeString(opts.Spec.Settings.MaintenanceWindow.UpdateTrack, opts.Instance.Settings.MaintenanceWindow.UpdateTrack) + opts.Spec.Settings.MaintenanceWindow.Day = gcp.LateInitializeInt64(opts.Spec.Settings.MaintenanceWindow.Day, opts.Instance.Settings.MaintenanceWindow.Day) + opts.Spec.Settings.MaintenanceWindow.Hour = gcp.LateInitializeInt64(opts.Spec.Settings.MaintenanceWindow.Hour, opts.Instance.Settings.MaintenanceWindow.Hour) } } - if in.DiskEncryptionConfiguration != nil { - if spec.DiskEncryptionConfiguration == nil { - spec.DiskEncryptionConfiguration = &v1beta1.DiskEncryptionConfiguration{} + if opts.Instance.DiskEncryptionConfiguration != nil { + if opts.Spec.DiskEncryptionConfiguration == nil { + opts.Spec.DiskEncryptionConfiguration = &v1beta1.DiskEncryptionConfiguration{} } - if spec.DiskEncryptionConfiguration.KmsKeyName == "" { - spec.DiskEncryptionConfiguration.KmsKeyName = in.DiskEncryptionConfiguration.KmsKeyName + if opts.Spec.DiskEncryptionConfiguration.KmsKeyName == "" { + opts.Spec.DiskEncryptionConfiguration.KmsKeyName = opts.Instance.DiskEncryptionConfiguration.KmsKeyName } } - if in.FailoverReplica != nil { - if spec.FailoverReplica == nil { - spec.FailoverReplica = &v1beta1.DatabaseInstanceFailoverReplicaSpec{ - Name: in.FailoverReplica.Name, + if opts.Instance.FailoverReplica != nil { + if opts.Spec.FailoverReplica == nil { + opts.Spec.FailoverReplica = &v1beta1.DatabaseInstanceFailoverReplicaSpec{ + Name: opts.Instance.FailoverReplica.Name, } } } - if in.OnPremisesConfiguration != nil { - if spec.OnPremisesConfiguration == nil { - spec.OnPremisesConfiguration = &v1beta1.OnPremisesConfiguration{ - HostPort: in.OnPremisesConfiguration.HostPort, + if opts.Instance.OnPremisesConfiguration != nil { + if opts.Spec.OnPremisesConfiguration == nil { + opts.Spec.OnPremisesConfiguration = &v1beta1.OnPremisesConfiguration{ + HostPort: opts.Instance.OnPremisesConfiguration.HostPort, } } } + + if opts.Instance.ReplicaConfiguration != nil { + if opts.Spec.ReplicaConfiguration.FailoverTarget == nil { + opts.Spec.ReplicaConfiguration = &v1beta1.ReplicaConfiguration{ + FailoverTarget: &opts.Instance.ReplicaConfiguration.FailoverTarget, + } + } + if opts.Instance.ReplicaConfiguration.MysqlReplicaConfiguration != nil { + if opts.Spec.ReplicaConfiguration.MysqlReplicaConfiguration == nil { + opts.Spec.ReplicaConfiguration.MysqlReplicaConfiguration = &v1beta1.MySqlReplicaConfiguration{ + MasterHeartbeatPeriod: &opts.Instance.ReplicaConfiguration.MysqlReplicaConfiguration.MasterHeartbeatPeriod, + } + } + opts.Spec.ReplicaConfiguration.MysqlReplicaConfiguration.DumpFilePath = gcp.LateInitializeString(opts.Spec.ReplicaConfiguration.MysqlReplicaConfiguration.DumpFilePath, opts.Instance.ReplicaConfiguration.MysqlReplicaConfiguration.DumpFilePath) + opts.Spec.ReplicaConfiguration.MysqlReplicaConfiguration.ConnectRetryInterval = gcp.LateInitializeInt64(opts.Spec.ReplicaConfiguration.MysqlReplicaConfiguration.ConnectRetryInterval ,opts.Instance.ReplicaConfiguration.MysqlReplicaConfiguration.ConnectRetryInterval) + opts.Spec.ReplicaConfiguration.MysqlReplicaConfiguration.MasterHeartbeatPeriod = gcp.LateInitializeInt64(opts.Spec.ReplicaConfiguration.MysqlReplicaConfiguration.MasterHeartbeatPeriod, opts.Instance.ReplicaConfiguration.MysqlReplicaConfiguration.MasterHeartbeatPeriod) + opts.Spec.ReplicaConfiguration.MysqlReplicaConfiguration.VerifyServerCertificate = gcp.LateInitializeBool(opts.Spec.ReplicaConfiguration.MysqlReplicaConfiguration.VerifyServerCertificate, opts.Instance.ReplicaConfiguration.MysqlReplicaConfiguration.VerifyServerCertificate) + //supplyReplicaConfigurationCredentials(opts.Instance, opts.Spec.ReplicaConfiguration, opts.Secret) + } + } } // IsUpToDate checks whether current state is up-to-date compared to the given // set of parameters. -func IsUpToDate(name string, in *v1beta1.CloudSQLInstanceParameters, observed *sqladmin.DatabaseInstance) (bool, error) { - generated, err := copystructure.Copy(observed) +func IsUpToDate(opts CloudSQLOptions) (bool, error) { + generated, err := copystructure.Copy(opts.Instance) if err != nil { return true, errors.Wrap(err, errCheckUpToDate) } @@ -318,8 +368,12 @@ func IsUpToDate(name string, in *v1beta1.CloudSQLInstanceParameters, observed *s if !ok { return true, errors.New(errCheckUpToDate) } - GenerateDatabaseInstance(name, *in, desired) - return cmp.Equal(desired, observed, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(sqladmin.DatabaseInstance{}, "Settings.IpConfiguration.ForceSendFields")), nil + err = GenerateDatabaseInstance(opts) + if err != nil { + return false, err + } + + return cmp.Equal(desired, opts.Instance, cmpopts.EquateEmpty(), cmpopts.IgnoreFields(sqladmin.DatabaseInstance{}, "Settings.IpConfiguration.ForceSendFields", "ReplicaConfiguration.MysqlReplicaConfiguration")), nil } // DatabaseUserName returns default database user name base on database version @@ -346,3 +400,53 @@ func GetServerCACertificate(in sqladmin.DatabaseInstance) map[string][]byte { v1beta1.CloudSQLSecretServerCACertificateSha1FingerprintKey: []byte(in.ServerCaCert.Sha1Fingerprint), } } + + +// supplyReplicaConfigurationCredentials Fetch Credentials from the corev1.Secret +func supplyReplicaConfigurationCredentials(in *sqladmin.DatabaseInstance, rc *v1beta1.ReplicaConfiguration, sc *corev1.Secret) error { + + caCert, err := extractValue(sc, rc.MysqlReplicaConfiguration.CaCertificateKey) + if err != nil { + return err + } + in.ReplicaConfiguration.MysqlReplicaConfiguration.CaCertificate = caCert + + clientCert, err := extractValue(sc, rc.MysqlReplicaConfiguration.ClientCertificateKey) + if err != nil { + return err + } + in.ReplicaConfiguration.MysqlReplicaConfiguration.ClientCertificate = clientCert + + clientKey, err := extractValue(sc, rc.MysqlReplicaConfiguration.ClientKey) + if err != nil { + return err + } + in.ReplicaConfiguration.MysqlReplicaConfiguration.ClientKey = clientKey + + password, err := extractValue(sc, rc.MysqlReplicaConfiguration.PasswordKey) + if err != nil { + return err + } + in.ReplicaConfiguration.MysqlReplicaConfiguration.Password = password + + username, err := extractValue(sc, rc.MysqlReplicaConfiguration.UsernameKey) + if err != nil { + return err + } + in.ReplicaConfiguration.MysqlReplicaConfiguration.Username = username + + return nil +} + +// extractValue extract value from the given Secret +func extractValue(sc *corev1.Secret, key *string) (string, error) { + + if key != nil && sc != nil { + if value, ok := sc.Data[*key]; !ok { + return "", errors.Errorf(errSecretKey, *key) + } else { + return string(value), nil + } + } + return "", nil +} diff --git a/pkg/controller/database/cloudsql.go b/pkg/controller/database/cloudsql.go index baae59b43..94611a196 100644 --- a/pkg/controller/database/cloudsql.go +++ b/pkg/controller/database/cloudsql.go @@ -22,6 +22,8 @@ import ( "github.com/google/go-cmp/cmp" sqladmin "google.golang.org/api/sqladmin/v1beta4" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/workqueue" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -38,6 +40,7 @@ import ( "github.com/crossplane/provider-gcp/apis/database/v1beta1" gcp "github.com/crossplane/provider-gcp/pkg/clients" "github.com/crossplane/provider-gcp/pkg/clients/cloudsql" + corev1 "k8s.io/api/core/v1" ) const ( @@ -52,6 +55,7 @@ const ( errGetFailed = "cannot get the CloudSQL instance" errGeneratePassword = "cannot generate root password" errCheckUpToDate = "cannot determine if CloudSQL instance is up to date" + errGetSecretFailed = "failed to get Kubernetes secret for spec.replicaConfiguration.mysqlReplicaConfiguration.secretRef" ) // SetupCloudSQLInstance adds a controller that reconciles @@ -105,8 +109,21 @@ func (c *cloudsqlExternal) Observe(ctx context.Context, mg resource.Managed) (ma if err != nil { return managed.ExternalObservation{}, errors.Wrap(resource.Ignore(gcp.IsErrorNotFound, err), errGetFailed) } + + // in case secretRef is defined, fetch the Secret + var sc *corev1.Secret + if cr.Spec.ForProvider.ReplicaConfiguration != nil && + cr.Spec.ForProvider.ReplicaConfiguration.MysqlReplicaConfiguration != nil && + cr.Spec.ForProvider.ReplicaConfiguration.MysqlReplicaConfiguration.SecretRef != nil { + + sc, err = c.fetchSecret(ctx, cr.Spec.ForProvider.ReplicaConfiguration.MysqlReplicaConfiguration.SecretRef) + if err != nil { + return managed.ExternalObservation{}, errors.Wrap(err, errGetSecretFailed) + } + } + currentSpec := cr.Spec.ForProvider.DeepCopy() - cloudsql.LateInitializeSpec(&cr.Spec.ForProvider, *instance) + cloudsql.LateInitializeSpec(cloudsql.CloudSQLOptions{Instance: instance, Spec: &cr.Spec.ForProvider, Secret: sc}) // TODO(muvaf): reflection in production code might cause performance bottlenecks. Generating comparison // methods would make more sense. if !cmp.Equal(currentSpec, &cr.Spec.ForProvider) { @@ -124,7 +141,7 @@ func (c *cloudsqlExternal) Observe(ctx context.Context, mg resource.Managed) (ma cr.Status.SetConditions(xpv1.Unavailable()) } - upToDate, err := cloudsql.IsUpToDate(meta.GetExternalName(cr), &cr.Spec.ForProvider, instance) + upToDate, err := cloudsql.IsUpToDate(cloudsql.CloudSQLOptions{Name: meta.GetExternalName(cr), Spec: &cr.Spec.ForProvider, Instance: instance, Secret: sc}) if err != nil { return managed.ExternalObservation{}, errors.Wrap(err, errCheckUpToDate) } @@ -142,7 +159,24 @@ func (c *cloudsqlExternal) Create(ctx context.Context, mg resource.Managed) (man } cr.SetConditions(xpv1.Creating()) instance := &sqladmin.DatabaseInstance{} - cloudsql.GenerateDatabaseInstance(meta.GetExternalName(cr), cr.Spec.ForProvider, instance) + + // in case secretRef is defined, fetch the Secret + var sc *corev1.Secret + if cr.Spec.ForProvider.ReplicaConfiguration != nil && + cr.Spec.ForProvider.ReplicaConfiguration.MysqlReplicaConfiguration != nil && + cr.Spec.ForProvider.ReplicaConfiguration.MysqlReplicaConfiguration.SecretRef != nil { + + var err error + sc, err = c.fetchSecret(ctx, cr.Spec.ForProvider.ReplicaConfiguration.MysqlReplicaConfiguration.SecretRef) + if err != nil { + return managed.ExternalCreation{}, errors.Wrap(err, errGetSecretFailed) + } + } + + err := cloudsql.GenerateDatabaseInstance(cloudsql.CloudSQLOptions{Name: meta.GetExternalName(cr), Instance: instance, Spec: &cr.Spec.ForProvider, Secret: sc}) + if err != nil { + return managed.ExternalCreation{}, errors.Wrap(err, errCreateFailed) + } pw, err := password.Generate() if err != nil { return managed.ExternalCreation{}, errors.Wrap(err, errGeneratePassword) @@ -172,11 +206,27 @@ func (c *cloudsqlExternal) Update(ctx context.Context, mg resource.Managed) (man if cr.Status.AtProvider.State == v1beta1.StateCreating { return managed.ExternalUpdate{}, nil } + + // in case secretRef is defined, fetch the Secret + var sc *corev1.Secret + if cr.Spec.ForProvider.ReplicaConfiguration != nil && + cr.Spec.ForProvider.ReplicaConfiguration.MysqlReplicaConfiguration != nil && + cr.Spec.ForProvider.ReplicaConfiguration.MysqlReplicaConfiguration.SecretRef != nil { + + var err error + sc, err = c.fetchSecret(ctx, cr.Spec.ForProvider.ReplicaConfiguration.MysqlReplicaConfiguration.SecretRef) + if err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errGetSecretFailed) + } + } instance := &sqladmin.DatabaseInstance{} - cloudsql.GenerateDatabaseInstance(meta.GetExternalName(cr), cr.Spec.ForProvider, instance) + err := cloudsql.GenerateDatabaseInstance(cloudsql.CloudSQLOptions{Name: meta.GetExternalName(cr), Spec: &cr.Spec.ForProvider, Instance: instance, Secret: sc}) + if err != nil { + return managed.ExternalUpdate{}, errors.Wrap(err, errUpdateFailed) + } // TODO(muvaf): the returned operation handle could help us not to send Patch // request aggressively. - _, err := c.db.Patch(c.projectID, meta.GetExternalName(cr), instance).Context(ctx).Do() + _, err = c.db.Patch(c.projectID, meta.GetExternalName(cr), instance).Context(ctx).Do() return managed.ExternalUpdate{}, errors.Wrap(err, errUpdateFailed) } @@ -242,3 +292,15 @@ func (t *cloudsqlTagger) Initialize(ctx context.Context, mg resource.Managed) er } return errors.Wrap(t.kube.Update(ctx, cr), errManagedUpdateFailed) } + +// fetchSecret get Secret from SecretReference +func (c *cloudsqlExternal) fetchSecret(ctx context.Context, ref *xpv1.SecretReference) (*corev1.Secret, error) { + nn := types.NamespacedName{ + Name: ref.Name, + Namespace: ref.Namespace, + } + sc := &corev1.Secret{} + err := c.kube.Get(ctx, nn, sc) + + return sc, err +} diff --git a/pkg/controller/database/cloudsql_test.go b/pkg/controller/database/cloudsql_test.go index 22f0d7512..82801f4c4 100644 --- a/pkg/controller/database/cloudsql_test.go +++ b/pkg/controller/database/cloudsql_test.go @@ -206,7 +206,7 @@ func TestObserve(t *testing.T) { w.WriteHeader(http.StatusOK) instance := instance(withBackupConfigurationStartTime("22:00")) db := &sqladmin.DatabaseInstance{} - cloudsql.GenerateDatabaseInstance(meta.GetExternalName(instance), instance.Spec.ForProvider, db) + cloudsql.GenerateDatabaseInstance(cloudsql.CloudSQLOptions{Name: meta.GetExternalName(instance), Spec: &instance.Spec.ForProvider, Instance: db}) _ = json.NewEncoder(w).Encode(db) }), kube: &test.MockClient{ @@ -229,7 +229,7 @@ func TestObserve(t *testing.T) { } w.WriteHeader(http.StatusOK) db := &sqladmin.DatabaseInstance{} - cloudsql.GenerateDatabaseInstance(meta.GetExternalName(instance()), instance().Spec.ForProvider, db) + cloudsql.GenerateDatabaseInstance(cloudsql.CloudSQLOptions{Name: meta.GetExternalName(instance()), Spec: &instance().Spec.ForProvider, Instance: db }) db.State = v1beta1.StateCreating _ = json.NewEncoder(w).Encode(db) }), @@ -253,7 +253,7 @@ func TestObserve(t *testing.T) { } w.WriteHeader(http.StatusOK) db := &sqladmin.DatabaseInstance{} - cloudsql.GenerateDatabaseInstance(meta.GetExternalName(instance()), instance().Spec.ForProvider, db) + cloudsql.GenerateDatabaseInstance(cloudsql.CloudSQLOptions{Name: meta.GetExternalName(instance()), Spec: &instance().Spec.ForProvider, Instance: db}) db.State = v1beta1.StateMaintenance _ = json.NewEncoder(w).Encode(db) }), @@ -277,7 +277,7 @@ func TestObserve(t *testing.T) { } w.WriteHeader(http.StatusOK) db := &sqladmin.DatabaseInstance{} - cloudsql.GenerateDatabaseInstance(meta.GetExternalName(instance()), instance().Spec.ForProvider, db) + cloudsql.GenerateDatabaseInstance(cloudsql.CloudSQLOptions{Name: meta.GetExternalName(instance()), Spec: &instance().Spec.ForProvider, Instance: db}) db.ConnectionName = connectionName db.State = v1beta1.StateRunnable _ = json.NewEncoder(w).Encode(db)