diff --git a/VERSION b/VERSION index ace256e..2fa3901 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.21 \ No newline at end of file +1.0.22 \ No newline at end of file diff --git a/api/client.go b/api/client.go index d5a88bf..17048eb 100644 --- a/api/client.go +++ b/api/client.go @@ -7,6 +7,50 @@ import ( v1alpha1 "google.golang.org/genproto/googleapis/api/expr/v1alpha1" ) +// InstanceFilter is the filter for list instances API. +type InstanceFilter struct { + Query string + Environment string + Project string + State v1pb.State + Engines []v1pb.Engine + Host string + Port string +} + +// ProjectFilter is the filter for list projects API. +type ProjectFilter struct { + Query string + ExcludeDefault bool + State v1pb.State +} + +// Label is the database label. +type Label struct { + Key string + Value string +} + +// DatabaseFilter is the filter for list databases API. +type DatabaseFilter struct { + Query string + Environment string + Project string + Instance string + Engines []v1pb.Engine + Labels []*Label + ExcludeUnassigned bool +} + +// UserFilter is the filter for list users API. +type UserFilter struct { + Name string + Email string + Project string + UserTypes []v1pb.UserType + State v1pb.State +} + // Client is the API message for Bytebase OpenAPI client. type Client interface { // GetCaller returns the API caller. @@ -28,7 +72,7 @@ type Client interface { // Instance // ListInstance will return instances. - ListInstance(ctx context.Context, showDeleted bool) (*v1pb.ListInstancesResponse, error) + ListInstance(ctx context.Context, filter *InstanceFilter) ([]*v1pb.Instance, error) // GetInstance gets the instance by full name. GetInstance(ctx context.Context, instanceName string) (*v1pb.Instance, error) // CreateInstance creates the instance. @@ -56,7 +100,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, listAll bool) ([]*v1pb.Database, error) + ListDatabase(ctx context.Context, instanceID string, filter *DatabaseFilter, listAll bool) ([]*v1pb.Database, error) // UpdateDatabase patches the database. UpdateDatabase(ctx context.Context, patch *v1pb.Database, updateMasks []string) (*v1pb.Database, error) // BatchUpdateDatabases batch updates databases. @@ -70,7 +114,7 @@ type Client interface { // GetProject gets the project by project full name. GetProject(ctx context.Context, projectName string) (*v1pb.Project, error) // ListProject list all projects, - ListProject(ctx context.Context, showDeleted bool) ([]*v1pb.Project, error) + ListProject(ctx context.Context, filter *ProjectFilter) ([]*v1pb.Project, error) // CreateProject creates the project. CreateProject(ctx context.Context, projectID string, project *v1pb.Project) (*v1pb.Project, error) // UpdateProject updates the project. @@ -98,7 +142,7 @@ type Client interface { // User // ListUser list all users. - ListUser(ctx context.Context, showDeleted bool) ([]*v1pb.User, error) + ListUser(ctx context.Context, filter *UserFilter) ([]*v1pb.User, error) // CreateUser creates the user. CreateUser(ctx context.Context, user *v1pb.User) (*v1pb.User, error) // GetUser gets the user by name. diff --git a/client/database.go b/client/database.go index cc8c74c..57720b0 100644 --- a/client/database.go +++ b/client/database.go @@ -11,6 +11,8 @@ import ( v1pb "github.com/bytebase/bytebase/proto/generated-go/v1" "github.com/hashicorp/terraform-plugin-log/tflog" "google.golang.org/protobuf/encoding/protojson" + + "github.com/bytebase/terraform-provider-bytebase/api" ) // GetDatabase gets the database by the database full name. @@ -28,15 +30,57 @@ func (c *client) GetDatabase(ctx context.Context, databaseName string) (*v1pb.Da return &res, nil } +func buildDatabaseQuery(filter *api.DatabaseFilter) string { + params := []string{} + + if v := filter.Query; v != "" { + params = append(params, fmt.Sprintf(`name.matches("%s")`, strings.ToLower(v))) + } + if v := filter.Environment; v != "" { + params = append(params, fmt.Sprintf(`environment == "%s"`, v)) + } + if v := filter.Project; v != "" { + params = append(params, fmt.Sprintf(`project == "%s"`, v)) + } + if v := filter.Instance; v != "" { + params = append(params, fmt.Sprintf(`instance == "%s"`, v)) + } + if filter.ExcludeUnassigned { + params = append(params, "exclude_unassigned == true") + } + if v := filter.Engines; len(v) > 0 { + engines := []string{} + for _, e := range v { + engines = append(engines, fmt.Sprintf(`"%s"`, e.String())) + } + params = append(params, fmt.Sprintf(`engine in [%s]`, strings.Join(engines, ", "))) + } + if v := filter.Labels; len(v) > 0 { + labelMap := map[string][]string{} + for _, label := range v { + if _, ok := labelMap[label.Key]; !ok { + labelMap[label.Key] = []string{} + } + labelMap[label.Key] = append(labelMap[label.Key], label.Value) + } + for key, values := range labelMap { + params = append(params, fmt.Sprintf(`label == "%s:%s"`, key, strings.Join(values, ","))) + } + } + + return fmt.Sprintf("filter=%s", url.QueryEscape(strings.Join(params, " && "))) +} + // ListDatabase list all databases. -func (c *client) ListDatabase(ctx context.Context, parent, filter string, listAll bool) ([]*v1pb.Database, error) { +func (c *client) ListDatabase(ctx context.Context, parent string, filter *api.DatabaseFilter, listAll bool) ([]*v1pb.Database, error) { res := []*v1pb.Database{} pageToken := "" startTime := time.Now() + query := buildDatabaseQuery(filter) for { startTimePerPage := time.Now() - resp, err := c.listDatabasePerPage(ctx, parent, filter, pageToken, 500) + resp, err := c.listDatabasePerPage(ctx, parent, query, pageToken, 500) if err != nil { return nil, err } @@ -63,11 +107,11 @@ func (c *client) ListDatabase(ctx context.Context, parent, filter string, listAl // listDatabasePerPage list the databases. func (c *client) listDatabasePerPage(ctx context.Context, parent, filter, pageToken string, pageSize int) (*v1pb.ListDatabasesResponse, error) { requestURL := fmt.Sprintf( - "%s/%s/%s/databases?filter=%s&page_size=%d&page_token=%s", + "%s/%s/%s/databases?%s&page_size=%d&page_token=%s", c.url, c.version, parent, - url.QueryEscape(filter), + filter, pageSize, url.QueryEscape(pageToken), ) diff --git a/client/instance.go b/client/instance.go index 91e4904..013cf6a 100644 --- a/client/instance.go +++ b/client/instance.go @@ -4,15 +4,99 @@ import ( "context" "fmt" "net/http" + "net/url" "strings" + "time" v1pb "github.com/bytebase/bytebase/proto/generated-go/v1" + "github.com/hashicorp/terraform-plugin-log/tflog" "google.golang.org/protobuf/encoding/protojson" + + "github.com/bytebase/terraform-provider-bytebase/api" ) +func buildInstanceQuery(filter *api.InstanceFilter) string { + params := []string{} + showDeleted := v1pb.State_DELETED == filter.State + + if v := filter.Query; v != "" { + params = append(params, fmt.Sprintf(`(name.matches("%s") || resource_id.matches("%s"))`, strings.ToLower(v), strings.ToLower(v))) + } + if v := filter.Project; v != "" { + params = append(params, fmt.Sprintf(`project == "%s"`, v)) + } + if v := filter.Environment; v != "" { + params = append(params, fmt.Sprintf(`environment == "%s"`, v)) + } + if v := filter.Host; v != "" { + params = append(params, fmt.Sprintf(`host == "%s"`, v)) + } + if v := filter.Port; v != "" { + params = append(params, fmt.Sprintf(`port == "%s"`, v)) + } + if v := filter.Engines; len(v) > 0 { + engines := []string{} + for _, e := range v { + engines = append(engines, fmt.Sprintf(`"%s"`, e.String())) + } + params = append(params, fmt.Sprintf(`engine in [%s]`, strings.Join(engines, ", "))) + } + if showDeleted { + params = append(params, fmt.Sprintf(`state == "%s"`, filter.State.String())) + } + + if len(params) == 0 { + return fmt.Sprintf("showDeleted=%v", showDeleted) + } + + return fmt.Sprintf("filter=%s&showDeleted=%v", url.QueryEscape(strings.Join(params, " && ")), showDeleted) +} + // ListInstance will return instances. -func (c *client) ListInstance(ctx context.Context, showDeleted bool) (*v1pb.ListInstancesResponse, error) { - req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/instances?showDeleted=%v", c.url, c.version, showDeleted), nil) +func (c *client) ListInstance(ctx context.Context, filter *api.InstanceFilter) ([]*v1pb.Instance, error) { + res := []*v1pb.Instance{} + pageToken := "" + startTime := time.Now() + query := buildInstanceQuery(filter) + + for { + startTimePerPage := time.Now() + resp, err := c.listInstancePerPage(ctx, query, pageToken, 500) + if err != nil { + return nil, err + } + res = append(res, resp.Instances...) + tflog.Debug(ctx, "[list instance per page]", map[string]interface{}{ + "count": len(resp.Instances), + "ms": time.Since(startTimePerPage).Milliseconds(), + }) + + pageToken = resp.NextPageToken + if pageToken == "" { + break + } + } + + tflog.Debug(ctx, "[list instance]", map[string]interface{}{ + "total": len(res), + "ms": time.Since(startTime).Milliseconds(), + }) + + return res, nil +} + +// listInstancePerPage list the instance. +func (c *client) listInstancePerPage(ctx context.Context, query, pageToken string, pageSize int) (*v1pb.ListInstancesResponse, error) { + requestURL := fmt.Sprintf( + "%s/%s/instances?%s&page_size=%d&page_token=%s", + c.url, + c.version, + query, + pageSize, + url.QueryEscape(pageToken), + ) + + req, err := http.NewRequestWithContext(ctx, "GET", requestURL, nil) if err != nil { return nil, err } diff --git a/client/project.go b/client/project.go index eaf2b3e..766e0cf 100644 --- a/client/project.go +++ b/client/project.go @@ -11,6 +11,8 @@ import ( v1pb "github.com/bytebase/bytebase/proto/generated-go/v1" "github.com/hashicorp/terraform-plugin-log/tflog" "google.golang.org/protobuf/encoding/protojson" + + "github.com/bytebase/terraform-provider-bytebase/api" ) // GetProject gets the project by project full name. @@ -69,15 +71,37 @@ func (c *client) SetProjectIAMPolicy(ctx context.Context, projectName string, up return &res, nil } +func buildProjectQuery(filter *api.ProjectFilter) string { + params := []string{} + showDeleted := v1pb.State_DELETED == filter.State + + if v := filter.Query; v != "" { + params = append(params, fmt.Sprintf(`(name.matches("%s") || resource_id.matches("%s"))`, strings.ToLower(v), strings.ToLower(v))) + } + if filter.ExcludeDefault { + params = append(params, "exclude_default == true") + } + if showDeleted { + params = append(params, fmt.Sprintf(`state == "%s"`, filter.State.String())) + } + + if len(params) == 0 { + return fmt.Sprintf("showDeleted=%v", showDeleted) + } + + return fmt.Sprintf("filter=%s&showDeleted=%v", url.QueryEscape(strings.Join(params, " && ")), showDeleted) +} + // ListProject list all projects. -func (c *client) ListProject(ctx context.Context, showDeleted bool) ([]*v1pb.Project, error) { +func (c *client) ListProject(ctx context.Context, filter *api.ProjectFilter) ([]*v1pb.Project, error) { res := []*v1pb.Project{} pageToken := "" startTime := time.Now() + query := buildProjectQuery(filter) for { startTimePerPage := time.Now() - resp, err := c.listProjectPerPage(ctx, showDeleted, pageToken, 500) + resp, err := c.listProjectPerPage(ctx, query, pageToken, 500) if err != nil { return nil, err } @@ -102,12 +126,12 @@ func (c *client) ListProject(ctx context.Context, showDeleted bool) ([]*v1pb.Pro } // listProjectPerPage list the projects. -func (c *client) listProjectPerPage(ctx context.Context, showDeleted bool, pageToken string, pageSize int) (*v1pb.ListProjectsResponse, error) { +func (c *client) listProjectPerPage(ctx context.Context, query, pageToken string, pageSize int) (*v1pb.ListProjectsResponse, error) { requestURL := fmt.Sprintf( - "%s/%s/projects?showDeleted=%v&page_size=%d&page_token=%s", + "%s/%s/projects?%s&page_size=%d&page_token=%s", c.url, c.version, - showDeleted, + query, pageSize, url.QueryEscape(pageToken), ) diff --git a/client/user.go b/client/user.go index acd3f76..3dfd61d 100644 --- a/client/user.go +++ b/client/user.go @@ -11,17 +11,51 @@ import ( v1pb "github.com/bytebase/bytebase/proto/generated-go/v1" "github.com/hashicorp/terraform-plugin-log/tflog" "google.golang.org/protobuf/encoding/protojson" + + "github.com/bytebase/terraform-provider-bytebase/api" ) +func buildUserQuery(filter *api.UserFilter) string { + params := []string{} + showDeleted := v1pb.State_DELETED == filter.State + + if v := filter.Name; v != "" { + params = append(params, fmt.Sprintf(`name == "%s"`, strings.ToLower(v))) + } + if v := filter.Email; v != "" { + params = append(params, fmt.Sprintf(`email == "%s"`, strings.ToLower(v))) + } + if v := filter.Project; v != "" { + params = append(params, fmt.Sprintf(`project == "%s"`, v)) + } + if v := filter.UserTypes; len(v) > 0 { + userTypes := []string{} + for _, t := range v { + userTypes = append(userTypes, fmt.Sprintf(`"%s"`, t.String())) + } + params = append(params, fmt.Sprintf(`user_type in [%s]`, strings.Join(userTypes, ", "))) + } + if showDeleted { + params = append(params, fmt.Sprintf(`state == "%s"`, filter.State.String())) + } + + if len(params) == 0 { + return fmt.Sprintf("showDeleted=%v", showDeleted) + } + + return fmt.Sprintf("filter=%s&showDeleted=%v", url.QueryEscape(strings.Join(params, " && ")), showDeleted) +} + // ListUser list all users. -func (c *client) ListUser(ctx context.Context, showDeleted bool) ([]*v1pb.User, error) { +func (c *client) ListUser(ctx context.Context, filter *api.UserFilter) ([]*v1pb.User, error) { res := []*v1pb.User{} pageToken := "" startTime := time.Now() + query := buildUserQuery(filter) for { startTimePerPage := time.Now() - resp, err := c.listUserPerPage(ctx, showDeleted, pageToken, 500) + resp, err := c.listUserPerPage(ctx, query, pageToken, 500) if err != nil { return nil, err } @@ -46,12 +80,12 @@ func (c *client) ListUser(ctx context.Context, showDeleted bool) ([]*v1pb.User, } // listUserPerPage list the users. -func (c *client) listUserPerPage(ctx context.Context, showDeleted bool, pageToken string, pageSize int) (*v1pb.ListUsersResponse, error) { +func (c *client) listUserPerPage(ctx context.Context, query, pageToken string, pageSize int) (*v1pb.ListUsersResponse, error) { requestURL := fmt.Sprintf( - "%s/%s/users?showDeleted=%v&page_size=%d&page_token=%s", + "%s/%s/users?%s&page_size=%d&page_token=%s", c.url, c.version, - showDeleted, + query, pageSize, url.QueryEscape(pageToken), ) diff --git a/docs/data-sources/database_list.md b/docs/data-sources/database_list.md index 6ffe69b..1b302e0 100644 --- a/docs/data-sources/database_list.md +++ b/docs/data-sources/database_list.md @@ -19,6 +19,16 @@ The database data source list. - `parent` (String) +### Optional + +- `engines` (Set of String) Filter databases by engines. +- `environment` (String) The environment full name. Filter databases by environment. +- `exclude_unassigned` (Boolean) If not include unassigned databases in the response. +- `instance` (String) The instance full name. Filter databases by instance. +- `labels` (Map of String) Filter databases by labels +- `project` (String) The project full name. Filter databases by project. +- `query` (String) Filter databases by name with wildcard + ### Read-Only - `databases` (List of Object) (see [below for nested schema](#nestedatt--databases)) diff --git a/docs/data-sources/instance_list.md b/docs/data-sources/instance_list.md index b5e5975..6abfd6a 100644 --- a/docs/data-sources/instance_list.md +++ b/docs/data-sources/instance_list.md @@ -17,7 +17,13 @@ The instance data source list. ### Optional -- `show_deleted` (Boolean) Including removed instance in the response. +- `engines` (Set of String) Filter instances by engines. +- `environment` (String) The environment full name. Filter instances by environment. +- `host` (String) Filter instances by host. +- `port` (String) Filter instances by port. +- `project` (String) The project full name. Filter instances by project. +- `query` (String) Filter instances by name or resource id with wildcard +- `state` (String) Filter instances by state. Default ACTIVE. ### Read-Only diff --git a/docs/data-sources/project_list.md b/docs/data-sources/project_list.md index e1c9580..965c24c 100644 --- a/docs/data-sources/project_list.md +++ b/docs/data-sources/project_list.md @@ -17,7 +17,9 @@ The project data source list. ### Optional -- `show_deleted` (Boolean) Including removed project in the response. +- `exclude_default` (Boolean) If not include the default project in the response. +- `query` (String) Filter projects by name or resource id with wildcard. +- `state` (String) Filter projects by state. Default ACTIVE. ### Read-Only diff --git a/docs/data-sources/user_list.md b/docs/data-sources/user_list.md index 5c54403..2d9ba4d 100644 --- a/docs/data-sources/user_list.md +++ b/docs/data-sources/user_list.md @@ -17,7 +17,11 @@ The user data source list. ### Optional -- `show_deleted` (Boolean) Including removed users in the response. +- `email` (String) Filter users by email with wildcard +- `name` (String) Filter users by name with wildcard +- `project` (String) The project full name. Filter users by project. +- `state` (String) Filter users by state. Default ACTIVE. +- `user_types` (Set of String) Filter users by types. ### Read-Only diff --git a/examples/database/main.tf b/examples/database/main.tf index 22fbc6b..d100e82 100644 --- a/examples/database/main.tf +++ b/examples/database/main.tf @@ -1,8 +1,8 @@ -# Examples for query the databases +# Examples for query the database terraform { required_providers { bytebase = { - version = "1.0.8" + version = "1.0.22" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } @@ -18,7 +18,15 @@ provider "bytebase" { url = "https://bytebase.example.com" } +data "bytebase_database_list" "all" { + parent = "workspaces/-" + environment = "environments/test" + project = "projects/sample-project" +} +output "all_databases" { + value = data.bytebase_database_list.all +} data "bytebase_database_catalog" "employee" { database = "instances/test-sample-instance/databases/employee" diff --git a/examples/environments/main.tf b/examples/environments/main.tf index 543a8aa..62de4e5 100644 --- a/examples/environments/main.tf +++ b/examples/environments/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { bytebase = { - version = "1.0.21" + version = "1.0.22" # 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 02c63f3..4d9014f 100644 --- a/examples/groups/main.tf +++ b/examples/groups/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.21" + version = "1.0.22" # 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 32c980c..3793fe9 100644 --- a/examples/instances/main.tf +++ b/examples/instances/main.tf @@ -1,8 +1,8 @@ -# Examples for query the instances +# Examples for query the instance terraform { required_providers { bytebase = { - version = "1.0.21" + version = "1.0.22" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } @@ -24,7 +24,12 @@ locals { } # List all instances in all environments -data "bytebase_instance_list" "all" {} +data "bytebase_instance_list" "all" { + environment = "environments/test" + engines = [ + "MYSQL" + ] +} output "all_instances" { value = data.bytebase_instance_list.all diff --git a/examples/policies/main.tf b/examples/policies/main.tf index b4cd985..b5b09dd 100644 --- a/examples/policies/main.tf +++ b/examples/policies/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.21" + version = "1.0.22" # 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 a43388e..eafb10e 100644 --- a/examples/projects/main.tf +++ b/examples/projects/main.tf @@ -1,8 +1,8 @@ -# Examples for query the projects +# Examples for query the project terraform { required_providers { bytebase = { - version = "1.0.21" + version = "1.0.22" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } @@ -23,7 +23,10 @@ locals { } # List all projects -data "bytebase_project_list" "all" {} +data "bytebase_project_list" "all" { + query = "sample" + exclude_default = true +} output "all_projects" { value = data.bytebase_project_list.all diff --git a/examples/roles/main.tf b/examples/roles/main.tf index e215ec1..69fa358 100644 --- a/examples/roles/main.tf +++ b/examples/roles/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.21" + version = "1.0.22" # 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 e78326e..94c3fa4 100644 --- a/examples/settings/main.tf +++ b/examples/settings/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.21" + version = "1.0.22" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/setup/data_masking.tf b/examples/setup/data_masking.tf index 614e028..7aead21 100644 --- a/examples/setup/data_masking.tf +++ b/examples/setup/data_masking.tf @@ -82,6 +82,19 @@ resource "bytebase_setting" "semantic_types" { } } } + + semantic_types { + id = "9c84e2a6-02e5-4031-89c5-13342b568f8b" + title = "Inner Outer mask" + algorithm { + inner_outer_mask { + prefix_len = 1 + suffix_len = 1 + substitution = "*" + type = "INNER" + } + } + } } resource "bytebase_policy" "masking_exception_policy" { diff --git a/examples/setup/main.tf b/examples/setup/main.tf index 84d503a..079e548 100644 --- a/examples/setup/main.tf +++ b/examples/setup/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.21" + version = "1.0.22" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } diff --git a/examples/sql_review/main.tf b/examples/sql_review/main.tf index 5579905..93d5512 100644 --- a/examples/sql_review/main.tf +++ b/examples/sql_review/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { bytebase = { - version = "1.0.21" + version = "1.0.22" # 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 c92021f..8d219c3 100644 --- a/examples/users/main.tf +++ b/examples/users/main.tf @@ -1,7 +1,8 @@ +# Examples for query the user terraform { required_providers { bytebase = { - version = "1.0.21" + version = "1.0.22" # For local development, please use "terraform.local/bytebase/bytebase" instead source = "registry.terraform.io/bytebase/bytebase" } @@ -18,7 +19,9 @@ provider "bytebase" { } # List all users -data "bytebase_user_list" "all" {} +data "bytebase_user_list" "all" { + name = "ed" +} output "all_users" { value = data.bytebase_user_list.all diff --git a/provider/data_source_database_list.go b/provider/data_source_database_list.go index 2f4023a..ef86ad8 100644 --- a/provider/data_source_database_list.go +++ b/provider/data_source_database_list.go @@ -10,6 +10,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + v1pb "github.com/bytebase/bytebase/proto/generated-go/v1" + "github.com/bytebase/terraform-provider-bytebase/api" "github.com/bytebase/terraform-provider-bytebase/provider/internal" ) @@ -23,12 +25,54 @@ func dataSourceDatabaseList() *schema.Resource { Type: schema.TypeString, Required: true, ValidateDiagFunc: internal.ResourceNameValidation( - // instance policy + regexp.MustCompile("^workspaces/-$"), regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.InstanceNamePrefix, internal.ResourceIDPattern)), - // project policy regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.ProjectNamePrefix, internal.ResourceIDPattern)), ), }, + "query": { + Type: schema.TypeString, + Optional: true, + Description: "Filter databases by name with wildcard", + }, + "exclude_unassigned": { + Type: schema.TypeBool, + Optional: true, + Description: "If not include unassigned databases in the response.", + }, + "environment": { + Type: schema.TypeString, + Optional: true, + Description: "The environment full name. Filter databases by environment.", + ValidateDiagFunc: internal.ResourceNameValidation(regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.EnvironmentNamePrefix, internal.ResourceIDPattern))), + }, + "project": { + Type: schema.TypeString, + Optional: true, + Description: "The project full name. Filter databases by project.", + ValidateDiagFunc: internal.ResourceNameValidation(regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.ProjectNamePrefix, internal.ResourceIDPattern))), + }, + "instance": { + Type: schema.TypeString, + Optional: true, + Description: "The instance full name. Filter databases by instance.", + ValidateDiagFunc: internal.ResourceNameValidation(regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.InstanceNamePrefix, internal.ResourceIDPattern))), + }, + "engines": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: internal.EngineValidation, + }, + Description: "Filter databases by engines.", + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "Filter databases by labels", + }, "databases": { Type: schema.TypeList, Computed: true, @@ -81,7 +125,30 @@ func dataSourceDatabaseListRead(ctx context.Context, d *schema.ResourceData, m i client := m.(api.Client) parent := d.Get("parent").(string) - databases, err := client.ListDatabase(ctx, parent, "", true) + filter := &api.DatabaseFilter{ + Query: d.Get("query").(string), + Environment: d.Get("environment").(string), + Project: d.Get("project").(string), + Instance: d.Get("instance").(string), + ExcludeUnassigned: d.Get("exclude_unassigned").(bool), + } + + engines := d.Get("engines").(*schema.Set) + for _, engine := range engines.List() { + engineString := engine.(string) + engineValue, ok := v1pb.Engine_value[engineString] + if ok { + filter.Engines = append(filter.Engines, v1pb.Engine(engineValue)) + } + } + for key, val := range d.Get("labels").(map[string]interface{}) { + filter.Labels = append(filter.Labels, &api.Label{ + Key: key, + Value: val.(string), + }) + } + + databases, err := client.ListDatabase(ctx, parent, filter, true) if err != nil { return diag.FromErr(err) } diff --git a/provider/data_source_instance_list.go b/provider/data_source_instance_list.go index 113408a..5958728 100644 --- a/provider/data_source_instance_list.go +++ b/provider/data_source_instance_list.go @@ -2,11 +2,16 @@ package provider import ( "context" + "fmt" + "regexp" "strconv" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + v1pb "github.com/bytebase/bytebase/proto/generated-go/v1" "github.com/bytebase/terraform-provider-bytebase/api" "github.com/bytebase/terraform-provider-bytebase/provider/internal" @@ -17,11 +22,51 @@ func dataSourceInstanceList() *schema.Resource { Description: "The instance data source list.", ReadWithoutTimeout: dataSourceInstanceListRead, Schema: map[string]*schema.Schema{ - "show_deleted": { - Type: schema.TypeBool, + "query": { + Type: schema.TypeString, + Optional: true, + Description: "Filter instances by name or resource id with wildcard", + }, + "environment": { + Type: schema.TypeString, + Optional: true, + Description: "The environment full name. Filter instances by environment.", + ValidateDiagFunc: internal.ResourceNameValidation(regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.EnvironmentNamePrefix, internal.ResourceIDPattern))), + }, + "project": { + Type: schema.TypeString, + Optional: true, + Description: "The project full name. Filter instances by project.", + ValidateDiagFunc: internal.ResourceNameValidation(regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.ProjectNamePrefix, internal.ResourceIDPattern))), + }, + "host": { + Type: schema.TypeString, + Optional: true, + Description: "Filter instances by host.", + }, + "port": { + Type: schema.TypeString, Optional: true, - Default: false, - Description: "Including removed instance in the response.", + Description: "Filter instances by port.", + }, + "state": { + Type: schema.TypeString, + Optional: true, + Default: v1pb.State_ACTIVE.String(), + ValidateFunc: validation.StringInSlice([]string{ + v1pb.State_ACTIVE.String(), + v1pb.State_DELETED.String(), + }, false), + Description: "Filter instances by state. Default ACTIVE.", + }, + "engines": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: internal.EngineValidation, + }, + Description: "Filter instances by engines.", }, "instances": { Type: schema.TypeList, @@ -155,13 +200,35 @@ func dataSourceInstanceListRead(ctx context.Context, d *schema.ResourceData, m i // Warning or errors can be collected in a slice type var diags diag.Diagnostics - response, err := c.ListInstance(ctx, d.Get("show_deleted").(bool)) + filter := &api.InstanceFilter{ + Query: d.Get("query").(string), + Environment: d.Get("environment").(string), + Project: d.Get("project").(string), + Host: d.Get("host").(string), + Port: d.Get("port").(string), + } + stateString := d.Get("state").(string) + stateValue, ok := v1pb.State_value[stateString] + if ok { + filter.State = v1pb.State(stateValue) + } + + engines := d.Get("engines").(*schema.Set) + for _, engine := range engines.List() { + engineString := engine.(string) + engineValue, ok := v1pb.Engine_value[engineString] + if ok { + filter.Engines = append(filter.Engines, v1pb.Engine(engineValue)) + } + } + + response, err := c.ListInstance(ctx, filter) if err != nil { return diag.FromErr(err) } instances := make([]map[string]interface{}, 0) - for _, instance := range response.Instances { + for _, instance := range response { instanceID, err := internal.GetInstanceID(instance.Name) if err != nil { return diag.FromErr(err) diff --git a/provider/data_source_project.go b/provider/data_source_project.go index 1969c3d..fa2b710 100644 --- a/provider/data_source_project.go +++ b/provider/data_source_project.go @@ -252,7 +252,7 @@ func setProject( "project": project.Name, }) - databases, err := client.ListDatabase(ctx, project.Name, "", true) + databases, err := client.ListDatabase(ctx, project.Name, &api.DatabaseFilter{}, true) if err != nil { return diag.FromErr(err) } diff --git a/provider/data_source_project_list.go b/provider/data_source_project_list.go index 9230433..050e694 100644 --- a/provider/data_source_project_list.go +++ b/provider/data_source_project_list.go @@ -7,6 +7,9 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + v1pb "github.com/bytebase/bytebase/proto/generated-go/v1" "github.com/bytebase/terraform-provider-bytebase/api" "github.com/bytebase/terraform-provider-bytebase/provider/internal" @@ -17,11 +20,25 @@ func dataSourceProjectList() *schema.Resource { Description: "The project data source list.", ReadWithoutTimeout: dataSourceProjectListRead, Schema: map[string]*schema.Schema{ - "show_deleted": { + "query": { + Type: schema.TypeString, + Optional: true, + Description: "Filter projects by name or resource id with wildcard.", + }, + "exclude_default": { Type: schema.TypeBool, Optional: true, - Default: false, - Description: "Including removed project in the response.", + Description: "If not include the default project in the response.", + }, + "state": { + Type: schema.TypeString, + Optional: true, + Default: v1pb.State_ACTIVE.String(), + ValidateFunc: validation.StringInSlice([]string{ + v1pb.State_ACTIVE.String(), + v1pb.State_DELETED.String(), + }, false), + Description: "Filter projects by state. Default ACTIVE.", }, "projects": { Type: schema.TypeList, @@ -88,7 +105,17 @@ func dataSourceProjectListRead(ctx context.Context, d *schema.ResourceData, m in // Warning or errors can be collected in a slice type var diags diag.Diagnostics - allProjects, err := c.ListProject(ctx, d.Get("show_deleted").(bool)) + filter := &api.ProjectFilter{ + Query: d.Get("query").(string), + ExcludeDefault: d.Get("exclude_default").(bool), + } + stateString := d.Get("state").(string) + stateValue, ok := v1pb.State_value[stateString] + if ok { + filter.State = v1pb.State(stateValue) + } + + allProjects, err := c.ListProject(ctx, filter) if err != nil { return diag.FromErr(err) } @@ -111,7 +138,7 @@ func dataSourceProjectListRead(ctx context.Context, d *schema.ResourceData, m in proj["skip_backup_errors"] = project.AllowModifyStatement proj["postgres_database_tenant_mode"] = project.PostgresDatabaseTenantMode - databases, err := c.ListDatabase(ctx, project.Name, "", false) + databases, err := c.ListDatabase(ctx, project.Name, &api.DatabaseFilter{}, false) if err != nil { return diag.FromErr(err) } diff --git a/provider/data_source_user_list.go b/provider/data_source_user_list.go index 34cb71f..f928233 100644 --- a/provider/data_source_user_list.go +++ b/provider/data_source_user_list.go @@ -3,13 +3,18 @@ package provider import ( "context" "fmt" + "regexp" "strconv" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + v1pb "github.com/bytebase/bytebase/proto/generated-go/v1" "github.com/bytebase/terraform-provider-bytebase/api" + "github.com/bytebase/terraform-provider-bytebase/provider/internal" ) func dataSourceUserList() *schema.Resource { @@ -17,11 +22,44 @@ func dataSourceUserList() *schema.Resource { Description: "The user data source list.", ReadContext: dataSourceUserListRead, Schema: map[string]*schema.Schema{ - "show_deleted": { - Type: schema.TypeBool, + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Filter users by name with wildcard", + }, + "email": { + Type: schema.TypeString, Optional: true, - Default: false, - Description: "Including removed users in the response.", + Description: "Filter users by email with wildcard", + }, + "project": { + Type: schema.TypeString, + Optional: true, + Description: "The project full name. Filter users by project.", + ValidateDiagFunc: internal.ResourceNameValidation(regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.ProjectNamePrefix, internal.ResourceIDPattern))), + }, + "state": { + Type: schema.TypeString, + Optional: true, + Default: v1pb.State_ACTIVE.String(), + ValidateFunc: validation.StringInSlice([]string{ + v1pb.State_ACTIVE.String(), + v1pb.State_DELETED.String(), + }, false), + Description: "Filter users by state. Default ACTIVE.", + }, + "user_types": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + v1pb.UserType_USER.String(), + v1pb.UserType_SERVICE_ACCOUNT.String(), + v1pb.UserType_SYSTEM_BOT.String(), + }, false), + }, + Description: "Filter users by types.", }, "users": { Type: schema.TypeList, @@ -96,7 +134,27 @@ func dataSourceUserList() *schema.Resource { func dataSourceUserListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(api.Client) - allUsers, err := c.ListUser(ctx, d.Get("show_deleted").(bool)) + filter := &api.UserFilter{ + Name: d.Get("name").(string), + Email: d.Get("email").(string), + Project: d.Get("project").(string), + } + stateString := d.Get("state").(string) + stateValue, ok := v1pb.State_value[stateString] + if ok { + filter.State = v1pb.State(stateValue) + } + + userTypes := d.Get("user_types").(*schema.Set) + for _, userType := range userTypes.List() { + userTypeString := userType.(string) + userTypeValue, ok := v1pb.UserType_value[userTypeString] + if ok { + filter.UserTypes = append(filter.UserTypes, v1pb.UserType(userTypeValue)) + } + } + + allUsers, err := c.ListUser(ctx, filter) if err != nil { return diag.FromErr(err) } diff --git a/provider/internal/mock_client.go b/provider/internal/mock_client.go index 534a8ef..8b8a277 100644 --- a/provider/internal/mock_client.go +++ b/provider/internal/mock_client.go @@ -172,18 +172,16 @@ func (c *mockClient) UndeleteEnvironment(ctx context.Context, environmentName st } // ListInstance will return instances in environment. -func (c *mockClient) ListInstance(_ context.Context, showDeleted bool) (*v1pb.ListInstancesResponse, error) { +func (c *mockClient) ListInstance(_ context.Context, filter *api.InstanceFilter) ([]*v1pb.Instance, error) { instances := make([]*v1pb.Instance, 0) for _, ins := range c.instanceMap { - if ins.State == v1pb.State_DELETED && !showDeleted { + if ins.State == v1pb.State_DELETED && filter.State != v1pb.State_DELETED { continue } instances = append(instances, ins) } - return &v1pb.ListInstancesResponse{ - Instances: instances, - }, nil + return instances, nil } // GetInstance gets the instance by id. @@ -381,10 +379,10 @@ func (c *mockClient) GetDatabase(_ context.Context, databaseName string) (*v1pb. } // ListDatabase list the databases. -func (c *mockClient) ListDatabase(_ context.Context, instaceID, filter string, _ bool) ([]*v1pb.Database, error) { +func (c *mockClient) ListDatabase(_ context.Context, instaceID string, filter *api.DatabaseFilter, _ bool) ([]*v1pb.Database, error) { projectID := "-" - if strings.HasPrefix(filter, "project == ") { - projectID = strings.Split(filter, "project == ")[1] + if filter.Project != "" { + projectID = filter.Project } databases := make([]*v1pb.Database, 0) for _, db := range c.databaseMap { @@ -459,10 +457,10 @@ func (c *mockClient) GetProject(_ context.Context, projectName string) (*v1pb.Pr } // ListProject list the projects. -func (c *mockClient) ListProject(_ context.Context, showDeleted bool) ([]*v1pb.Project, error) { +func (c *mockClient) ListProject(_ context.Context, filter *api.ProjectFilter) ([]*v1pb.Project, error) { projects := make([]*v1pb.Project, 0) for _, proj := range c.projectMap { - if proj.State == v1pb.State_DELETED && !showDeleted { + if proj.State == v1pb.State_DELETED && filter.State != v1pb.State_DELETED { continue } projects = append(projects, proj) @@ -579,10 +577,10 @@ func (*mockClient) ParseExpression(_ context.Context, _ string) (*v1alpha1.Expr, } // ListUser list all users. -func (c *mockClient) ListUser(_ context.Context, showDeleted bool) ([]*v1pb.User, error) { +func (c *mockClient) ListUser(_ context.Context, filter *api.UserFilter) ([]*v1pb.User, error) { users := make([]*v1pb.User, 0) for _, user := range c.userMap { - if user.State == v1pb.State_DELETED && !showDeleted { + if user.State == v1pb.State_DELETED && filter.State != v1pb.State_DELETED { continue } users = append(users, user) diff --git a/provider/resource_instance.go b/provider/resource_instance.go index 99958bc..07ef8bd 100644 --- a/provider/resource_instance.go +++ b/provider/resource_instance.go @@ -495,7 +495,7 @@ func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, m inter return diag.FromErr(err) } - paths := []string{} + paths := []string{"data_sources"} if d.HasChange("title") { paths = append(paths, "title") } @@ -625,7 +625,7 @@ func setInstanceMessage( }) listAllDatabases := d.Get("list_all_databases").(bool) - databases, err := client.ListDatabase(ctx, instance.Name, "", listAllDatabases) + databases, err := client.ListDatabase(ctx, instance.Name, &api.DatabaseFilter{}, listAllDatabases) if err != nil { return diag.FromErr(err) } diff --git a/provider/resource_project.go b/provider/resource_project.go index ecb3bb0..c879065 100644 --- a/provider/resource_project.go +++ b/provider/resource_project.go @@ -440,7 +440,7 @@ func updateMembersInProject(ctx context.Context, d *schema.ResourceData, client const batchSize = 100 func updateDatabasesInProject(ctx context.Context, d *schema.ResourceData, client api.Client, projectName string) diag.Diagnostics { - databases, err := client.ListDatabase(ctx, projectName, "", true) + databases, err := client.ListDatabase(ctx, projectName, &api.DatabaseFilter{}, true) if err != nil { return diag.Errorf("failed to list database with error: %v", err.Error()) }