From 6ee12bbbe67c22245f1130a7d2a841b259ec829f Mon Sep 17 00:00:00 2001 From: ecmadao Date: Wed, 5 Feb 2025 14:54:48 +0800 Subject: [PATCH 1/7] chore: use set for unordered collection --- VERSION | 2 +- api/client.go | 2 + client/database.go | 28 +++++++ examples/environments/main.tf | 2 +- examples/groups/main.tf | 2 +- examples/instances/main.tf | 2 +- examples/policies/main.tf | 2 +- examples/projects/main.tf | 2 +- examples/settings/main.tf | 2 +- examples/setup/main.tf | 2 +- examples/users/main.tf | 2 +- examples/vcs/main.tf | 2 +- provider/data_source_database_catalog.go | 73 +++++++++++++++-- provider/data_source_instance.go | 3 +- provider/data_source_policy.go | 35 +++++++- provider/data_source_project.go | 16 +++- provider/data_source_setting.go | 29 +++++-- provider/internal/mock_client.go | 16 ++++ provider/resource_database_catalog.go | 33 +++++--- provider/resource_instance.go | 100 +++++++++++++---------- provider/resource_policy.go | 7 +- provider/resource_project.go | 62 ++++++++++---- provider/resource_setting.go | 22 +++-- 23 files changed, 342 insertions(+), 104 deletions(-) diff --git a/VERSION b/VERSION index 337a6a8..e5a4a5e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.8 \ No newline at end of file +1.0.9 \ No newline at end of file diff --git a/api/client.go b/api/client.go index 56f0864..a1e0227 100644 --- a/api/client.go +++ b/api/client.go @@ -59,6 +59,8 @@ type Client interface { ListDatabase(ctx context.Context, instanceID, filter string) (*v1pb.ListDatabasesResponse, error) // UpdateDatabase patches the database. UpdateDatabase(ctx context.Context, patch *v1pb.Database, updateMasks []string) (*v1pb.Database, error) + // BatchUpdateDatabases batch updates databases. + BatchUpdateDatabases(ctx context.Context, request *v1pb.BatchUpdateDatabasesRequest) (*v1pb.BatchUpdateDatabasesResponse, error) // GetDatabaseCatalog gets the database catalog by the database full name. GetDatabaseCatalog(ctx context.Context, databaseName string) (*v1pb.DatabaseCatalog, error) // UpdateDatabaseCatalog patches the database catalog. diff --git a/client/database.go b/client/database.go index 00b29cf..e3e95a4 100644 --- a/client/database.go +++ b/client/database.go @@ -5,8 +5,10 @@ import ( "fmt" "net/http" "net/url" + "strings" v1pb "github.com/bytebase/bytebase/proto/generated-go/v1" + "google.golang.org/protobuf/encoding/protojson" ) // GetDatabase gets the database by the database full name. @@ -64,6 +66,32 @@ func (c *client) UpdateDatabase(ctx context.Context, patch *v1pb.Database, updat return &res, nil } +// BatchUpdateDatabases batch updates databases. +func (c *client) BatchUpdateDatabases(ctx context.Context, request *v1pb.BatchUpdateDatabasesRequest) (*v1pb.BatchUpdateDatabasesResponse, error) { + requestURL := fmt.Sprintf("%s/%s/instances/-/databases:batchUpdate", c.url, c.version) + payload, err := protojson.Marshal(request) + if err != nil { + return nil, err + } + + req, err := http.NewRequestWithContext(ctx, "POST", requestURL, strings.NewReader(string(payload))) + if err != nil { + return nil, err + } + + body, err := c.doRequest(req) + if err != nil { + return nil, err + } + + var res v1pb.BatchUpdateDatabasesResponse + if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil { + return nil, err + } + + return &res, nil +} + // GetDatabaseCatalog gets the database catalog by the database full name. func (c *client) GetDatabaseCatalog(ctx context.Context, databaseName string) (*v1pb.DatabaseCatalog, error) { body, err := c.getResource(ctx, fmt.Sprintf("%s/catalog", databaseName)) diff --git a/examples/environments/main.tf b/examples/environments/main.tf index a22b79a..19fc9d0 100644 --- a/examples/environments/main.tf +++ b/examples/environments/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { bytebase = { - version = "1.0.8" + version = "1.0.9" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/groups/main.tf b/examples/groups/main.tf index 9fa05f3..1577f8b 100644 --- a/examples/groups/main.tf +++ b/examples/groups/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.8" + version = "1.0.9" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/instances/main.tf b/examples/instances/main.tf index ba73b15..f9fef86 100644 --- a/examples/instances/main.tf +++ b/examples/instances/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { bytebase = { - version = "1.0.8" + version = "1.0.9" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/policies/main.tf b/examples/policies/main.tf index f561371..48fb027 100644 --- a/examples/policies/main.tf +++ b/examples/policies/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.8" + version = "1.0.9" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/projects/main.tf b/examples/projects/main.tf index e76bbc5..a405d25 100644 --- a/examples/projects/main.tf +++ b/examples/projects/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { bytebase = { - version = "1.0.8" + version = "1.0.9" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/settings/main.tf b/examples/settings/main.tf index dbf691c..51d14f1 100644 --- a/examples/settings/main.tf +++ b/examples/settings/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.8" + version = "1.0.9" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/setup/main.tf b/examples/setup/main.tf index b5f8506..0b2636b 100644 --- a/examples/setup/main.tf +++ b/examples/setup/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.8" + version = "1.0.9" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/users/main.tf b/examples/users/main.tf index 0972cc0..9407675 100644 --- a/examples/users/main.tf +++ b/examples/users/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.8" + version = "1.0.9" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/vcs/main.tf b/examples/vcs/main.tf index 12d72e5..91f03a5 100644 --- a/examples/vcs/main.tf +++ b/examples/vcs/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.8" + version = "1.0.9" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/provider/data_source_database_catalog.go b/provider/data_source_database_catalog.go index e634e71..b43d1cb 100644 --- a/provider/data_source_database_catalog.go +++ b/provider/data_source_database_catalog.go @@ -1,6 +1,7 @@ package provider import ( + "bytes" "context" "fmt" "regexp" @@ -29,7 +30,7 @@ func dataSourceDatabaseCatalog() *schema.Resource { }, "schemas": { Computed: true, - Type: schema.TypeList, + Type: schema.TypeSet, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { @@ -38,7 +39,7 @@ func dataSourceDatabaseCatalog() *schema.Resource { }, "tables": { Computed: true, - Type: schema.TypeList, + Type: schema.TypeSet, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { @@ -52,7 +53,7 @@ func dataSourceDatabaseCatalog() *schema.Resource { }, "columns": { Computed: true, - Type: schema.TypeList, + Type: schema.TypeSet, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { @@ -76,12 +77,19 @@ func dataSourceDatabaseCatalog() *schema.Resource { }, }, }, + Set: func(i interface{}) int { + return internal.ToHashcodeInt(columnHash(i)) + }, }, }, }, + Set: func(i interface{}) int { + return internal.ToHashcodeInt(tableHash(i)) + }, }, }, }, + Set: schemaHash, }, }, } @@ -108,11 +116,11 @@ func setDatabaseCatalog(d *schema.ResourceData, catalog *v1pb.DatabaseCatalog) d } schemaList := []interface{}{} - for _, schema := range catalog.Schemas { + for _, schemaCatalog := range catalog.Schemas { rawSchema := map[string]interface{}{} tableList := []interface{}{} - for _, table := range schema.Tables { + for _, table := range schemaCatalog.Tables { rawTable := map[string]interface{}{} rawTable["name"] = table.Name rawTable["classification"] = table.Classification @@ -126,15 +134,64 @@ func setDatabaseCatalog(d *schema.ResourceData, catalog *v1pb.DatabaseCatalog) d rawColumn["labels"] = column.Labels columnList = append(columnList, rawColumn) } - rawTable["columns"] = columnList + rawTable["columns"] = schema.NewSet(func(i interface{}) int { + return internal.ToHashcodeInt(columnHash(i)) + }, columnList) tableList = append(tableList, rawTable) } - rawSchema["tables"] = tableList + rawSchema["tables"] = schema.NewSet(func(i interface{}) int { + return internal.ToHashcodeInt(tableHash(i)) + }, tableList) schemaList = append(schemaList, rawSchema) } - if err := d.Set("schemas", schemaList); err != nil { + if err := d.Set("schemas", schema.NewSet(schemaHash, schemaList)); err != nil { return diag.Errorf("cannot set schemas: %s", err.Error()) } return nil } + +func columnHash(rawColumn interface{}) string { + var buf bytes.Buffer + column := rawColumn.(map[string]interface{}) + + if v, ok := column["name"].(string); ok { + _, _ = buf.WriteString(fmt.Sprintf("%s-", v)) + } + + return buf.String() +} + +func tableHash(rawTable interface{}) string { + var buf bytes.Buffer + table := rawTable.(map[string]interface{}) + + if v, ok := table["name"].(string); ok { + _, _ = buf.WriteString(fmt.Sprintf("%s-", v)) + } + if columns, ok := table["columns"].(*schema.Set); ok { + for _, column := range columns.List() { + rawColumn := column.(map[string]interface{}) + _, _ = buf.WriteString(columnHash(rawColumn)) + } + } + + return buf.String() +} + +func schemaHash(rawSchema interface{}) int { + var buf bytes.Buffer + raw := rawSchema.(map[string]interface{}) + + if v, ok := raw["name"].(string); ok { + _, _ = buf.WriteString(fmt.Sprintf("%s-", v)) + } + if tables, ok := raw["tables"].(*schema.Set); ok { + for _, table := range tables.List() { + rawTable := table.(map[string]interface{}) + _, _ = buf.WriteString(tableHash(rawTable)) + } + } + + return internal.ToHashcodeInt(buf.String()) +} diff --git a/provider/data_source_instance.go b/provider/data_source_instance.go index bd14105..8bbed10 100644 --- a/provider/data_source_instance.go +++ b/provider/data_source_instance.go @@ -63,7 +63,7 @@ func dataSourceInstance() *schema.Resource { Description: "The maximum number of connections. The default value is 10.", }, "data_sources": { - Type: schema.TypeList, + Type: schema.TypeSet, Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -123,6 +123,7 @@ func dataSourceInstance() *schema.Resource { }, }, }, + Set: dataSourceHash, }, }, } diff --git a/provider/data_source_policy.go b/provider/data_source_policy.go index 51e475f..a4a3325 100644 --- a/provider/data_source_policy.go +++ b/provider/data_source_policy.go @@ -1,6 +1,7 @@ package provider import ( + "bytes" "context" "fmt" "regexp" @@ -83,7 +84,7 @@ func getMaskingExceptionPolicySchema(computed bool) *schema.Schema { Optional: true, Default: nil, MinItems: 0, - Type: schema.TypeList, + Type: schema.TypeSet, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "database": { @@ -134,6 +135,7 @@ func getMaskingExceptionPolicySchema(computed bool) *schema.Schema { }, }, }, + Set: exceptionHash, }, }, }, @@ -243,7 +245,36 @@ func flattenMaskingExceptionPolicy(p *v1pb.MaskingExceptionPolicy) ([]interface{ exceptionList = append(exceptionList, raw) } policy := map[string]interface{}{ - "exceptions": exceptionList, + "exceptions": schema.NewSet(exceptionHash, exceptionList), } return []interface{}{policy}, nil } + +func exceptionHash(rawException interface{}) int { + var buf bytes.Buffer + exception := rawException.(map[string]interface{}) + + if v, ok := exception["database"].(string); ok { + _, _ = buf.WriteString(fmt.Sprintf("%s-", v)) + } + if v, ok := exception["schema"].(string); ok { + _, _ = buf.WriteString(fmt.Sprintf("%s-", v)) + } + if v, ok := exception["table"].(string); ok { + _, _ = buf.WriteString(fmt.Sprintf("%s-", v)) + } + if v, ok := exception["column"].(string); ok { + _, _ = buf.WriteString(fmt.Sprintf("%s-", v)) + } + if v, ok := exception["member"].(string); ok { + _, _ = buf.WriteString(fmt.Sprintf("%s-", v)) + } + if v, ok := exception["action"].(string); ok { + _, _ = buf.WriteString(fmt.Sprintf("%s-", v)) + } + if v, ok := exception["expire_timestamp"].(string); ok { + _, _ = buf.WriteString(fmt.Sprintf("%s-", v)) + } + + return internal.ToHashcodeInt(buf.String()) +} diff --git a/provider/data_source_project.go b/provider/data_source_project.go index 157a7be..db884e9 100644 --- a/provider/data_source_project.go +++ b/provider/data_source_project.go @@ -87,7 +87,7 @@ func dataSourceProject() *schema.Resource { func getProjectDatabasesSchema(computed bool) *schema.Schema { return &schema.Schema{ - Type: schema.TypeList, + Type: schema.TypeSet, Computed: computed, Optional: !computed, Description: "The databases in the project.", @@ -128,6 +128,7 @@ func getProjectDatabasesSchema(computed bool) *schema.Schema { }, }, }, + Set: databaseHash, } } @@ -352,7 +353,8 @@ func setProject( return diag.Errorf("cannot set postgres_database_tenant_mode for project: %s", err.Error()) } - if err := d.Set("databases", flattenDatabaseList(listDBResponse.Databases)); err != nil { + databaseList := flattenDatabaseList(listDBResponse.Databases) + if err := d.Set("databases", schema.NewSet(databaseHash, databaseList)); err != nil { return diag.Errorf("cannot set databases for project: %s", err.Error()) } @@ -367,6 +369,16 @@ func setProject( return nil } +func databaseHash(rawDatabase interface{}) int { + var buf bytes.Buffer + database := rawDatabase.(map[string]interface{}) + + if v, ok := database["name"].(string); ok { + _, _ = buf.WriteString(fmt.Sprintf("%s-", v)) + } + return internal.ToHashcodeInt(buf.String()) +} + func memberHash(rawMember interface{}) int { var buf bytes.Buffer member := rawMember.(map[string]interface{}) diff --git a/provider/data_source_setting.go b/provider/data_source_setting.go index c5240c1..c398b5b 100644 --- a/provider/data_source_setting.go +++ b/provider/data_source_setting.go @@ -1,6 +1,7 @@ package provider import ( + "bytes" "context" "fmt" @@ -72,7 +73,7 @@ func getClassificationSetting(computed bool) *schema.Schema { "levels": { Computed: computed, Optional: true, - Type: schema.TypeList, + Type: schema.TypeSet, MinItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -96,11 +97,12 @@ func getClassificationSetting(computed bool) *schema.Schema { }, }, }, + Set: itemIDHash, }, "classifications": { Computed: computed, Optional: true, - Type: schema.TypeList, + Type: schema.TypeSet, MinItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -130,6 +132,7 @@ func getClassificationSetting(computed bool) *schema.Schema { }, }, }, + Set: itemIDHash, }, }, }, @@ -190,11 +193,13 @@ func getExternalApprovalSetting(computed bool) *schema.Schema { Optional: true, Default: nil, Type: schema.TypeList, + MaxItems: 1, + MinItems: 1, Description: "Configure external nodes in the approval flow. Require ENTERPRISE subscription.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "nodes": { - Type: schema.TypeList, + Type: schema.TypeSet, Computed: computed, Required: !computed, Elem: &schema.Resource{ @@ -219,6 +224,7 @@ func getExternalApprovalSetting(computed bool) *schema.Schema { }, }, }, + Set: itemIDHash, }, }, }, @@ -231,6 +237,8 @@ func getWorkspaceApprovalSetting(computed bool) *schema.Schema { Optional: true, Default: nil, Type: schema.TypeList, + MaxItems: 1, + MinItems: 1, Description: "Configure risk level and approval flow for different tasks. Require ENTERPRISE subscription.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -544,7 +552,7 @@ func flattenClassificationSetting(setting *v1pb.DataClassificationSetting) []int rawLevel["description"] = level.Description rawLevels = append(rawLevels, rawLevel) } - raw["levels"] = rawLevels + raw["levels"] = schema.NewSet(itemIDHash, rawLevels) rawClassifications := []interface{}{} for _, classification := range config.GetClassification() { @@ -555,8 +563,19 @@ func flattenClassificationSetting(setting *v1pb.DataClassificationSetting) []int rawClassification["level"] = classification.LevelId rawClassifications = append(rawClassifications, rawClassification) } - raw["classifications"] = rawClassifications + raw["classifications"] = schema.NewSet(itemIDHash, rawClassifications) } return []interface{}{raw} } + +func itemIDHash(rawItem interface{}) int { + var buf bytes.Buffer + item := rawItem.(map[string]interface{}) + + if v, ok := item["id"].(string); ok { + _, _ = buf.WriteString(fmt.Sprintf("%s-", v)) + } + + return internal.ToHashcodeInt(buf.String()) +} diff --git a/provider/internal/mock_client.go b/provider/internal/mock_client.go index 7502f87..b54ceaf 100644 --- a/provider/internal/mock_client.go +++ b/provider/internal/mock_client.go @@ -412,6 +412,22 @@ func (c *mockClient) UpdateDatabase(ctx context.Context, patch *v1pb.Database, u return db, nil } +// BatchUpdateDatabases batch updates databases. +func (c *mockClient) BatchUpdateDatabases(ctx context.Context, request *v1pb.BatchUpdateDatabasesRequest) (*v1pb.BatchUpdateDatabasesResponse, error) { + for _, req := range request.Requests { + db, err := c.GetDatabase(ctx, req.Database.Name) + if err != nil { + return nil, err + } + if slices.Contains(req.UpdateMask.Paths, "project") { + db.Project = req.Database.Project + } + c.databaseMap[db.Name] = db + } + + return &v1pb.BatchUpdateDatabasesResponse{}, nil +} + // GetDatabaseCatalog gets the database catalog by the database full name. func (c *mockClient) GetDatabaseCatalog(_ context.Context, databaseName string) (*v1pb.DatabaseCatalog, error) { db, ok := c.databaseCatalogMap[databaseName] diff --git a/provider/resource_database_catalog.go b/provider/resource_database_catalog.go index 3d83369..fcca4fb 100644 --- a/provider/resource_database_catalog.go +++ b/provider/resource_database_catalog.go @@ -38,7 +38,7 @@ func resourceDatabaseCatalog() *schema.Resource { }, "schemas": { Required: true, - Type: schema.TypeList, + Type: schema.TypeSet, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { @@ -48,7 +48,7 @@ func resourceDatabaseCatalog() *schema.Resource { }, "tables": { Required: true, - Type: schema.TypeList, + Type: schema.TypeSet, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { @@ -64,7 +64,7 @@ func resourceDatabaseCatalog() *schema.Resource { }, "columns": { Required: true, - Type: schema.TypeList, + Type: schema.TypeSet, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { @@ -88,12 +88,19 @@ func resourceDatabaseCatalog() *schema.Resource { }, }, }, + Set: func(i interface{}) int { + return internal.ToHashcodeInt(columnHash(i)) + }, }, }, }, + Set: func(i interface{}) int { + return internal.ToHashcodeInt(tableHash(i)) + }, }, }, }, + Set: schemaHash, }, }, } @@ -177,7 +184,7 @@ func convertToDatabaseCatalog(d *schema.ResourceData) (*v1pb.DatabaseCatalog, er if !ok || database == "" { return nil, errors.Errorf("invalid database") } - rawSchemaList, ok := d.Get("schemas").([]interface{}) + rawSchemaList, ok := d.Get("schemas").(*schema.Set) if !ok { return nil, errors.Errorf("invalid schemas") } @@ -187,17 +194,17 @@ func convertToDatabaseCatalog(d *schema.ResourceData) (*v1pb.DatabaseCatalog, er Schemas: []*v1pb.SchemaCatalog{}, } - for _, schema := range rawSchemaList { - rawSchema := schema.(map[string]interface{}) - schema := &v1pb.SchemaCatalog{ + for _, raw := range rawSchemaList.List() { + rawSchema := raw.(map[string]interface{}) + schemaCatalog := &v1pb.SchemaCatalog{ Name: rawSchema["name"].(string), } - rawTableList, ok := rawSchema["tables"].([]interface{}) + rawTableList, ok := rawSchema["tables"].(*schema.Set) if !ok { return nil, errors.Errorf("invalid tables") } - for _, table := range rawTableList { + for _, table := range rawTableList.List() { rawTable := table.(map[string]interface{}) table := &v1pb.TableCatalog{ Name: rawTable["name"].(string), @@ -205,11 +212,11 @@ func convertToDatabaseCatalog(d *schema.ResourceData) (*v1pb.DatabaseCatalog, er } columnList := []*v1pb.ColumnCatalog{} - rawColumnList, ok := rawTable["columns"].([]interface{}) + rawColumnList, ok := rawTable["columns"].(*schema.Set) if !ok { return nil, errors.Errorf("invalid columns") } - for _, column := range rawColumnList { + for _, column := range rawColumnList.List() { rawColumn := column.(map[string]interface{}) labels := map[string]string{} for key, val := range rawColumn["labels"].(map[string]interface{}) { @@ -231,10 +238,10 @@ func convertToDatabaseCatalog(d *schema.ResourceData) (*v1pb.DatabaseCatalog, er }, } - schema.Tables = append(schema.Tables, table) + schemaCatalog.Tables = append(schemaCatalog.Tables, table) } - catalog.Schemas = append(catalog.Schemas, schema) + catalog.Schemas = append(catalog.Schemas, schemaCatalog) } return catalog, nil diff --git a/provider/resource_instance.go b/provider/resource_instance.go index 31f8f72..484ae60 100644 --- a/provider/resource_instance.go +++ b/provider/resource_instance.go @@ -1,6 +1,7 @@ package provider import ( + "bytes" "context" "fmt" "regexp" @@ -103,7 +104,7 @@ func resourceInstance() *schema.Resource { Description: "The maximum number of connections.", }, "data_sources": { - Type: schema.TypeList, + Type: schema.TypeSet, Required: true, MinItems: 1, Description: "The connection for the instance. You can configure read-only or admin connection account here.", @@ -177,6 +178,7 @@ func resourceInstance() *schema.Resource { }, }, }, + Set: dataSourceHash, }, }, } @@ -473,7 +475,7 @@ func setInstanceMessage(d *schema.ResourceData, instance *v1pb.Instance) diag.Di if err != nil { return diag.FromErr(err) } - if err := d.Set("data_sources", dataSources); err != nil { + if err := d.Set("data_sources", schema.NewSet(dataSourceHash, dataSources)); err != nil { return diag.Errorf("cannot set data_sources for instance: %s", err.Error()) } @@ -512,51 +514,67 @@ func flattenDataSourceList(d *schema.ResourceData, dataSourceList []*v1pb.DataSo return res, nil } +func dataSourceHash(rawDataSource interface{}) int { + var buf bytes.Buffer + dataSource := rawDataSource.(map[string]interface{}) + + if v, ok := dataSource["id"].(string); ok { + _, _ = buf.WriteString(fmt.Sprintf("%s-", v)) + } + if v, ok := dataSource["type"].(string); ok { + _, _ = buf.WriteString(fmt.Sprintf("%s-", v)) + } + return internal.ToHashcodeInt(buf.String()) +} + func convertDataSourceCreateList(d *schema.ResourceData, validate bool) ([]*v1pb.DataSource, error) { var dataSourceList []*v1pb.DataSource - if rawList, ok := d.Get("data_sources").([]interface{}); ok { - dataSourceTypeMap := map[v1pb.DataSourceType]bool{} - for _, raw := range rawList { - obj := raw.(map[string]interface{}) - dataSource := &v1pb.DataSource{ - Id: obj["id"].(string), - Type: v1pb.DataSourceType(v1pb.DataSourceType_value[obj["type"].(string)]), - } - if dataSourceTypeMap[dataSource.Type] && dataSource.Type == v1pb.DataSourceType_ADMIN { - return nil, errors.Errorf("duplicate data source type ADMIN") - } - dataSourceTypeMap[dataSource.Type] = true + dataSourceSet, ok := d.Get("data_sources").(*schema.Set) + if !ok { + return dataSourceList, nil + } - if v, ok := obj["username"].(string); ok { - dataSource.Username = v - } - if v, ok := obj["password"].(string); ok && v != "" { - dataSource.Password = v - } - if v, ok := obj["ssl_ca"].(string); ok { - dataSource.SslCa = v - } - if v, ok := obj["ssl_cert"].(string); ok { - dataSource.SslCert = v - } - if v, ok := obj["ssl_key"].(string); ok { - dataSource.SslKey = v - } - if v, ok := obj["host"].(string); ok { - dataSource.Host = v - } - if v, ok := obj["port"].(string); ok { - dataSource.Port = v - } - if v, ok := obj["database"].(string); ok { - dataSource.Database = v - } - dataSourceList = append(dataSourceList, dataSource) + dataSourceTypeMap := map[v1pb.DataSourceType]bool{} + for _, raw := range dataSourceSet.List() { + obj := raw.(map[string]interface{}) + dataSource := &v1pb.DataSource{ + Id: obj["id"].(string), + Type: v1pb.DataSourceType(v1pb.DataSourceType_value[obj["type"].(string)]), + } + if dataSourceTypeMap[dataSource.Type] && dataSource.Type == v1pb.DataSourceType_ADMIN { + return nil, errors.Errorf("duplicate data source type ADMIN") } + dataSourceTypeMap[dataSource.Type] = true - if !dataSourceTypeMap[v1pb.DataSourceType_ADMIN] && validate { - return nil, errors.Errorf("data source \"%v\" is required", v1pb.DataSourceType_ADMIN.String()) + if v, ok := obj["username"].(string); ok { + dataSource.Username = v + } + if v, ok := obj["password"].(string); ok && v != "" { + dataSource.Password = v + } + if v, ok := obj["ssl_ca"].(string); ok { + dataSource.SslCa = v + } + if v, ok := obj["ssl_cert"].(string); ok { + dataSource.SslCert = v } + if v, ok := obj["ssl_key"].(string); ok { + dataSource.SslKey = v + } + if v, ok := obj["host"].(string); ok { + dataSource.Host = v + } + if v, ok := obj["port"].(string); ok { + dataSource.Port = v + } + if v, ok := obj["database"].(string); ok { + dataSource.Database = v + } + dataSourceList = append(dataSourceList, dataSource) + } + + if !dataSourceTypeMap[v1pb.DataSourceType_ADMIN] && validate { + return nil, errors.Errorf("data source \"%v\" is required", v1pb.DataSourceType_ADMIN.String()) } return dataSourceList, nil diff --git a/provider/resource_policy.go b/provider/resource_policy.go index ccc211d..f174834 100644 --- a/provider/resource_policy.go +++ b/provider/resource_policy.go @@ -215,11 +215,14 @@ func convertToMaskingExceptionPolicy(d *schema.ResourceData) (*v1pb.MaskingExcep } raw := rawList[0].(map[string]interface{}) - exceptionList := raw["exceptions"].([]interface{}) + exceptionList, ok := raw["exceptions"].(*schema.Set) + if !ok { + return nil, errors.Errorf("invalid exceptions") + } policy := &v1pb.MaskingExceptionPolicy{} - for _, exception := range exceptionList { + for _, exception := range exceptionList.List() { rawException := exception.(map[string]interface{}) databaseFullName := rawException["database"].(string) diff --git a/provider/resource_project.go b/provider/resource_project.go index 21a75f3..2b9f82a 100644 --- a/provider/resource_project.go +++ b/provider/resource_project.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/pkg/errors" "google.golang.org/genproto/googleapis/type/expr" + fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb" "github.com/bytebase/terraform-provider-bytebase/api" "github.com/bytebase/terraform-provider-bytebase/provider/internal" @@ -375,12 +376,13 @@ func updateDatabasesInProject(ctx context.Context, d *schema.ResourceData, clien existedDBMap[db.Name] = db } - rawList, ok := d.Get("databases").([]interface{}) + rawSet, ok := d.Get("databases").(*schema.Set) if !ok { return nil } updatedDBMap := map[string]*v1pb.Database{} - for _, raw := range rawList { + batchTransferDatabases := []*v1pb.UpdateDatabaseRequest{} + for _, raw := range rawSet.List() { obj := raw.(map[string]interface{}) dbName := obj["name"].(string) if _, _, err := internal.GetInstanceDatabaseID(dbName); err != nil { @@ -397,20 +399,52 @@ func updateDatabasesInProject(ctx context.Context, d *schema.ResourceData, clien Project: projectName, Labels: labels, } - if _, err := client.UpdateDatabase(ctx, updatedDBMap[dbName], []string{"project", "label"}); err != nil { - return diag.Errorf("failed to update database %s with error: %v", dbName, err) + if _, ok := existedDBMap[dbName]; !ok { + batchTransferDatabases = append(batchTransferDatabases, &v1pb.UpdateDatabaseRequest{ + Database: updatedDBMap[dbName], + UpdateMask: &fieldmaskpb.FieldMask{ + Paths: []string{"project"}, + }, + }) + } + } + + if len(batchTransferDatabases) > 0 { + if _, err := client.BatchUpdateDatabases(ctx, &v1pb.BatchUpdateDatabasesRequest{ + Requests: batchTransferDatabases, + Parent: "instances/-", + }); err != nil { + return diag.Errorf("failed to assign databases to project %s", projectName) + } + } + + for _, database := range updatedDBMap { + if len(database.Labels) > 0 { + if _, err := client.UpdateDatabase(ctx, database, []string{"label"}); err != nil { + return diag.Errorf("failed to update database %s with error: %v", database.Name, err) + } } } + unassignDatabases := []*v1pb.UpdateDatabaseRequest{} for _, db := range existedDBMap { if _, ok := updatedDBMap[db.Name]; !ok { // move db to default project - if _, err := client.UpdateDatabase(ctx, &v1pb.Database{ - Name: db.Name, - Project: projectName, - }, []string{"project"}); err != nil { - return diag.Errorf("failed to move database %s to project %s with error: %v", db.Name, defaultProj, err) - } + db.Project = defaultProj + unassignDatabases = append(unassignDatabases, &v1pb.UpdateDatabaseRequest{ + Database: db, + UpdateMask: &fieldmaskpb.FieldMask{ + Paths: []string{"project"}, + }, + }) + } + } + if len(unassignDatabases) > 0 { + if _, err := client.BatchUpdateDatabases(ctx, &v1pb.BatchUpdateDatabasesRequest{ + Requests: unassignDatabases, + Parent: "instances/-", + }); err != nil { + return diag.Errorf("failed to move databases to default project") } } @@ -484,11 +518,11 @@ func updateMembersInProject(ctx context.Context, d *schema.ResourceData, client }) } - if !existProjectOwner { - return diag.Errorf("require at least 1 member with roles/projectOwner role") - } - if len(iamPolicy.Bindings) > 0 { + if !existProjectOwner { + return diag.Errorf("require at least 1 member with roles/projectOwner role") + } + if _, err := client.SetProjectIAMPolicy(ctx, projectName, &v1pb.SetIamPolicyRequest{ Policy: iamPolicy, Etag: iamPolicy.Etag, diff --git a/provider/resource_setting.go b/provider/resource_setting.go index 4702a8b..87632cc 100644 --- a/provider/resource_setting.go +++ b/provider/resource_setting.go @@ -176,8 +176,11 @@ func convertToV1ClassificationSetting(d *schema.ResourceData) (*v1pb.DataClassif return nil, errors.Errorf("id is required for classification config") } - rawLevels := raw["levels"].([]interface{}) - for _, level := range rawLevels { + rawLevels := raw["levels"].(*schema.Set) + if !ok { + return nil, errors.Errorf("levels is required for classification config") + } + for _, level := range rawLevels.List() { rawLevel := level.(map[string]interface{}) classificationLevel := &v1pb.DataClassificationSetting_DataClassificationConfig_Level{ Id: rawLevel["id"].(string), @@ -193,8 +196,11 @@ func convertToV1ClassificationSetting(d *schema.ResourceData) (*v1pb.DataClassif dataClassificationConfig.Levels = append(dataClassificationConfig.Levels, classificationLevel) } - rawClassificationss := raw["classifications"].([]interface{}) - for _, classification := range rawClassificationss { + rawClassificationss := raw["classifications"].(*schema.Set) + if !ok { + return nil, errors.Errorf("classifications is required for classification config") + } + for _, classification := range rawClassificationss.List() { rawClassification := classification.(map[string]interface{}) classificationData := &v1pb.DataClassificationSetting_DataClassificationConfig_DataClassification{ Id: rawClassification["id"].(string), @@ -228,10 +234,14 @@ func convertToV1ExternalNodesSetting(d *schema.ResourceData) (*v1pb.ExternalAppr } raw := rawList[0].(map[string]interface{}) - nodes := raw["nodes"].([]interface{}) + nodes, ok := raw["nodes"].(*schema.Set) + if !ok { + return nil, errors.Errorf("missing nodes") + } + externalApprovalSetting := &v1pb.ExternalApprovalSetting{} - for _, node := range nodes { + for _, node := range nodes.List() { rawNode := node.(map[string]interface{}) externalApprovalSetting.Nodes = append(externalApprovalSetting.Nodes, &v1pb.ExternalApprovalSetting_Node{ Id: rawNode["id"].(string), From 92bff8185ef0d099c46229a380984217e5083bfd Mon Sep 17 00:00:00 2001 From: ecmadao Date: Wed, 5 Feb 2025 14:57:44 +0800 Subject: [PATCH 2/7] chore: update doc --- docs/data-sources/database_catalog.md | 6 +++--- docs/data-sources/instance.md | 2 +- docs/data-sources/policy.md | 2 +- docs/data-sources/policy_list.md | 2 +- docs/data-sources/project.md | 2 +- docs/data-sources/project_list.md | 2 +- docs/data-sources/setting.md | 6 +++--- docs/resources/database_catalog.md | 6 +++--- docs/resources/instance.md | 2 +- docs/resources/policy.md | 2 +- docs/resources/project.md | 2 +- docs/resources/setting.md | 6 +++--- provider/data_source_setting.go | 4 ---- 13 files changed, 20 insertions(+), 24 deletions(-) diff --git a/docs/data-sources/database_catalog.md b/docs/data-sources/database_catalog.md index fa4d185..28a29ed 100644 --- a/docs/data-sources/database_catalog.md +++ b/docs/data-sources/database_catalog.md @@ -22,7 +22,7 @@ The database catalog data source. ### Read-Only - `id` (String) The ID of this resource. -- `schemas` (List of Object) (see [below for nested schema](#nestedatt--schemas)) +- `schemas` (Set of Object) (see [below for nested schema](#nestedatt--schemas)) ### Nested Schema for `schemas` @@ -30,7 +30,7 @@ The database catalog data source. Read-Only: - `name` (String) -- `tables` (List of Object) (see [below for nested schema](#nestedobjatt--schemas--tables)) +- `tables` (Set of Object) (see [below for nested schema](#nestedobjatt--schemas--tables)) ### Nested Schema for `schemas.tables` @@ -38,7 +38,7 @@ Read-Only: Read-Only: - `classification` (String) -- `columns` (List of Object) (see [below for nested schema](#nestedobjatt--schemas--tables--columns)) +- `columns` (Set of Object) (see [below for nested schema](#nestedobjatt--schemas--tables--columns)) - `name` (String) diff --git a/docs/data-sources/instance.md b/docs/data-sources/instance.md index 89ae596..e3e515d 100644 --- a/docs/data-sources/instance.md +++ b/docs/data-sources/instance.md @@ -21,7 +21,7 @@ The instance data source. ### Read-Only -- `data_sources` (List of Object) (see [below for nested schema](#nestedatt--data_sources)) +- `data_sources` (Set of Object) (see [below for nested schema](#nestedatt--data_sources)) - `engine` (String) The instance engine. Support MYSQL, POSTGRES, TIDB, SNOWFLAKE, CLICKHOUSE, MONGODB, SQLITE, REDIS, ORACLE, SPANNER, MSSQL, REDSHIFT, MARIADB, OCEANBASE. - `engine_version` (String) The engine version. - `environment` (String) The environment name for your instance in "environments/{resource id}" format. diff --git a/docs/data-sources/policy.md b/docs/data-sources/policy.md index f99bc66..bcd12cb 100644 --- a/docs/data-sources/policy.md +++ b/docs/data-sources/policy.md @@ -36,7 +36,7 @@ The policy data source. Optional: -- `exceptions` (Block List) (see [below for nested schema](#nestedblock--masking_exception_policy--exceptions)) +- `exceptions` (Block Set) (see [below for nested schema](#nestedblock--masking_exception_policy--exceptions)) ### Nested Schema for `masking_exception_policy.exceptions` diff --git a/docs/data-sources/policy_list.md b/docs/data-sources/policy_list.md index 3b3db7c..c709036 100644 --- a/docs/data-sources/policy_list.md +++ b/docs/data-sources/policy_list.md @@ -40,7 +40,7 @@ Read-Only: Read-Only: -- `exceptions` (List of Object) (see [below for nested schema](#nestedobjatt--policies--masking_exception_policy--exceptions)) +- `exceptions` (Set of Object) (see [below for nested schema](#nestedobjatt--policies--masking_exception_policy--exceptions)) ### Nested Schema for `policies.masking_exception_policy.exceptions` diff --git a/docs/data-sources/project.md b/docs/data-sources/project.md index fed2d1c..7a10ec2 100644 --- a/docs/data-sources/project.md +++ b/docs/data-sources/project.md @@ -24,7 +24,7 @@ The project data source. - `allow_modify_statement` (Boolean) Allow modifying statement after issue is created. - `auto_enable_backup` (Boolean) Whether to automatically enable backup. - `auto_resolve_issue` (Boolean) Enable auto resolve issue. -- `databases` (List of Object) The databases in the project. (see [below for nested schema](#nestedatt--databases)) +- `databases` (Set of Object) The databases in the project. (see [below for nested schema](#nestedatt--databases)) - `enforce_issue_title` (Boolean) Enforce issue title created by user instead of generated by Bytebase. - `id` (String) The ID of this resource. - `key` (String) The project key. diff --git a/docs/data-sources/project_list.md b/docs/data-sources/project_list.md index 078d399..b822b2d 100644 --- a/docs/data-sources/project_list.md +++ b/docs/data-sources/project_list.md @@ -32,7 +32,7 @@ Read-Only: - `allow_modify_statement` (Boolean) - `auto_enable_backup` (Boolean) - `auto_resolve_issue` (Boolean) -- `databases` (List of Object) (see [below for nested schema](#nestedobjatt--projects--databases)) +- `databases` (Set of Object) (see [below for nested schema](#nestedobjatt--projects--databases)) - `enforce_issue_title` (Boolean) - `key` (String) - `members` (Set of Object) (see [below for nested schema](#nestedobjatt--projects--members)) diff --git a/docs/data-sources/setting.md b/docs/data-sources/setting.md index 8c7447a..a03bc80 100644 --- a/docs/data-sources/setting.md +++ b/docs/data-sources/setting.md @@ -36,9 +36,9 @@ The setting data source. Optional: - `classification_from_config` (Boolean) If true, we will only store the classification in the config. Otherwise we will get the classification from table/column comment, and write back to the schema metadata. -- `classifications` (Block List) (see [below for nested schema](#nestedblock--classification--classifications)) +- `classifications` (Block Set) (see [below for nested schema](#nestedblock--classification--classifications)) - `id` (String) The classification unique uuid. -- `levels` (Block List) (see [below for nested schema](#nestedblock--classification--levels)) +- `levels` (Block Set) (see [below for nested schema](#nestedblock--classification--levels)) - `title` (String) The classification title. Optional. @@ -126,7 +126,7 @@ Read-Only: Read-Only: -- `nodes` (List of Object) (see [below for nested schema](#nestedatt--external_approval_nodes--nodes)) +- `nodes` (Set of Object) (see [below for nested schema](#nestedatt--external_approval_nodes--nodes)) ### Nested Schema for `external_approval_nodes.nodes` diff --git a/docs/resources/database_catalog.md b/docs/resources/database_catalog.md index 16f8ead..910935f 100644 --- a/docs/resources/database_catalog.md +++ b/docs/resources/database_catalog.md @@ -18,7 +18,7 @@ The database catalog resource. ### Required - `database` (String) The database full name in instances/{instance}/databases/{database} format -- `schemas` (Block List, Min: 1) (see [below for nested schema](#nestedblock--schemas)) +- `schemas` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--schemas)) ### Read-Only @@ -29,7 +29,7 @@ The database catalog resource. Required: -- `tables` (Block List, Min: 1) (see [below for nested schema](#nestedblock--schemas--tables)) +- `tables` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--schemas--tables)) Optional: @@ -40,7 +40,7 @@ Optional: Required: -- `columns` (Block List, Min: 1) (see [below for nested schema](#nestedblock--schemas--tables--columns)) +- `columns` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--schemas--tables--columns)) - `name` (String) Optional: diff --git a/docs/resources/instance.md b/docs/resources/instance.md index a10b62b..c192bdd 100644 --- a/docs/resources/instance.md +++ b/docs/resources/instance.md @@ -17,7 +17,7 @@ The instance resource. ### Required -- `data_sources` (Block List, Min: 1) The connection for the instance. You can configure read-only or admin connection account here. (see [below for nested schema](#nestedblock--data_sources)) +- `data_sources` (Block Set, Min: 1) The connection for the instance. You can configure read-only or admin connection account here. (see [below for nested schema](#nestedblock--data_sources)) - `engine` (String) The instance engine. Support MYSQL, POSTGRES, TIDB, SNOWFLAKE, CLICKHOUSE, MONGODB, SQLITE, REDIS, ORACLE, SPANNER, MSSQL, REDSHIFT, MARIADB, OCEANBASE. - `environment` (String) The environment full name for the instance in environments/{environment id} format. - `resource_id` (String) The instance unique resource id. diff --git a/docs/resources/policy.md b/docs/resources/policy.md index dc743a2..0d1f50d 100644 --- a/docs/resources/policy.md +++ b/docs/resources/policy.md @@ -36,7 +36,7 @@ The policy resource. Optional: -- `exceptions` (Block List) (see [below for nested schema](#nestedblock--masking_exception_policy--exceptions)) +- `exceptions` (Block Set) (see [below for nested schema](#nestedblock--masking_exception_policy--exceptions)) ### Nested Schema for `masking_exception_policy.exceptions` diff --git a/docs/resources/project.md b/docs/resources/project.md index 31d0bae..3689bdb 100644 --- a/docs/resources/project.md +++ b/docs/resources/project.md @@ -26,7 +26,7 @@ The project resource. - `allow_modify_statement` (Boolean) Allow modifying statement after issue is created. - `auto_enable_backup` (Boolean) Whether to automatically enable backup. - `auto_resolve_issue` (Boolean) Enable auto resolve issue. -- `databases` (Block List) The databases in the project. (see [below for nested schema](#nestedblock--databases)) +- `databases` (Block Set) The databases in the project. (see [below for nested schema](#nestedblock--databases)) - `enforce_issue_title` (Boolean) Enforce issue title created by user instead of generated by Bytebase. - `members` (Block Set) The members in the project. (see [below for nested schema](#nestedblock--members)) - `postgres_database_tenant_mode` (Boolean) Whether to enable the database tenant mode for PostgreSQL. If enabled, the issue will be created with the pre-appended "set role " statement. diff --git a/docs/resources/setting.md b/docs/resources/setting.md index 421ad28..96ce8bd 100644 --- a/docs/resources/setting.md +++ b/docs/resources/setting.md @@ -91,9 +91,9 @@ Optional: Optional: - `classification_from_config` (Boolean) If true, we will only store the classification in the config. Otherwise we will get the classification from table/column comment, and write back to the schema metadata. -- `classifications` (Block List) (see [below for nested schema](#nestedblock--classification--classifications)) +- `classifications` (Block Set) (see [below for nested schema](#nestedblock--classification--classifications)) - `id` (String) The classification unique uuid. -- `levels` (Block List) (see [below for nested schema](#nestedblock--classification--levels)) +- `levels` (Block Set) (see [below for nested schema](#nestedblock--classification--levels)) - `title` (String) The classification title. Optional. @@ -123,7 +123,7 @@ Optional: Required: -- `nodes` (Block List, Min: 1) (see [below for nested schema](#nestedblock--external_approval_nodes--nodes)) +- `nodes` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--external_approval_nodes--nodes)) ### Nested Schema for `external_approval_nodes.nodes` diff --git a/provider/data_source_setting.go b/provider/data_source_setting.go index c398b5b..dbcd758 100644 --- a/provider/data_source_setting.go +++ b/provider/data_source_setting.go @@ -193,8 +193,6 @@ func getExternalApprovalSetting(computed bool) *schema.Schema { Optional: true, Default: nil, Type: schema.TypeList, - MaxItems: 1, - MinItems: 1, Description: "Configure external nodes in the approval flow. Require ENTERPRISE subscription.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -237,8 +235,6 @@ func getWorkspaceApprovalSetting(computed bool) *schema.Schema { Optional: true, Default: nil, Type: schema.TypeList, - MaxItems: 1, - MinItems: 1, Description: "Configure risk level and approval flow for different tasks. Require ENTERPRISE subscription.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ From c0edb6cadaec2c5c8e78d937f588c201b2030e63 Mon Sep 17 00:00:00 2001 From: ecmadao Date: Thu, 6 Feb 2025 11:40:04 +0800 Subject: [PATCH 3/7] fix: list all databases in the project --- VERSION | 2 +- api/client.go | 2 +- client/database.go | 37 +++++++++++++++++++++++----- provider/data_source_project.go | 4 +-- provider/data_source_project_list.go | 4 +-- provider/internal/mock_client.go | 6 ++--- provider/resource_project.go | 4 +-- 7 files changed, 41 insertions(+), 18 deletions(-) diff --git a/VERSION b/VERSION index e5a4a5e..437d26b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.9 \ No newline at end of file +1.0.10 \ No newline at end of file diff --git a/api/client.go b/api/client.go index a1e0227..605f04b 100644 --- a/api/client.go +++ b/api/client.go @@ -56,7 +56,7 @@ type Client interface { // GetDatabase gets the database by instance resource id and the database name. GetDatabase(ctx context.Context, databaseName string) (*v1pb.Database, error) // ListDatabase list the databases. - ListDatabase(ctx context.Context, instanceID, filter string) (*v1pb.ListDatabasesResponse, error) + ListDatabase(ctx context.Context, instanceID, filter string) ([]*v1pb.Database, error) // UpdateDatabase patches the database. UpdateDatabase(ctx context.Context, patch *v1pb.Database, updateMasks []string) (*v1pb.Database, error) // BatchUpdateDatabases batch updates databases. diff --git a/client/database.go b/client/database.go index e3e95a4..2ace0aa 100644 --- a/client/database.go +++ b/client/database.go @@ -26,12 +26,37 @@ func (c *client) GetDatabase(ctx context.Context, databaseName string) (*v1pb.Da return &res, nil } -// ListDatabase list the databases. -func (c *client) ListDatabase(ctx context.Context, instanceID, filter string) (*v1pb.ListDatabasesResponse, error) { - requestURL := fmt.Sprintf("%s/%s/instances/%s/databases", c.url, c.version, instanceID) - if filter != "" { - requestURL = fmt.Sprintf("%s?filter=%s", requestURL, url.QueryEscape(filter)) - } +// ListDatabase list all databases. +func (c *client) ListDatabase(ctx context.Context, instanceID, filter string) ([]*v1pb.Database, error) { + res := []*v1pb.Database{} + pageToken := "" + + for true { + resp, err := c.listDatabase(ctx, instanceID, filter, pageToken, 500) + if err != nil { + return nil, err + } + res = append(res, resp.Databases...) + pageToken = resp.NextPageToken + if pageToken == "" { + break + } + } + + return res, nil +} + +// listDatabase list the databases. +func (c *client) listDatabase(ctx context.Context, instanceID, filter, pageToken string, pageSize int) (*v1pb.ListDatabasesResponse, error) { + requestURL := fmt.Sprintf( + "%s/%s/instances/%s/databases?filter=%s&page_size=%d&page_token=%s", + c.url, + c.version, + instanceID, + url.QueryEscape(filter), + pageSize, + pageToken, + ) req, err := http.NewRequestWithContext(ctx, "GET", requestURL, nil) if err != nil { diff --git a/provider/data_source_project.go b/provider/data_source_project.go index db884e9..3892e7a 100644 --- a/provider/data_source_project.go +++ b/provider/data_source_project.go @@ -300,7 +300,7 @@ func setProject( project *v1pb.Project, ) diag.Diagnostics { filter := fmt.Sprintf(`project == "%s"`, project.Name) - listDBResponse, err := client.ListDatabase(ctx, "-", filter) + databases, err := client.ListDatabase(ctx, "-", filter) if err != nil { return diag.FromErr(err) } @@ -353,7 +353,7 @@ func setProject( return diag.Errorf("cannot set postgres_database_tenant_mode for project: %s", err.Error()) } - databaseList := flattenDatabaseList(listDBResponse.Databases) + databaseList := flattenDatabaseList(databases) if err := d.Set("databases", schema.NewSet(databaseHash, databaseList)); err != nil { return diag.Errorf("cannot set databases for project: %s", err.Error()) } diff --git a/provider/data_source_project_list.go b/provider/data_source_project_list.go index fce5bc3..2e86eb1 100644 --- a/provider/data_source_project_list.go +++ b/provider/data_source_project_list.go @@ -125,12 +125,12 @@ func dataSourceProjectListRead(ctx context.Context, d *schema.ResourceData, m in proj["postgres_database_tenant_mode"] = project.PostgresDatabaseTenantMode filter := fmt.Sprintf(`project == "%s"`, project.Name) - response, err := c.ListDatabase(ctx, "-", filter) + databases, err := c.ListDatabase(ctx, "-", filter) if err != nil { return diag.FromErr(err) } - proj["databases"] = flattenDatabaseList(response.Databases) + proj["databases"] = flattenDatabaseList(databases) iamPolicy, err := c.GetProjectIAMPolicy(ctx, project.Name) if err != nil { diff --git a/provider/internal/mock_client.go b/provider/internal/mock_client.go index b54ceaf..005471c 100644 --- a/provider/internal/mock_client.go +++ b/provider/internal/mock_client.go @@ -375,7 +375,7 @@ func (c *mockClient) GetDatabase(_ context.Context, databaseName string) (*v1pb. } // ListDatabase list the databases. -func (c *mockClient) ListDatabase(_ context.Context, instaceID, filter string) (*v1pb.ListDatabasesResponse, error) { +func (c *mockClient) ListDatabase(_ context.Context, instaceID, filter string) ([]*v1pb.Database, error) { projectID := "-" if strings.HasPrefix(filter, "project == ") { projectID = strings.Split(filter, "project == ")[1] @@ -391,9 +391,7 @@ func (c *mockClient) ListDatabase(_ context.Context, instaceID, filter string) ( databases = append(databases, db) } - return &v1pb.ListDatabasesResponse{ - Databases: databases, - }, nil + return databases, nil } // UpdateDatabase patches the database. diff --git a/provider/resource_project.go b/provider/resource_project.go index 2b9f82a..f6a1a8f 100644 --- a/provider/resource_project.go +++ b/provider/resource_project.go @@ -367,12 +367,12 @@ func resourceProjectDelete(ctx context.Context, d *schema.ResourceData, m interf func updateDatabasesInProject(ctx context.Context, d *schema.ResourceData, client api.Client, projectName string) diag.Diagnostics { filter := fmt.Sprintf(`project == "%s"`, projectName) - listDB, err := client.ListDatabase(ctx, "-", filter) + databases, err := client.ListDatabase(ctx, "-", filter) if err != nil { return diag.Errorf("failed to list database with error: %v", err) } existedDBMap := map[string]*v1pb.Database{} - for _, db := range listDB.Databases { + for _, db := range databases { existedDBMap[db.Name] = db } From 404484c6db2054c82f86ae8f404fc16851fc3119 Mon Sep 17 00:00:00 2001 From: ecmadao Date: Thu, 6 Feb 2025 11:50:01 +0800 Subject: [PATCH 4/7] chore: update --- docs/resources/project.md | 2 +- examples/setup/project.tf | 2 +- provider/data_source_project.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/resources/project.md b/docs/resources/project.md index 3689bdb..6264da0 100644 --- a/docs/resources/project.md +++ b/docs/resources/project.md @@ -69,7 +69,7 @@ Optional: Optional: - `database` (String) The accessible database full name in instances/{instance resource id}/databases/{database name} format -- `expire_timestamp` (String) The expiration timestamp in YYYY-MM-DDThh:mm:ss.000Z format +- `expire_timestamp` (String) The expiration timestamp in YYYY-MM-DDThh:mm:ssZ format - `row_limit` (Number) The export row limit for exporter role - `schema` (String) The accessible schema in the database - `tables` (Set of String) The accessible table list diff --git a/examples/setup/project.tf b/examples/setup/project.tf index 287fc99..3eb5bdd 100644 --- a/examples/setup/project.tf +++ b/examples/setup/project.tf @@ -27,7 +27,7 @@ resource "bytebase_project" "sample_project" { database = "instances/test-sample-instance/databases/employee" tables = ["dept_emp", "dept_manager"] row_limit = 10000 - expire_timestamp = "2027-03-09T16:17:49.637Z" + expire_timestamp = "2027-03-09T16:17:49Z" } } } diff --git a/provider/data_source_project.go b/provider/data_source_project.go index 3892e7a..03be430 100644 --- a/provider/data_source_project.go +++ b/provider/data_source_project.go @@ -191,7 +191,7 @@ func getProjectMembersSchema(computed bool) *schema.Schema { Type: schema.TypeString, Computed: computed, Optional: true, - Description: "The expiration timestamp in YYYY-MM-DDThh:mm:ss.000Z format", + Description: "The expiration timestamp in YYYY-MM-DDThh:mm:ssZ format", }, }, }, From 78b4069ffc9d70eddb88072ba6f5cd5b7ad28918 Mon Sep 17 00:00:00 2001 From: ecmadao Date: Thu, 6 Feb 2025 11:53:58 +0800 Subject: [PATCH 5/7] fix: lint --- client/database.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/database.go b/client/database.go index 2ace0aa..e0becc4 100644 --- a/client/database.go +++ b/client/database.go @@ -31,8 +31,8 @@ func (c *client) ListDatabase(ctx context.Context, instanceID, filter string) ([ res := []*v1pb.Database{} pageToken := "" - for true { - resp, err := c.listDatabase(ctx, instanceID, filter, pageToken, 500) + for { + resp, err := c.listDatabasePerPage(ctx, instanceID, filter, pageToken, 500) if err != nil { return nil, err } @@ -46,8 +46,8 @@ func (c *client) ListDatabase(ctx context.Context, instanceID, filter string) ([ return res, nil } -// listDatabase list the databases. -func (c *client) listDatabase(ctx context.Context, instanceID, filter, pageToken string, pageSize int) (*v1pb.ListDatabasesResponse, error) { +// listDatabasePerPage list the databases. +func (c *client) listDatabasePerPage(ctx context.Context, instanceID, filter, pageToken string, pageSize int) (*v1pb.ListDatabasesResponse, error) { requestURL := fmt.Sprintf( "%s/%s/instances/%s/databases?filter=%s&page_size=%d&page_token=%s", c.url, From 19d7ec2dc6b21a68ce32696f2b395e75651dc19a Mon Sep 17 00:00:00 2001 From: ecmadao Date: Thu, 6 Feb 2025 14:55:44 +0800 Subject: [PATCH 6/7] chore: add databases for instance --- docs/data-sources/instance.md | 15 +++++++++++++++ docs/data-sources/instance_list.md | 17 ++++++++++++++++- docs/data-sources/project.md | 3 ++- docs/data-sources/project_list.md | 1 + docs/resources/instance.md | 15 +++++++++++++++ docs/resources/project.md | 3 ++- provider/data_source_instance.go | 3 ++- provider/data_source_instance_list.go | 13 +++++++++++-- provider/data_source_project.go | 12 +++++++++--- provider/data_source_project_list.go | 7 ++++--- provider/resource_instance.go | 19 +++++++++++++++++-- provider/resource_project.go | 2 +- 12 files changed, 95 insertions(+), 15 deletions(-) diff --git a/docs/data-sources/instance.md b/docs/data-sources/instance.md index e3e515d..7247c41 100644 --- a/docs/data-sources/instance.md +++ b/docs/data-sources/instance.md @@ -22,6 +22,7 @@ The instance data source. ### Read-Only - `data_sources` (Set of Object) (see [below for nested schema](#nestedatt--data_sources)) +- `databases` (Set of Object) The databases in the resource. (see [below for nested schema](#nestedatt--databases)) - `engine` (String) The instance engine. Support MYSQL, POSTGRES, TIDB, SNOWFLAKE, CLICKHOUSE, MONGODB, SQLITE, REDIS, ORACLE, SPANNER, MSSQL, REDSHIFT, MARIADB, OCEANBASE. - `engine_version` (String) The engine version. - `environment` (String) The environment name for your instance in "environments/{resource id}" format. @@ -49,3 +50,17 @@ Read-Only: - `username` (String) + +### Nested Schema for `databases` + +Read-Only: + +- `environment` (String) +- `labels` (Map of String) +- `name` (String) +- `project` (String) +- `schema_version` (String) +- `successful_sync_time` (String) +- `sync_state` (String) + + diff --git a/docs/data-sources/instance_list.md b/docs/data-sources/instance_list.md index 44d83e1..9bda9a7 100644 --- a/docs/data-sources/instance_list.md +++ b/docs/data-sources/instance_list.md @@ -29,7 +29,8 @@ The instance data source list. Read-Only: -- `data_sources` (List of Object) (see [below for nested schema](#nestedobjatt--instances--data_sources)) +- `data_sources` (Set of Object) (see [below for nested schema](#nestedobjatt--instances--data_sources)) +- `databases` (Set of Object) (see [below for nested schema](#nestedobjatt--instances--databases)) - `engine` (String) - `engine_version` (String) - `environment` (String) @@ -57,3 +58,17 @@ Read-Only: - `username` (String) + +### Nested Schema for `instances.databases` + +Read-Only: + +- `environment` (String) +- `labels` (Map of String) +- `name` (String) +- `project` (String) +- `schema_version` (String) +- `successful_sync_time` (String) +- `sync_state` (String) + + diff --git a/docs/data-sources/project.md b/docs/data-sources/project.md index 7a10ec2..c7f6972 100644 --- a/docs/data-sources/project.md +++ b/docs/data-sources/project.md @@ -24,7 +24,7 @@ The project data source. - `allow_modify_statement` (Boolean) Allow modifying statement after issue is created. - `auto_enable_backup` (Boolean) Whether to automatically enable backup. - `auto_resolve_issue` (Boolean) Enable auto resolve issue. -- `databases` (Set of Object) The databases in the project. (see [below for nested schema](#nestedatt--databases)) +- `databases` (Set of Object) The databases in the resource. (see [below for nested schema](#nestedatt--databases)) - `enforce_issue_title` (Boolean) Enforce issue title created by user instead of generated by Bytebase. - `id` (String) The ID of this resource. - `key` (String) The project key. @@ -43,6 +43,7 @@ Read-Only: - `environment` (String) - `labels` (Map of String) - `name` (String) +- `project` (String) - `schema_version` (String) - `successful_sync_time` (String) - `sync_state` (String) diff --git a/docs/data-sources/project_list.md b/docs/data-sources/project_list.md index b822b2d..ba0152f 100644 --- a/docs/data-sources/project_list.md +++ b/docs/data-sources/project_list.md @@ -51,6 +51,7 @@ Read-Only: - `environment` (String) - `labels` (Map of String) - `name` (String) +- `project` (String) - `schema_version` (String) - `successful_sync_time` (String) - `sync_state` (String) diff --git a/docs/resources/instance.md b/docs/resources/instance.md index c192bdd..d22f0d9 100644 --- a/docs/resources/instance.md +++ b/docs/resources/instance.md @@ -31,6 +31,7 @@ The instance resource. ### Read-Only +- `databases` (Set of Object) The databases in the resource. (see [below for nested schema](#nestedatt--databases)) - `engine_version` (String) The engine version. - `id` (String) The ID of this resource. - `name` (String) The instance full name in instances/{resource id} format. @@ -55,3 +56,17 @@ Optional: - `username` (String) The connection user name used by Bytebase to perform DDL and DML operations. + +### Nested Schema for `databases` + +Read-Only: + +- `environment` (String) +- `labels` (Map of String) +- `name` (String) +- `project` (String) +- `schema_version` (String) +- `successful_sync_time` (String) +- `sync_state` (String) + + diff --git a/docs/resources/project.md b/docs/resources/project.md index 6264da0..6c90a2d 100644 --- a/docs/resources/project.md +++ b/docs/resources/project.md @@ -26,7 +26,7 @@ The project resource. - `allow_modify_statement` (Boolean) Allow modifying statement after issue is created. - `auto_enable_backup` (Boolean) Whether to automatically enable backup. - `auto_resolve_issue` (Boolean) Enable auto resolve issue. -- `databases` (Block Set) The databases in the project. (see [below for nested schema](#nestedblock--databases)) +- `databases` (Block Set) The databases in the resource. (see [below for nested schema](#nestedblock--databases)) - `enforce_issue_title` (Boolean) Enforce issue title created by user instead of generated by Bytebase. - `members` (Block Set) The members in the project. (see [below for nested schema](#nestedblock--members)) - `postgres_database_tenant_mode` (Boolean) Whether to enable the database tenant mode for PostgreSQL. If enabled, the issue will be created with the pre-appended "set role " statement. @@ -49,6 +49,7 @@ Optional: Read-Only: - `environment` (String) The database environment. +- `project` (String) The project full name for the database. - `schema_version` (String) The version of database schema. - `successful_sync_time` (String) The latest synchronization time. - `sync_state` (String) The existence of a database on latest sync. diff --git a/provider/data_source_instance.go b/provider/data_source_instance.go index 8bbed10..58bbf0a 100644 --- a/provider/data_source_instance.go +++ b/provider/data_source_instance.go @@ -125,6 +125,7 @@ func dataSourceInstance() *schema.Resource { }, Set: dataSourceHash, }, + "databases": getDatabasesSchema(true), }, } } @@ -140,5 +141,5 @@ func dataSourceInstanceRead(ctx context.Context, d *schema.ResourceData, m inter d.SetId(ins.Name) - return setInstanceMessage(d, ins) + return setInstanceMessage(ctx, c, d, ins) } diff --git a/provider/data_source_instance_list.go b/provider/data_source_instance_list.go index 90e3385..24a7977 100644 --- a/provider/data_source_instance_list.go +++ b/provider/data_source_instance_list.go @@ -74,7 +74,7 @@ func dataSourceInstanceList() *schema.Resource { Description: "The maximum number of connections. The default value is 10.", }, "data_sources": { - Type: schema.TypeList, + Type: schema.TypeSet, Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -134,7 +134,9 @@ func dataSourceInstanceList() *schema.Resource { }, }, }, + Set: dataSourceHash, }, + "databases": getDatabasesSchema(true), }, }, }, @@ -178,7 +180,14 @@ func dataSourceInstanceListRead(ctx context.Context, d *schema.ResourceData, m i if err != nil { return diag.FromErr(err) } - ins["data_sources"] = dataSources + ins["data_sources"] = schema.NewSet(dataSourceHash, dataSources) + + databases, err := c.ListDatabase(ctx, instanceID, "") + if err != nil { + return diag.FromErr(err) + } + databaseList := flattenDatabaseList(databases) + ins["databases"] = schema.NewSet(databaseHash, databaseList) instances = append(instances, ins) } diff --git a/provider/data_source_project.go b/provider/data_source_project.go index 03be430..7fb3834 100644 --- a/provider/data_source_project.go +++ b/provider/data_source_project.go @@ -79,18 +79,18 @@ func dataSourceProject() *schema.Resource { Computed: true, Description: "Whether to enable the database tenant mode for PostgreSQL. If enabled, the issue will be created with the pre-appended \"set role \" statement.", }, - "databases": getProjectDatabasesSchema(true), + "databases": getDatabasesSchema(true), "members": getProjectMembersSchema(true), }, } } -func getProjectDatabasesSchema(computed bool) *schema.Schema { +func getDatabasesSchema(computed bool) *schema.Schema { return &schema.Schema{ Type: schema.TypeSet, Computed: computed, Optional: !computed, - Description: "The databases in the project.", + Description: "The databases in the resource.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { @@ -99,6 +99,11 @@ func getProjectDatabasesSchema(computed bool) *schema.Schema { Optional: !computed, Description: "The database full name in instances/{instance id}/databases/{db name} format.", }, + "project": { + Type: schema.TypeString, + Computed: true, + Description: "The project full name for the database.", + }, "environment": { Type: schema.TypeString, Computed: true, @@ -222,6 +227,7 @@ func flattenDatabaseList(databases []*v1pb.Database) []interface{} { for _, database := range databases { db := map[string]interface{}{} db["name"] = database.Name + db["project"] = database.Project db["environment"] = database.Environment db["sync_state"] = database.SyncState.String() db["successful_sync_time"] = database.SuccessfulSyncTime.AsTime().UTC().Format(time.RFC3339) diff --git a/provider/data_source_project_list.go b/provider/data_source_project_list.go index 2e86eb1..6156958 100644 --- a/provider/data_source_project_list.go +++ b/provider/data_source_project_list.go @@ -84,7 +84,7 @@ func dataSourceProjectList() *schema.Resource { Computed: true, Description: "Whether to enable the database tenant mode for PostgreSQL. If enabled, the issue will be created with the pre-appended \"set role \" statement.", }, - "databases": getProjectDatabasesSchema(true), + "databases": getDatabasesSchema(true), "members": getProjectMembersSchema(true), }, }, @@ -130,7 +130,8 @@ func dataSourceProjectListRead(ctx context.Context, d *schema.ResourceData, m in return diag.FromErr(err) } - proj["databases"] = flattenDatabaseList(databases) + databaseList := flattenDatabaseList(databases) + proj["databases"] = schema.NewSet(databaseHash, databaseList) iamPolicy, err := c.GetProjectIAMPolicy(ctx, project.Name) if err != nil { @@ -140,7 +141,7 @@ func dataSourceProjectListRead(ctx context.Context, d *schema.ResourceData, m in if err != nil { return diag.FromErr(err) } - proj["members"] = memberList + proj["members"] = schema.NewSet(memberHash, memberList) projects = append(projects, proj) } diff --git a/provider/resource_instance.go b/provider/resource_instance.go index 484ae60..4e6d3fd 100644 --- a/provider/resource_instance.go +++ b/provider/resource_instance.go @@ -180,6 +180,7 @@ func resourceInstance() *schema.Resource { }, Set: dataSourceHash, }, + "databases": getDatabasesSchema(true), }, } } @@ -325,7 +326,7 @@ func resourceInstanceRead(ctx context.Context, d *schema.ResourceData, m interfa return diag.FromErr(err) } - return setInstanceMessage(d, instance) + return setInstanceMessage(ctx, c, d, instance) } func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { @@ -436,7 +437,12 @@ func resourceInstanceDelete(ctx context.Context, d *schema.ResourceData, m inter return diags } -func setInstanceMessage(d *schema.ResourceData, instance *v1pb.Instance) diag.Diagnostics { +func setInstanceMessage( + ctx context.Context, + client api.Client, + d *schema.ResourceData, + instance *v1pb.Instance, +) diag.Diagnostics { instanceID, err := internal.GetInstanceID(instance.Name) if err != nil { return diag.FromErr(err) @@ -479,6 +485,15 @@ func setInstanceMessage(d *schema.ResourceData, instance *v1pb.Instance) diag.Di return diag.Errorf("cannot set data_sources for instance: %s", err.Error()) } + databases, err := client.ListDatabase(ctx, instanceID, "") + if err != nil { + return diag.FromErr(err) + } + databaseList := flattenDatabaseList(databases) + if err := d.Set("databases", schema.NewSet(databaseHash, databaseList)); err != nil { + return diag.Errorf("cannot set databases for instance: %s", err.Error()) + } + return nil } diff --git a/provider/resource_project.go b/provider/resource_project.go index f6a1a8f..f8e6617 100644 --- a/provider/resource_project.go +++ b/provider/resource_project.go @@ -98,7 +98,7 @@ func resourceProjct() *schema.Resource { Default: false, Description: "Whether to enable the database tenant mode for PostgreSQL. If enabled, the issue will be created with the pre-appended \"set role \" statement.", }, - "databases": getProjectDatabasesSchema(false), + "databases": getDatabasesSchema(false), "members": getProjectMembersSchema(false), }, } From e6ed605d8013465e7c1d05e6467dbbf5ac445ed0 Mon Sep 17 00:00:00 2001 From: ecmadao Date: Fri, 7 Feb 2025 10:41:30 +0800 Subject: [PATCH 7/7] chore: more error log --- VERSION | 2 +- provider/resource_project.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index 437d26b..8684498 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.10 \ No newline at end of file +1.0.11 \ No newline at end of file diff --git a/provider/resource_project.go b/provider/resource_project.go index f8e6617..21b4afc 100644 --- a/provider/resource_project.go +++ b/provider/resource_project.go @@ -369,7 +369,7 @@ func updateDatabasesInProject(ctx context.Context, d *schema.ResourceData, clien filter := fmt.Sprintf(`project == "%s"`, projectName) databases, err := client.ListDatabase(ctx, "-", filter) if err != nil { - return diag.Errorf("failed to list database with error: %v", err) + return diag.Errorf("failed to list database with error: %v", err.Error()) } existedDBMap := map[string]*v1pb.Database{} for _, db := range databases { @@ -414,14 +414,14 @@ func updateDatabasesInProject(ctx context.Context, d *schema.ResourceData, clien Requests: batchTransferDatabases, Parent: "instances/-", }); err != nil { - return diag.Errorf("failed to assign databases to project %s", projectName) + return diag.Errorf("failed to assign databases to project %s with error: %v", projectName, err.Error()) } } for _, database := range updatedDBMap { if len(database.Labels) > 0 { if _, err := client.UpdateDatabase(ctx, database, []string{"label"}); err != nil { - return diag.Errorf("failed to update database %s with error: %v", database.Name, err) + return diag.Errorf("failed to update database %s with error: %v", database.Name, err.Error()) } } } @@ -444,7 +444,7 @@ func updateDatabasesInProject(ctx context.Context, d *schema.ResourceData, clien Requests: unassignDatabases, Parent: "instances/-", }); err != nil { - return diag.Errorf("failed to move databases to default project") + return diag.Errorf("failed to move databases to default project with error: %v", err.Error()) } }