diff --git a/api/client.go b/api/client.go
index 9f742ca..7288a9d 100644
--- a/api/client.go
+++ b/api/client.go
@@ -86,4 +86,28 @@ type Client interface {
// Cel
// ParseExpression parse the expression string.
ParseExpression(ctx context.Context, expression string) (*v1alpha1.Expr, error)
+
+ // VCS Provider
+ // ListVCSProvider will returns all vcs providers.
+ ListVCSProvider(ctx context.Context) (*v1pb.ListVCSProvidersResponse, error)
+ // GetVCSProvider gets the vcs by id.
+ GetVCSProvider(ctx context.Context, name string) (*v1pb.VCSProvider, error)
+ // CreateVCSProvider creates the vcs provider.
+ CreateVCSProvider(ctx context.Context, vcsID string, vcs *v1pb.VCSProvider) (*v1pb.VCSProvider, error)
+ // UpdateVCSProvider updates the vcs provider.
+ UpdateVCSProvider(ctx context.Context, patch *v1pb.VCSProvider, updateMasks []string) (*v1pb.VCSConnector, error)
+ // DeleteVCSProvider deletes the vcs provider.
+ DeleteVCSProvider(ctx context.Context, name string) error
+
+ // VCS Connector
+ // ListVCSConnector will returns all vcs connector in a project.
+ ListVCSConnector(ctx context.Context, projectName string) (*v1pb.ListVCSConnectorsResponse, error)
+ // GetVCSConnector gets the vcs connector by id.
+ GetVCSConnector(ctx context.Context, name string) (*v1pb.VCSConnector, error)
+ // CreateVCSConnector creates the vcs connector in a project.
+ CreateVCSConnector(ctx context.Context, projectName, connectorID string, connector *v1pb.VCSConnector) (*v1pb.VCSConnector, error)
+ // UpdateVCSConnector updates the vcs connector.
+ UpdateVCSConnector(ctx context.Context, patch *v1pb.VCSConnector, updateMasks []string) (*v1pb.VCSConnector, error)
+ // DeleteVCSConnector deletes the vcs provider.
+ DeleteVCSConnector(ctx context.Context, name string) error
}
diff --git a/client/common.go b/client/common.go
index 92d30fa..23447d1 100644
--- a/client/common.go
+++ b/client/common.go
@@ -1,8 +1,76 @@
package client
import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
"google.golang.org/protobuf/encoding/protojson"
+ "google.golang.org/protobuf/reflect/protoreflect"
)
// ProtojsonUnmarshaler is the unmarshal for protocol.
var ProtojsonUnmarshaler = protojson.UnmarshalOptions{DiscardUnknown: true}
+
+// deleteResource deletes the resource by name.
+func (c *client) deleteResource(ctx context.Context, name string) error {
+ req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/%s/%s", c.url, c.version, name), nil)
+ if err != nil {
+ return err
+ }
+
+ if _, err := c.doRequest(req); err != nil {
+ return err
+ }
+ return nil
+}
+
+// undeleteResource undeletes the resource by name.
+func (c *client) undeleteResource(ctx context.Context, name string) ([]byte, error) {
+ req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/%s/%s:undelete", c.url, c.version, name), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ body, err := c.doRequest(req)
+ if err != nil {
+ return nil, err
+ }
+
+ return body, nil
+}
+
+// deleteResource deletes the resource by name.
+func (c *client) updateResource(ctx context.Context, name string, patch protoreflect.ProtoMessage, updateMasks []string, allowMissing bool) ([]byte, error) {
+ payload, err := protojson.Marshal(patch)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequestWithContext(ctx, "PATCH", fmt.Sprintf("%s/%s/%s?update_mask=%s&allow_missing=%v", c.url, c.version, name, strings.Join(updateMasks, ","), allowMissing), strings.NewReader(string(payload)))
+ if err != nil {
+ return nil, err
+ }
+
+ body, err := c.doRequest(req)
+ if err != nil {
+ return nil, err
+ }
+
+ return body, nil
+}
+
+// getResource gets the resource by name.
+func (c *client) getResource(ctx context.Context, name string) ([]byte, error) {
+ req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/%s", c.url, c.version, name), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ body, err := c.doRequest(req)
+ if err != nil {
+ return nil, err
+ }
+ return body, nil
+}
diff --git a/client/database.go b/client/database.go
index 05f090c..0677c17 100644
--- a/client/database.go
+++ b/client/database.go
@@ -5,20 +5,13 @@ 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 environment resource id, instance resource id and the database name.
+// GetDatabase gets the database by the database name.
func (c *client) GetDatabase(ctx context.Context, databaseName string) (*v1pb.Database, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/%s", c.url, c.version, databaseName), nil)
- if err != nil {
- return nil, err
- }
-
- body, err := c.doRequest(req)
+ body, err := c.getResource(ctx, databaseName)
if err != nil {
return nil, err
}
@@ -58,17 +51,7 @@ func (c *client) ListDatabase(ctx context.Context, instanceID, filter string) (*
// UpdateDatabase patches the database.
func (c *client) UpdateDatabase(ctx context.Context, patch *v1pb.Database, updateMasks []string) (*v1pb.Database, error) {
- payload, err := protojson.Marshal(patch)
- if err != nil {
- return nil, err
- }
-
- req, err := http.NewRequestWithContext(ctx, "PATCH", fmt.Sprintf("%s/%s/%s?update_mask=%s", c.url, c.version, patch.Name, strings.Join(updateMasks, ",")), strings.NewReader(string(payload)))
- if err != nil {
- return nil, err
- }
-
- body, err := c.doRequest(req)
+ body, err := c.updateResource(ctx, patch.Name, patch, updateMasks, false /* allow missing = false*/)
if err != nil {
return nil, err
}
diff --git a/client/environment.go b/client/environment.go
index 55a13be..e742578 100644
--- a/client/environment.go
+++ b/client/environment.go
@@ -37,12 +37,7 @@ func (c *client) CreateEnvironment(ctx context.Context, environmentID string, cr
// GetEnvironment gets the environment by id.
func (c *client) GetEnvironment(ctx context.Context, environmentName string) (*v1pb.Environment, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/%s", c.url, c.version, environmentName), nil)
- if err != nil {
- return nil, err
- }
-
- body, err := c.doRequest(req)
+ body, err := c.getResource(ctx, environmentName)
if err != nil {
return nil, err
}
@@ -76,18 +71,8 @@ func (c *client) ListEnvironment(ctx context.Context, showDeleted bool) (*v1pb.L
}
// UpdateEnvironment updates the environment.
-func (c *client) UpdateEnvironment(ctx context.Context, patch *v1pb.Environment, updateMask []string) (*v1pb.Environment, error) {
- payload, err := protojson.Marshal(patch)
- if err != nil {
- return nil, err
- }
-
- req, err := http.NewRequestWithContext(ctx, "PATCH", fmt.Sprintf("%s/%s/%s?update_mask=%s", c.url, c.version, patch.Name, strings.Join(updateMask, ",")), strings.NewReader(string(payload)))
- if err != nil {
- return nil, err
- }
-
- body, err := c.doRequest(req)
+func (c *client) UpdateEnvironment(ctx context.Context, patch *v1pb.Environment, updateMasks []string) (*v1pb.Environment, error) {
+ body, err := c.updateResource(ctx, patch.Name, patch, updateMasks, false /* allow missing = false*/)
if err != nil {
return nil, err
}
@@ -102,25 +87,12 @@ func (c *client) UpdateEnvironment(ctx context.Context, patch *v1pb.Environment,
// DeleteEnvironment deletes the environment.
func (c *client) DeleteEnvironment(ctx context.Context, environmentName string) error {
- req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/%s/%s", c.url, c.version, environmentName), nil)
- if err != nil {
- return err
- }
-
- if _, err := c.doRequest(req); err != nil {
- return err
- }
- return nil
+ return c.deleteResource(ctx, environmentName)
}
// UndeleteEnvironment undeletes the environment.
func (c *client) UndeleteEnvironment(ctx context.Context, environmentName string) (*v1pb.Environment, error) {
- req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/%s/%s:undelete", c.url, c.version, environmentName), nil)
- if err != nil {
- return nil, err
- }
-
- body, err := c.doRequest(req)
+ body, err := c.undeleteResource(ctx, environmentName)
if err != nil {
return nil, err
}
diff --git a/client/instance.go b/client/instance.go
index 215baf6..09172ac 100644
--- a/client/instance.go
+++ b/client/instance.go
@@ -32,12 +32,7 @@ func (c *client) ListInstance(ctx context.Context, showDeleted bool) (*v1pb.List
// GetInstance gets the instance by id.
func (c *client) GetInstance(ctx context.Context, instanceName string) (*v1pb.Instance, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/%s", c.url, c.version, instanceName), nil)
- if err != nil {
- return nil, err
- }
-
- body, err := c.doRequest(req)
+ body, err := c.getResource(ctx, instanceName)
if err != nil {
return nil, err
}
@@ -78,18 +73,7 @@ func (c *client) CreateInstance(ctx context.Context, instanceID string, instance
// UpdateInstance updates the instance.
func (c *client) UpdateInstance(ctx context.Context, patch *v1pb.Instance, updateMasks []string) (*v1pb.Instance, error) {
- payload, err := protojson.Marshal(patch)
- if err != nil {
- return nil, err
- }
-
- req, err := http.NewRequestWithContext(ctx, "PATCH", fmt.Sprintf("%s/%s/%s?update_mask=%s", c.url, c.version, patch.Name, strings.Join(updateMasks, ",")), strings.NewReader(string(payload)))
-
- if err != nil {
- return nil, err
- }
-
- body, err := c.doRequest(req)
+ body, err := c.updateResource(ctx, patch.Name, patch, updateMasks, false /* allow missing = false*/)
if err != nil {
return nil, err
}
@@ -104,25 +88,12 @@ func (c *client) UpdateInstance(ctx context.Context, patch *v1pb.Instance, updat
// DeleteInstance deletes the instance.
func (c *client) DeleteInstance(ctx context.Context, instanceName string) error {
- req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/%s/%s", c.url, c.version, instanceName), nil)
- if err != nil {
- return err
- }
-
- if _, err := c.doRequest(req); err != nil {
- return err
- }
- return nil
+ return c.deleteResource(ctx, instanceName)
}
// UndeleteInstance undeletes the instance.
func (c *client) UndeleteInstance(ctx context.Context, instanceName string) (*v1pb.Instance, error) {
- req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/%s/%s:undelete", c.url, c.version, instanceName), nil)
- if err != nil {
- return nil, err
- }
-
- body, err := c.doRequest(req)
+ body, err := c.undeleteResource(ctx, instanceName)
if err != nil {
return nil, err
}
diff --git a/client/policy.go b/client/policy.go
index 23e6e65..a2e8fcc 100644
--- a/client/policy.go
+++ b/client/policy.go
@@ -4,10 +4,8 @@ import (
"context"
"fmt"
"net/http"
- "strings"
v1pb "github.com/bytebase/bytebase/proto/generated-go/v1"
- "google.golang.org/protobuf/encoding/protojson"
)
// ListPolicies lists policies in a specific resource.
@@ -38,12 +36,7 @@ func (c *client) ListPolicies(ctx context.Context, parent string) (*v1pb.ListPol
// GetPolicy gets a policy in a specific resource.
func (c *client) GetPolicy(ctx context.Context, policyName string) (*v1pb.Policy, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/%s", c.url, c.version, policyName), nil)
- if err != nil {
- return nil, err
- }
-
- body, err := c.doRequest(req)
+ body, err := c.getResource(ctx, policyName)
if err != nil {
return nil, err
}
@@ -58,17 +51,7 @@ func (c *client) GetPolicy(ctx context.Context, policyName string) (*v1pb.Policy
// UpsertPolicy creates or updates the policy.
func (c *client) UpsertPolicy(ctx context.Context, policy *v1pb.Policy, updateMasks []string) (*v1pb.Policy, error) {
- payload, err := protojson.Marshal(policy)
- if err != nil {
- return nil, err
- }
-
- req, err := http.NewRequestWithContext(ctx, "PATCH", fmt.Sprintf("%s/%s/%s?allow_missing=true&update_mask=%s", c.url, c.version, policy.Name, strings.Join(updateMasks, ",")), strings.NewReader(string(payload)))
- if err != nil {
- return nil, err
- }
-
- body, err := c.doRequest(req)
+ body, err := c.updateResource(ctx, policy.Name, policy, updateMasks, true /* allow missing = true*/)
if err != nil {
return nil, err
}
@@ -83,13 +66,5 @@ func (c *client) UpsertPolicy(ctx context.Context, policy *v1pb.Policy, updateMa
// DeletePolicy deletes the policy.
func (c *client) DeletePolicy(ctx context.Context, policyName string) error {
- req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/%s/%s", c.url, c.version, policyName), nil)
- if err != nil {
- return err
- }
-
- if _, err := c.doRequest(req); err != nil {
- return err
- }
- return nil
+ return c.deleteResource(ctx, policyName)
}
diff --git a/client/project.go b/client/project.go
index 278d750..6b5e7ef 100644
--- a/client/project.go
+++ b/client/project.go
@@ -12,12 +12,7 @@ import (
// GetProject gets the project by resource id.
func (c *client) GetProject(ctx context.Context, projectName string) (*v1pb.Project, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/%s", c.url, c.version, projectName), nil)
- if err != nil {
- return nil, err
- }
-
- body, err := c.doRequest(req)
+ body, err := c.getResource(ctx, projectName)
if err != nil {
return nil, err
}
@@ -77,19 +72,8 @@ func (c *client) CreateProject(ctx context.Context, projectID string, project *v
}
// UpdateProject updates the project.
-func (c *client) UpdateProject(ctx context.Context, patch *v1pb.Project, updateMask []string) (*v1pb.Project, error) {
- payload, err := protojson.Marshal(patch)
- if err != nil {
- return nil, err
- }
-
- req, err := http.NewRequestWithContext(ctx, "PATCH", fmt.Sprintf("%s/%s/%s?update_mask=%s", c.url, c.version, patch.Name, strings.Join(updateMask, ",")), strings.NewReader(string(payload)))
-
- if err != nil {
- return nil, err
- }
-
- body, err := c.doRequest(req)
+func (c *client) UpdateProject(ctx context.Context, patch *v1pb.Project, updateMasks []string) (*v1pb.Project, error) {
+ body, err := c.updateResource(ctx, patch.Name, patch, updateMasks, false /* allow missing = false*/)
if err != nil {
return nil, err
}
@@ -104,25 +88,12 @@ func (c *client) UpdateProject(ctx context.Context, patch *v1pb.Project, updateM
// DeleteProject deletes the project.
func (c *client) DeleteProject(ctx context.Context, projectName string) error {
- req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/%s/%s", c.url, c.version, projectName), nil)
- if err != nil {
- return err
- }
-
- if _, err := c.doRequest(req); err != nil {
- return err
- }
- return nil
+ return c.deleteResource(ctx, projectName)
}
// UndeleteProject undeletes the project.
func (c *client) UndeleteProject(ctx context.Context, projectName string) (*v1pb.Project, error) {
- req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/%s/%s:undelete", c.url, c.version, projectName), nil)
- if err != nil {
- return nil, err
- }
-
- body, err := c.doRequest(req)
+ body, err := c.undeleteResource(ctx, projectName)
if err != nil {
return nil, err
}
diff --git a/client/setting.go b/client/setting.go
index 96fd47e..a33389d 100644
--- a/client/setting.go
+++ b/client/setting.go
@@ -4,10 +4,8 @@ import (
"context"
"fmt"
"net/http"
- "strings"
v1pb "github.com/bytebase/bytebase/proto/generated-go/v1"
- "google.golang.org/protobuf/encoding/protojson"
)
// ListSettings lists all settings.
@@ -32,12 +30,7 @@ func (c *client) ListSettings(ctx context.Context) (*v1pb.ListSettingsResponse,
// GetSetting gets the setting by the name.
func (c *client) GetSetting(ctx context.Context, settingName string) (*v1pb.Setting, error) {
- req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/%s", c.url, c.version, settingName), nil)
- if err != nil {
- return nil, err
- }
-
- body, err := c.doRequest(req)
+ body, err := c.getResource(ctx, settingName)
if err != nil {
return nil, err
}
@@ -52,18 +45,7 @@ func (c *client) GetSetting(ctx context.Context, settingName string) (*v1pb.Sett
// UpsertSetting updates or creates the setting.
func (c *client) UpsertSetting(ctx context.Context, upsert *v1pb.Setting, updateMasks []string) (*v1pb.Setting, error) {
- payload, err := protojson.Marshal(upsert)
- if err != nil {
- return nil, err
- }
-
- req, err := http.NewRequestWithContext(ctx, "PATCH", fmt.Sprintf("%s/%s/%s?update_mask=%s&allow_missing=true", c.url, c.version, upsert.Name, strings.Join(updateMasks, ",")), strings.NewReader(string(payload)))
-
- if err != nil {
- return nil, err
- }
-
- body, err := c.doRequest(req)
+ body, err := c.updateResource(ctx, upsert.Name, upsert, updateMasks, true /* allow missing = true*/)
if err != nil {
return nil, err
}
diff --git a/client/user.go b/client/user.go
new file mode 100644
index 0000000..0b72705
--- /dev/null
+++ b/client/user.go
@@ -0,0 +1,107 @@
+package client
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ v1pb "github.com/bytebase/bytebase/proto/generated-go/v1"
+ "google.golang.org/protobuf/encoding/protojson"
+)
+
+// ListUser list all users.
+func (c *client) ListUser(ctx context.Context, showDeleted bool) (*v1pb.ListUsersResponse, error) {
+ req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/users?showDeleted=%v", c.url, c.version, showDeleted), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ body, err := c.doRequest(req)
+ if err != nil {
+ return nil, err
+ }
+
+ var res v1pb.ListUsersResponse
+ if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil {
+ return nil, err
+ }
+
+ return &res, nil
+}
+
+// CreateUser creates the user.
+func (c *client) CreateUser(ctx context.Context, user *v1pb.User) (*v1pb.User, error) {
+ payload, err := protojson.Marshal(user)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/%s/users", c.url, c.version), strings.NewReader(string(payload)))
+
+ if err != nil {
+ return nil, err
+ }
+
+ body, err := c.doRequest(req)
+ if err != nil {
+ return nil, err
+ }
+
+ var res v1pb.User
+ if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil {
+ return nil, err
+ }
+
+ return &res, nil
+}
+
+// GetUser gets the user by name.
+func (c *client) GetUser(ctx context.Context, userName string) (*v1pb.User, error) {
+ body, err := c.getResource(ctx, userName)
+ if err != nil {
+ return nil, err
+ }
+
+ var res v1pb.User
+ if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil {
+ return nil, err
+ }
+
+ return &res, nil
+}
+
+// UpdateUser updates the user.
+func (c *client) UpdateUser(ctx context.Context, patch *v1pb.User, updateMasks []string) (*v1pb.User, error) {
+ body, err := c.updateResource(ctx, patch.Name, patch, updateMasks, false /* allow missing = false*/)
+ if err != nil {
+ return nil, err
+ }
+
+ var res v1pb.User
+ if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil {
+ return nil, err
+ }
+
+ return &res, nil
+}
+
+// DeleteUser deletes the user by name.
+func (c *client) DeleteUser(ctx context.Context, userName string) error {
+ return c.deleteResource(ctx, userName)
+}
+
+// UndeleteUser undeletes the user by name.
+func (c *client) UndeleteUser(ctx context.Context, userName string) (*v1pb.User, error) {
+ body, err := c.undeleteResource(ctx, userName)
+ if err != nil {
+ return nil, err
+ }
+
+ var res v1pb.User
+ if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil {
+ return nil, err
+ }
+
+ return &res, nil
+}
diff --git a/client/vcs.go b/client/vcs.go
new file mode 100644
index 0000000..e36382f
--- /dev/null
+++ b/client/vcs.go
@@ -0,0 +1,173 @@
+package client
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strings"
+
+ v1pb "github.com/bytebase/bytebase/proto/generated-go/v1"
+ "google.golang.org/protobuf/encoding/protojson"
+)
+
+// ListVCSProvider will returns all vcs providers.
+func (c *client) ListVCSProvider(ctx context.Context) (*v1pb.ListVCSProvidersResponse, error) {
+ req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/vcsProviders", c.url, c.version), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ body, err := c.doRequest(req)
+ if err != nil {
+ return nil, err
+ }
+
+ var res v1pb.ListVCSProvidersResponse
+ if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil {
+ return nil, err
+ }
+
+ return &res, nil
+}
+
+// GetVCSProvider gets the vcs by id.
+func (c *client) GetVCSProvider(ctx context.Context, name string) (*v1pb.VCSProvider, error) {
+ body, err := c.getResource(ctx, name)
+ if err != nil {
+ return nil, err
+ }
+
+ var res v1pb.VCSProvider
+ if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil {
+ return nil, err
+ }
+
+ return &res, nil
+}
+
+// CreateVCSProvider creates the vcs provider.
+func (c *client) CreateVCSProvider(ctx context.Context, vcsID string, vcs *v1pb.VCSProvider) (*v1pb.VCSProvider, error) {
+ payload, err := protojson.Marshal(vcs)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/%s/vcsProviders?vcsProviderId=%s", c.url, c.version, vcsID), strings.NewReader(string(payload)))
+
+ if err != nil {
+ return nil, err
+ }
+
+ body, err := c.doRequest(req)
+ if err != nil {
+ return nil, err
+ }
+
+ var res v1pb.VCSProvider
+ if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil {
+ return nil, err
+ }
+
+ return &res, nil
+}
+
+// UpdateVCSProvider updates the vcs provider.
+func (c *client) UpdateVCSProvider(ctx context.Context, patch *v1pb.VCSProvider, updateMasks []string) (*v1pb.VCSConnector, error) {
+ body, err := c.updateResource(ctx, patch.Name, patch, updateMasks, false /* allow missing = false*/)
+ if err != nil {
+ return nil, err
+ }
+
+ var res v1pb.VCSConnector
+ if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil {
+ return nil, err
+ }
+
+ return &res, nil
+}
+
+// DeleteVCSProvider deletes the vcs provider.
+func (c *client) DeleteVCSProvider(ctx context.Context, name string) error {
+ return c.deleteResource(ctx, name)
+}
+
+// ListVCSConnector will returns all vcs connector in a project.
+func (c *client) ListVCSConnector(ctx context.Context, projectName string) (*v1pb.ListVCSConnectorsResponse, error) {
+ req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s/%s/vcsConnectors", c.url, c.version, projectName), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ body, err := c.doRequest(req)
+ if err != nil {
+ return nil, err
+ }
+
+ var res v1pb.ListVCSConnectorsResponse
+ if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil {
+ return nil, err
+ }
+
+ return &res, nil
+}
+
+// GetVCSConnector gets the vcs connector by id.
+func (c *client) GetVCSConnector(ctx context.Context, name string) (*v1pb.VCSConnector, error) {
+ body, err := c.getResource(ctx, name)
+ if err != nil {
+ return nil, err
+ }
+
+ var res v1pb.VCSConnector
+ if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil {
+ return nil, err
+ }
+
+ return &res, nil
+}
+
+// CreateVCSConnector creates the vcs connector in a project.
+func (c *client) CreateVCSConnector(ctx context.Context, projectName, connectorID string, connector *v1pb.VCSConnector) (*v1pb.VCSConnector, error) {
+ payload, err := protojson.Marshal(connector)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/%s/%s/vcsConnectors?vcsConnectorId=%s", c.url, c.version, projectName, connectorID), strings.NewReader(string(payload)))
+
+ if err != nil {
+ return nil, err
+ }
+
+ body, err := c.doRequest(req)
+ if err != nil {
+ return nil, err
+ }
+
+ var res v1pb.VCSConnector
+ if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil {
+ return nil, err
+ }
+
+ return &res, nil
+}
+
+// UpdateVCSConnector updates the vcs connector.
+func (c *client) UpdateVCSConnector(ctx context.Context, patch *v1pb.VCSConnector, updateMasks []string) (*v1pb.VCSConnector, error) {
+ body, err := c.updateResource(ctx, patch.Name, patch, updateMasks, false /* allow missing = false*/)
+ if err != nil {
+ return nil, err
+ }
+
+ var res v1pb.VCSConnector
+ if err := ProtojsonUnmarshaler.Unmarshal(body, &res); err != nil {
+ return nil, err
+ }
+
+ return &res, nil
+}
+
+// DeleteVCSConnector deletes the vcs provider.
+func (c *client) DeleteVCSConnector(ctx context.Context, name string) error {
+ return c.deleteResource(ctx, name)
+}
diff --git a/docs/data-sources/policy.md b/docs/data-sources/policy.md
index c519319..4ac2664 100644
--- a/docs/data-sources/policy.md
+++ b/docs/data-sources/policy.md
@@ -21,8 +21,8 @@ The policy data source.
### Optional
-- `masking_exception_policy` (Block List) (see [below for nested schema](#nestedblock--masking_exception_policy))
-- `masking_policy` (Block List) (see [below for nested schema](#nestedblock--masking_policy))
+- `masking_exception_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--masking_exception_policy))
+- `masking_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--masking_policy))
- `parent` (String) The policy parent name for the policy, support projects/{resource id}, environments/{resource id}, instances/{resource id}, or instances/{resource id}/databases/{database name}
### Read-Only
diff --git a/docs/data-sources/vcs_connector.md b/docs/data-sources/vcs_connector.md
new file mode 100644
index 0000000..5e59ec7
--- /dev/null
+++ b/docs/data-sources/vcs_connector.md
@@ -0,0 +1,43 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "bytebase_vcs_connector Data Source - terraform-provider-bytebase"
+subcategory: ""
+description: |-
+ The vcs connector data source.
+---
+
+# bytebase_vcs_connector (Data Source)
+
+The vcs connector data source.
+
+
+
+
+## Schema
+
+### Required
+
+- `project` (String) The project name in projects/{resource id} format.
+- `resource_id` (String) The vcs connector unique resource id.
+
+### Optional
+
+- `database_group` (String) Apply changes to the database group.
+
+### Read-Only
+
+- `create_time` (String) The vcs connector create time in YYYY-MM-DDThh:mm:ss.000Z format
+- `creator` (String) The vcs connector creator in users/{email} format.
+- `id` (String) The ID of this resource.
+- `name` (String) The vcs connector full name in projects/{project}/vcsConnector/{resource id} format.
+- `repository_branch` (String) The connected repository branch in vcs provider.
+- `repository_directory` (String) The connected repository directory in vcs provider.
+- `repository_id` (String) The connected repository id in vcs provider.
+- `repository_path` (String) The connected repository path in vcs provider.
+- `repository_url` (String) The connected repository url in vcs provider.
+- `title` (String) The vcs connector title.
+- `update_time` (String) The vcs connector update time in YYYY-MM-DDThh:mm:ss.000Z format
+- `updater` (String) The vcs connector updater in users/{email} format.
+- `vcs_provider` (String) The vcs provider full name in vcsProviders/{resource id} format.
+
+
diff --git a/docs/data-sources/vcs_connector_list.md b/docs/data-sources/vcs_connector_list.md
new file mode 100644
index 0000000..7ed259a
--- /dev/null
+++ b/docs/data-sources/vcs_connector_list.md
@@ -0,0 +1,48 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "bytebase_vcs_connector_list Data Source - terraform-provider-bytebase"
+subcategory: ""
+description: |-
+ The vcs connector data source list.
+---
+
+# bytebase_vcs_connector_list (Data Source)
+
+The vcs connector data source list.
+
+
+
+
+## Schema
+
+### Required
+
+- `project` (String) The project name in projects/{resource id} format.
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+- `vcs_connectors` (List of Object) (see [below for nested schema](#nestedatt--vcs_connectors))
+
+
+### Nested Schema for `vcs_connectors`
+
+Read-Only:
+
+- `create_time` (String)
+- `creator` (String)
+- `database_group` (String)
+- `name` (String)
+- `project` (String)
+- `repository_branch` (String)
+- `repository_directory` (String)
+- `repository_id` (String)
+- `repository_path` (String)
+- `repository_url` (String)
+- `resource_id` (String)
+- `title` (String)
+- `update_time` (String)
+- `updater` (String)
+- `vcs_provider` (String)
+
+
diff --git a/docs/data-sources/vcs_provider.md b/docs/data-sources/vcs_provider.md
new file mode 100644
index 0000000..808fef6
--- /dev/null
+++ b/docs/data-sources/vcs_provider.md
@@ -0,0 +1,30 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "bytebase_vcs_provider Data Source - terraform-provider-bytebase"
+subcategory: ""
+description: |-
+ The vcs provider data source.
+---
+
+# bytebase_vcs_provider (Data Source)
+
+The vcs provider data source.
+
+
+
+
+## Schema
+
+### Required
+
+- `resource_id` (String) The vcs provider unique resource id.
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+- `name` (String) The vcs provider full name in vcsProviders/{resource id} format.
+- `title` (String) The vcs provider title.
+- `type` (String) The vcs provider type.
+- `url` (String) The vcs provider url.
+
+
diff --git a/docs/data-sources/vcs_provider_list.md b/docs/data-sources/vcs_provider_list.md
new file mode 100644
index 0000000..fdaa0b7
--- /dev/null
+++ b/docs/data-sources/vcs_provider_list.md
@@ -0,0 +1,34 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "bytebase_vcs_provider_list Data Source - terraform-provider-bytebase"
+subcategory: ""
+description: |-
+ The vcs provider data source list.
+---
+
+# bytebase_vcs_provider_list (Data Source)
+
+The vcs provider data source list.
+
+
+
+
+## Schema
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+- `vcs_providers` (List of Object) (see [below for nested schema](#nestedatt--vcs_providers))
+
+
+### Nested Schema for `vcs_providers`
+
+Read-Only:
+
+- `name` (String)
+- `resource_id` (String)
+- `title` (String)
+- `type` (String)
+- `url` (String)
+
+
diff --git a/docs/resources/environment.md b/docs/resources/environment.md
index 9ccec51..d9f22d1 100644
--- a/docs/resources/environment.md
+++ b/docs/resources/environment.md
@@ -20,7 +20,7 @@ The environment resource.
- `environment_tier_policy` (String) If marked as PROTECTED, developers cannot execute any query on this environment's databases using SQL Editor by default.
- `order` (Number) The environment sorting order.
- `resource_id` (String) The environment unique resource id.
-- `title` (String) The environment unique name.
+- `title` (String) The environment title.
### Read-Only
diff --git a/docs/resources/policy.md b/docs/resources/policy.md
index 7df802b..315c84e 100644
--- a/docs/resources/policy.md
+++ b/docs/resources/policy.md
@@ -24,8 +24,8 @@ The policy resource.
- `enforce` (Boolean) Decide if the policy is enforced.
- `inherit_from_parent` (Boolean) Decide if the policy should inherit from the parent.
-- `masking_exception_policy` (Block List) (see [below for nested schema](#nestedblock--masking_exception_policy))
-- `masking_policy` (Block List) (see [below for nested schema](#nestedblock--masking_policy))
+- `masking_exception_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--masking_exception_policy))
+- `masking_policy` (Block List, Max: 1) (see [below for nested schema](#nestedblock--masking_policy))
### Read-Only
diff --git a/docs/resources/vcs_connector.md b/docs/resources/vcs_connector.md
new file mode 100644
index 0000000..e89a4b1
--- /dev/null
+++ b/docs/resources/vcs_connector.md
@@ -0,0 +1,43 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "bytebase_vcs_connector Resource - terraform-provider-bytebase"
+subcategory: ""
+description: |-
+ The vcs connector resource.
+---
+
+# bytebase_vcs_connector (Resource)
+
+The vcs connector resource.
+
+
+
+
+## Schema
+
+### Required
+
+- `project` (String) The project name in projects/{resource id} format.
+- `repository_branch` (String) The connected repository branch in vcs provider.
+- `repository_directory` (String) The connected repository directory in vcs provider.
+- `repository_id` (String) The connected repository id in vcs provider.
+- `repository_path` (String) The connected repository path in vcs provider.
+- `repository_url` (String) The connected repository url in vcs provider.
+- `resource_id` (String) The vcs connector unique resource id.
+- `title` (String) The vcs connector title.
+- `vcs_provider` (String) The vcs provider full name in vcsProviders/{resource id} format.
+
+### Optional
+
+- `database_group` (String) Apply changes to the database group.
+
+### Read-Only
+
+- `create_time` (String) The vcs connector create time in YYYY-MM-DDThh:mm:ss.000Z format
+- `creator` (String) The vcs connector creator in users/{email} format.
+- `id` (String) The ID of this resource.
+- `name` (String) The vcs connector full name in projects/{project}/vcsConnector/{resource id} format.
+- `update_time` (String) The vcs connector update time in YYYY-MM-DDThh:mm:ss.000Z format
+- `updater` (String) The vcs connector updater in users/{email} format.
+
+
diff --git a/docs/resources/vcs_provider.md b/docs/resources/vcs_provider.md
new file mode 100644
index 0000000..51bcab7
--- /dev/null
+++ b/docs/resources/vcs_provider.md
@@ -0,0 +1,34 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "bytebase_vcs_provider Resource - terraform-provider-bytebase"
+subcategory: ""
+description: |-
+ The vcs provider resource.
+---
+
+# bytebase_vcs_provider (Resource)
+
+The vcs provider resource.
+
+
+
+
+## Schema
+
+### Required
+
+- `access_token` (String, Sensitive) The vcs provider token. Check the docs https://bytebase.cc/docs/vcs-integration/add-git-provider for details.
+- `resource_id` (String) The vcs provider unique resource id.
+- `title` (String) The vcs provider title.
+- `type` (String) The vcs provider type.
+
+### Optional
+
+- `url` (String) The vcs provider url. You need to provide the url if you're using the self-host GitLab or self-host GitHub.
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+- `name` (String) The vcs provider full name in vcsProviders/{resource id} format.
+
+
diff --git a/examples/policies/main.tf b/examples/policies/main.tf
index b0da04d..fe07e4a 100644
--- a/examples/policies/main.tf
+++ b/examples/policies/main.tf
@@ -26,3 +26,11 @@ data "bytebase_policy" "masking_exception_policy" {
parent = "projects/project-sample"
type = "MASKING_EXCEPTION"
}
+
+output "masking_policy" {
+ value = data.bytebase_policy.masking_policy
+}
+
+output "masking_exception_policy" {
+ value = data.bytebase_policy.masking_exception_policy
+}
diff --git a/examples/settings/main.tf b/examples/settings/main.tf
index 377e866..d130a71 100644
--- a/examples/settings/main.tf
+++ b/examples/settings/main.tf
@@ -24,3 +24,11 @@ data "bytebase_setting" "approval_flow" {
data "bytebase_setting" "external_approval" {
name = "bb.workspace.approval.external"
}
+
+output "approval_flow" {
+ value = data.bytebase_setting.approval_flow
+}
+
+output "external_approval" {
+ value = data.bytebase_setting.external_approval
+}
diff --git a/examples/setup/main.tf b/examples/setup/main.tf
index 93b1dd7..34ee83e 100644
--- a/examples/setup/main.tf
+++ b/examples/setup/main.tf
@@ -208,3 +208,29 @@ resource "bytebase_policy" "masking_exception_policy" {
}
}
}
+
+resource "bytebase_vcs_provider" "github" {
+ resource_id = "vcs-github"
+ title = "GitHub GitOps"
+ type = "GITHUB"
+ access_token = ""
+}
+
+resource "bytebase_vcs_connector" "github" {
+ depends_on = [
+ bytebase_project.sample_project,
+ bytebase_vcs_provider.github
+ ]
+
+ resource_id = "connector-github"
+ title = "GitHub Connector"
+ project = bytebase_project.sample_project.name
+ vcs_provider = bytebase_vcs_provider.github.name
+ repository_id = "ed-bytebase/gitops"
+ repository_path = "ed-bytebase/gitops"
+ repository_directory = "/bytebase"
+ repository_branch = "main"
+ repository_url = "https://github.com/ed-bytebase/gitops"
+}
+
+
diff --git a/examples/vcs/main.tf b/examples/vcs/main.tf
new file mode 100644
index 0000000..3e763b3
--- /dev/null
+++ b/examples/vcs/main.tf
@@ -0,0 +1,46 @@
+terraform {
+ required_providers {
+ bytebase = {
+ version = "1.0.4"
+ # For local development, please use "terraform.local/bytebase/bytebase" instead
+ source = "registry.terraform.io/bytebase/bytebase"
+ }
+ }
+}
+
+provider "bytebase" {
+ # You need to replace the account and key with your Bytebase service account.
+ service_account = "terraform@service.bytebase.com"
+ service_key = "bbs_BxVIp7uQsARl8nR92ZZV"
+ # The Bytebase service URL. You can use the external URL in production.
+ # Check the docs about external URL: https://www.bytebase.com/docs/get-started/install/external-url
+ url = "https://bytebase.example.com"
+}
+
+locals {
+ project_id = "project-sample"
+}
+
+data "bytebase_vcs_provider" "github" {
+ resource_id = "vcs-github"
+}
+
+data "bytebase_project" "sample_project" {
+ resource_id = local.project_id
+}
+
+data "bytebase_vcs_connector" "github" {
+ depends_on = [
+ data.bytebase_project.sample_project
+ ]
+ resource_id = "connector-github"
+ project = data.bytebase_project.sample_project.name
+}
+
+output "vcs_provider_github" {
+ value = data.bytebase_vcs_provider.github
+}
+
+output "vcs_connector_github" {
+ value = data.bytebase_vcs_connector.github
+}
diff --git a/provider/data_source_policy.go b/provider/data_source_policy.go
index 1be5abd..f0ce11a 100644
--- a/provider/data_source_policy.go
+++ b/provider/data_source_policy.go
@@ -76,12 +76,15 @@ func getMaskingExceptionPolicySchema(computed bool) *schema.Schema {
Optional: true,
Default: nil,
Type: schema.TypeList,
+ MinItems: 0,
+ MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"exceptions": {
Computed: computed,
Optional: true,
Default: nil,
+ MinItems: 0,
Type: schema.TypeList,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
@@ -154,9 +157,12 @@ func getMaskingPolicySchema(computed bool) *schema.Schema {
Optional: true,
Default: nil,
Type: schema.TypeList,
+ MinItems: 0,
+ MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"mask_data": {
+ MinItems: 0,
Computed: computed,
Optional: true,
Default: nil,
diff --git a/provider/data_source_setting.go b/provider/data_source_setting.go
index 5b6a78a..91c8c4d 100644
--- a/provider/data_source_setting.go
+++ b/provider/data_source_setting.go
@@ -142,6 +142,7 @@ func getWorkspaceApprovalSetting(computed bool) *schema.Schema {
},
},
"conditions": {
+ MinItems: 0,
Computed: computed,
Type: schema.TypeList,
Optional: true,
diff --git a/provider/data_source_vcs_connector.go b/provider/data_source_vcs_connector.go
new file mode 100644
index 0000000..599d6e9
--- /dev/null
+++ b/provider/data_source_vcs_connector.go
@@ -0,0 +1,173 @@
+package provider
+
+import (
+ "context"
+ "fmt"
+ "regexp"
+ "time"
+
+ "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"
+)
+
+func dataSourceVCSConnector() *schema.Resource {
+ return &schema.Resource{
+ Description: "The vcs connector data source.",
+ ReadContext: dataSourceVCSConnectorRead,
+ Schema: map[string]*schema.Schema{
+ "resource_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: internal.ResourceIDValidation,
+ Description: "The vcs connector unique resource id.",
+ },
+ "project": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateDiagFunc: internal.ResourceNameValidation(regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.ProjectNamePrefix, internal.ResourceIDPattern))),
+ Description: "The project name in projects/{resource id} format.",
+ },
+ "name": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector full name in projects/{project}/vcsConnector/{resource id} format.",
+ },
+ "title": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector title.",
+ },
+ "creator": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector creator in users/{email} format.",
+ },
+ "create_time": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector create time in YYYY-MM-DDThh:mm:ss.000Z format",
+ },
+ "updater": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector updater in users/{email} format.",
+ },
+ "update_time": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector update time in YYYY-MM-DDThh:mm:ss.000Z format",
+ },
+ "vcs_provider": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs provider full name in vcsProviders/{resource id} format.",
+ },
+ "database_group": {
+ Type: schema.TypeString,
+ Computed: true,
+ Optional: true,
+ Description: "Apply changes to the database group.",
+ },
+ "repository_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The connected repository id in vcs provider.",
+ },
+ "repository_path": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The connected repository path in vcs provider.",
+ },
+ "repository_directory": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The connected repository directory in vcs provider.",
+ },
+ "repository_branch": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The connected repository branch in vcs provider.",
+ },
+ "repository_url": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The connected repository url in vcs provider.",
+ },
+ },
+ }
+}
+
+func dataSourceVCSConnectorRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ c := m.(api.Client)
+ project := d.Get("project").(string)
+ connectorName := fmt.Sprintf("%s/%s%s", project, internal.VCSConnectorNamePrefix, d.Get("resource_id").(string))
+
+ connector, err := c.GetVCSConnector(ctx, connectorName)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ d.SetId(connector.Name)
+
+ return setVCSConnector(d, connector)
+}
+
+func setVCSConnector(d *schema.ResourceData, connector *v1pb.VCSConnector) diag.Diagnostics {
+ projectID, connectorID, err := internal.GetVCSConnectorID(connector.Name)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ if err := d.Set("resource_id", connectorID); err != nil {
+ return diag.Errorf("cannot set resource_id for vcs connector: %s", err.Error())
+ }
+ if err := d.Set("project", fmt.Sprintf("%s%s", internal.ProjectNamePrefix, projectID)); err != nil {
+ return diag.Errorf("cannot set project for vcs connector: %s", err.Error())
+ }
+ if err := d.Set("title", connector.Title); err != nil {
+ return diag.Errorf("cannot set title for vcs connector: %s", err.Error())
+ }
+ if err := d.Set("name", connector.Name); err != nil {
+ return diag.Errorf("cannot set name for vcs connector: %s", err.Error())
+ }
+ if err := d.Set("creator", connector.Creator); err != nil {
+ return diag.Errorf("cannot set creator for vcs connector: %s", err.Error())
+ }
+ if err := d.Set("create_time", connector.CreateTime.AsTime().UTC().Format(time.RFC3339)); err != nil {
+ return diag.Errorf("cannot set create_time for vcs connector: %s", err.Error())
+ }
+ if err := d.Set("updater", connector.Updater); err != nil {
+ return diag.Errorf("cannot set updater for vcs connector: %s", err.Error())
+ }
+ if err := d.Set("update_time", connector.UpdateTime.AsTime().UTC().Format(time.RFC3339)); err != nil {
+ return diag.Errorf("cannot set update_time for vcs connector: %s", err.Error())
+ }
+ if err := d.Set("vcs_provider", connector.VcsProvider); err != nil {
+ return diag.Errorf("cannot set vcs_provider for vcs connector: %s", err.Error())
+ }
+ if err := d.Set("database_group", connector.DatabaseGroup); err != nil {
+ return diag.Errorf("cannot set database_group for vcs connector: %s", err.Error())
+ }
+ if err := d.Set("repository_id", connector.ExternalId); err != nil {
+ return diag.Errorf("cannot set repository_id for vcs connector: %s", err.Error())
+ }
+ if err := d.Set("repository_path", connector.FullPath); err != nil {
+ return diag.Errorf("cannot set repository_path for vcs connector: %s", err.Error())
+ }
+ if err := d.Set("repository_directory", connector.BaseDirectory); err != nil {
+ return diag.Errorf("cannot set repository_directory for vcs connector: %s", err.Error())
+ }
+ if err := d.Set("repository_branch", connector.Branch); err != nil {
+ return diag.Errorf("cannot set repository_branch for vcs connector: %s", err.Error())
+ }
+ if err := d.Set("repository_url", connector.WebUrl); err != nil {
+ return diag.Errorf("cannot set repository_url for vcs connector: %s", err.Error())
+ }
+
+ return nil
+}
diff --git a/provider/data_source_vcs_connector_list.go b/provider/data_source_vcs_connector_list.go
new file mode 100644
index 0000000..00f7012
--- /dev/null
+++ b/provider/data_source_vcs_connector_list.go
@@ -0,0 +1,160 @@
+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/bytebase/terraform-provider-bytebase/api"
+ "github.com/bytebase/terraform-provider-bytebase/provider/internal"
+)
+
+func dataSourceVCSConnectorList() *schema.Resource {
+ return &schema.Resource{
+ Description: "The vcs connector data source list.",
+ ReadContext: dataSourceVCSConnectorListRead,
+ Schema: map[string]*schema.Schema{
+ "project": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateDiagFunc: internal.ResourceNameValidation(regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.ProjectNamePrefix, internal.ResourceIDPattern))),
+ Description: "The project name in projects/{resource id} format.",
+ },
+ "vcs_connectors": {
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "resource_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector unique resource id.",
+ },
+ "project": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The project name in projects/{resource id} format.",
+ },
+ "name": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector full name in projects/{project}/vcsConnector/{resource id} format.",
+ },
+ "title": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector title.",
+ },
+ "creator": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector creator in users/{email} format.",
+ },
+ "create_time": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector create time in YYYY-MM-DDThh:mm:ss.000Z format",
+ },
+ "updater": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector updater in users/{email} format.",
+ },
+ "update_time": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector update time in YYYY-MM-DDThh:mm:ss.000Z format",
+ },
+ "vcs_provider": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs provider full name in vcsProviders/{resource id} format.",
+ },
+ "database_group": {
+ Type: schema.TypeString,
+ Computed: true,
+ Optional: true,
+ Description: "Apply changes to the database group.",
+ },
+ "repository_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The connected repository id in vcs provider.",
+ },
+ "repository_path": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The connected repository path in vcs provider.",
+ },
+ "repository_directory": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The connected repository directory in vcs provider.",
+ },
+ "repository_branch": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The connected repository branch in vcs provider.",
+ },
+ "repository_url": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The connected repository url in vcs provider.",
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func dataSourceVCSConnectorListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ c := m.(api.Client)
+ project := d.Get("project").(string)
+
+ response, err := c.ListVCSConnector(ctx, project)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ connectors := []map[string]interface{}{}
+ for _, connector := range response.VcsConnectors {
+ projectID, connectorID, err := internal.GetVCSConnectorID(connector.Name)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ rawConnector := make(map[string]interface{})
+ rawConnector["resource_id"] = connectorID
+ rawConnector["project"] = fmt.Sprintf("%s%s", internal.ProjectNamePrefix, projectID)
+ rawConnector["title"] = connector.Title
+ rawConnector["name"] = connector.Name
+ rawConnector["creator"] = connector.Creator
+ rawConnector["create_time"] = connector.CreateTime.AsTime().UTC().Format(time.RFC3339)
+ rawConnector["updater"] = connector.Updater
+ rawConnector["update_time"] = connector.UpdateTime.AsTime().UTC().Format(time.RFC3339)
+ rawConnector["vcs_provider"] = connector.VcsProvider
+ rawConnector["database_group"] = connector.DatabaseGroup
+ rawConnector["repository_id"] = connector.ExternalId
+ rawConnector["repository_path"] = connector.FullPath
+ rawConnector["repository_directory"] = connector.BaseDirectory
+ rawConnector["repository_branch"] = connector.Branch
+ rawConnector["repository_url"] = connector.WebUrl
+
+ connectors = append(connectors, rawConnector)
+ }
+
+ if err := d.Set("vcs_connectors", connectors); err != nil {
+ return diag.FromErr(err)
+ }
+
+ // always refresh
+ d.SetId(strconv.FormatInt(time.Now().Unix(), 10))
+
+ return nil
+}
diff --git a/provider/data_source_vcs_provider.go b/provider/data_source_vcs_provider.go
new file mode 100644
index 0000000..660870c
--- /dev/null
+++ b/provider/data_source_vcs_provider.go
@@ -0,0 +1,88 @@
+package provider
+
+import (
+ "context"
+ "fmt"
+
+ "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"
+)
+
+func dataSourceVCSProvider() *schema.Resource {
+ return &schema.Resource{
+ Description: "The vcs provider data source.",
+ ReadContext: dataSourceVCSProviderRead,
+ Schema: map[string]*schema.Schema{
+ "resource_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: internal.ResourceIDValidation,
+ Description: "The vcs provider unique resource id.",
+ },
+ "title": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs provider title.",
+ },
+ "name": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs provider full name in vcsProviders/{resource id} format.",
+ },
+ "type": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs provider type.",
+ },
+ "url": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs provider url.",
+ },
+ },
+ }
+}
+
+func dataSourceVCSProviderRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ c := m.(api.Client)
+ providerName := fmt.Sprintf("%s%s", internal.VCSProviderNamePrefix, d.Get("resource_id").(string))
+
+ provider, err := c.GetVCSProvider(ctx, providerName)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ d.SetId(provider.Name)
+
+ return setVCSProvider(d, provider)
+}
+
+func setVCSProvider(d *schema.ResourceData, provider *v1pb.VCSProvider) diag.Diagnostics {
+ providerID, err := internal.GetVCSProviderID(provider.Name)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ if err := d.Set("resource_id", providerID); err != nil {
+ return diag.Errorf("cannot set resource_id for vcs provider: %s", err.Error())
+ }
+ if err := d.Set("title", provider.Title); err != nil {
+ return diag.Errorf("cannot set title for vcs provider: %s", err.Error())
+ }
+ if err := d.Set("name", provider.Name); err != nil {
+ return diag.Errorf("cannot set name for vcs provider: %s", err.Error())
+ }
+ if err := d.Set("type", provider.Type.String()); err != nil {
+ return diag.Errorf("cannot set type for vcs provider: %s", err.Error())
+ }
+ if err := d.Set("url", provider.Url); err != nil {
+ return diag.Errorf("cannot set url for vcs provider: %s", err.Error())
+ }
+
+ return nil
+}
diff --git a/provider/data_source_vcs_provider_list.go b/provider/data_source_vcs_provider_list.go
new file mode 100644
index 0000000..8f4ecf4
--- /dev/null
+++ b/provider/data_source_vcs_provider_list.go
@@ -0,0 +1,93 @@
+package provider
+
+import (
+ "context"
+ "strconv"
+ "time"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+
+ "github.com/bytebase/terraform-provider-bytebase/api"
+ "github.com/bytebase/terraform-provider-bytebase/provider/internal"
+)
+
+func dataSourceVCSProviderList() *schema.Resource {
+ return &schema.Resource{
+ Description: "The vcs provider data source list.",
+ ReadContext: dataSourceVCSProviderListRead,
+ Schema: map[string]*schema.Schema{
+ "vcs_providers": {
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "resource_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs provider unique resource id.",
+ },
+ "title": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs provider title.",
+ },
+ "name": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs provider full name in vcsProviders/{resource id} format.",
+ },
+ "type": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs provider type.",
+ },
+ "url": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs provider url.",
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func dataSourceVCSProviderListRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ c := m.(api.Client)
+
+ // Warning or errors can be collected in a slice type
+ var diags diag.Diagnostics
+
+ response, err := c.ListVCSProvider(ctx)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ providers := []map[string]interface{}{}
+ for _, provider := range response.VcsProviders {
+ providerID, err := internal.GetVCSProviderID(provider.Name)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ rawProvider := make(map[string]interface{})
+ rawProvider["resource_id"] = providerID
+ rawProvider["title"] = provider.Title
+ rawProvider["name"] = provider.Name
+ rawProvider["type"] = provider.Type.String()
+ rawProvider["url"] = provider.Url
+
+ providers = append(providers, rawProvider)
+ }
+
+ if err := d.Set("vcs_providers", providers); err != nil {
+ return diag.FromErr(err)
+ }
+
+ // always refresh
+ d.SetId(strconv.FormatInt(time.Now().Unix(), 10))
+
+ return diags
+}
diff --git a/provider/internal/mock_client.go b/provider/internal/mock_client.go
index 4cf78d3..884f75e 100644
--- a/provider/internal/mock_client.go
+++ b/provider/internal/mock_client.go
@@ -504,6 +504,57 @@ func (c *mockClient) UpsertSetting(_ context.Context, upsert *v1pb.Setting, _ []
return c.settingMap[upsert.Name], nil
}
+// ParseExpression parse the expression string.
func (*mockClient) ParseExpression(_ context.Context, _ string) (*v1alpha1.Expr, error) {
return nil, nil
}
+
+// ListVCSProvider will returns all vcs providers.
+func (*mockClient) ListVCSProvider(_ context.Context) (*v1pb.ListVCSProvidersResponse, error) {
+ return nil, nil
+}
+
+// GetVCSProvider gets the vcs by id.
+func (*mockClient) GetVCSProvider(_ context.Context, _ string) (*v1pb.VCSProvider, error) {
+ return nil, nil
+}
+
+// CreateVCSProvider creates the vcs provider.
+func (*mockClient) CreateVCSProvider(_ context.Context, _ string, _ *v1pb.VCSProvider) (*v1pb.VCSProvider, error) {
+ return nil, nil
+}
+
+// UpdateVCSProvider updates the vcs provider.
+func (*mockClient) UpdateVCSProvider(_ context.Context, _ *v1pb.VCSProvider, _ []string) (*v1pb.VCSConnector, error) {
+ return nil, nil
+}
+
+// DeleteVCSProvider deletes the vcs provider.
+func (*mockClient) DeleteVCSProvider(_ context.Context, _ string) error {
+ return nil
+}
+
+// ListVCSConnector will returns all vcs connector in a project.
+func (*mockClient) ListVCSConnector(_ context.Context, _ string) (*v1pb.ListVCSConnectorsResponse, error) {
+ return nil, nil
+}
+
+// GetVCSConnector gets the vcs connector by id.
+func (*mockClient) GetVCSConnector(_ context.Context, _ string) (*v1pb.VCSConnector, error) {
+ return nil, nil
+}
+
+// CreateVCSConnector creates the vcs connector in a project.
+func (*mockClient) CreateVCSConnector(_ context.Context, _, _ string, _ *v1pb.VCSConnector) (*v1pb.VCSConnector, error) {
+ return nil, nil
+}
+
+// UpdateVCSConnector updates the vcs connector.
+func (*mockClient) UpdateVCSConnector(_ context.Context, _ *v1pb.VCSConnector, _ []string) (*v1pb.VCSConnector, error) {
+ return nil, nil
+}
+
+// DeleteVCSConnector deletes the vcs provider.
+func (*mockClient) DeleteVCSConnector(_ context.Context, _ string) error {
+ return nil
+}
diff --git a/provider/internal/utils.go b/provider/internal/utils.go
index 8a8a435..3e1e877 100644
--- a/provider/internal/utils.go
+++ b/provider/internal/utils.go
@@ -26,6 +26,12 @@ const (
PolicyNamePrefix = "policies/"
// SettingNamePrefix is the prefix for setting unique name.
SettingNamePrefix = "settings/"
+ // VCSProviderNamePrefix is the prefix for vcs provider unique name.
+ VCSProviderNamePrefix = "vcsProviders/"
+ // VCSConnectorNamePrefix is the prefix for vcs connector unique name.
+ VCSConnectorNamePrefix = "vcsConnectors/"
+ // UserNamePrefix is the prefix for user name.
+ UserNamePrefix = "users/"
// ResourceIDPattern is the pattern for resource id.
ResourceIDPattern = "[a-z]([a-z0-9-]{0,61}[a-z0-9])?"
)
@@ -92,6 +98,26 @@ func GetEnvironmentID(name string) (string, error) {
return tokens[0], nil
}
+// GetVCSProviderID will parse the vcs provider resource id.
+func GetVCSProviderID(name string) (string, error) {
+ // the vcs provider name should be vcsProviders/{resource-id}
+ tokens, err := getNameParentTokens(name, VCSProviderNamePrefix)
+ if err != nil {
+ return "", err
+ }
+ return tokens[0], nil
+}
+
+// GetVCSConnectorID will parse the vcs connector resource id.
+func GetVCSConnectorID(name string) (string, string, error) {
+ // the vcs connector name should be projects/{project}/vcsConnectors/{resource-id}
+ tokens, err := getNameParentTokens(name, ProjectNamePrefix, VCSConnectorNamePrefix)
+ if err != nil {
+ return "", "", err
+ }
+ return tokens[0], tokens[1], nil
+}
+
// GetInstanceID will parse the environment resource id and instance resource id.
func GetInstanceID(name string) (string, error) {
// the instance request should be instances/{instance-id}
diff --git a/provider/provider.go b/provider/provider.go
index d53005b..4abfe57 100644
--- a/provider/provider.go
+++ b/provider/provider.go
@@ -49,22 +49,28 @@ func NewProvider() *schema.Provider {
},
ConfigureContextFunc: providerConfigure,
DataSourcesMap: map[string]*schema.Resource{
- "bytebase_instance": dataSourceInstance(),
- "bytebase_instance_list": dataSourceInstanceList(),
- "bytebase_environment": dataSourceEnvironment(),
- "bytebase_environment_list": dataSourceEnvironmentList(),
- "bytebase_policy": dataSourcePolicy(),
- "bytebase_policy_list": dataSourcePolicyList(),
- "bytebase_project": dataSourceProject(),
- "bytebase_project_list": dataSourceProjectList(),
- "bytebase_setting": dataSourceSetting(),
+ "bytebase_instance": dataSourceInstance(),
+ "bytebase_instance_list": dataSourceInstanceList(),
+ "bytebase_environment": dataSourceEnvironment(),
+ "bytebase_environment_list": dataSourceEnvironmentList(),
+ "bytebase_policy": dataSourcePolicy(),
+ "bytebase_policy_list": dataSourcePolicyList(),
+ "bytebase_project": dataSourceProject(),
+ "bytebase_project_list": dataSourceProjectList(),
+ "bytebase_setting": dataSourceSetting(),
+ "bytebase_vcs_provider": dataSourceVCSProvider(),
+ "bytebase_vcs_provider_list": dataSourceVCSProviderList(),
+ "bytebase_vcs_connector": dataSourceVCSConnector(),
+ "bytebase_vcs_connector_list": dataSourceVCSConnectorList(),
},
ResourcesMap: map[string]*schema.Resource{
- "bytebase_environment": resourceEnvironment(),
- "bytebase_instance": resourceInstance(),
- "bytebase_policy": resourcePolicy(),
- "bytebase_project": resourceProjct(),
- "bytebase_setting": resourceSetting(),
+ "bytebase_environment": resourceEnvironment(),
+ "bytebase_instance": resourceInstance(),
+ "bytebase_policy": resourcePolicy(),
+ "bytebase_project": resourceProjct(),
+ "bytebase_setting": resourceSetting(),
+ "bytebase_vcs_provider": resourceVCSProvider(),
+ "bytebase_vcs_connector": resourceVCSConnector(),
},
}
}
diff --git a/provider/resource_environment.go b/provider/resource_environment.go
index 1e703a7..e783870 100644
--- a/provider/resource_environment.go
+++ b/provider/resource_environment.go
@@ -38,7 +38,7 @@ func resourceEnvironment() *schema.Resource {
"title": {
Type: schema.TypeString,
Required: true,
- Description: "The environment unique name.",
+ Description: "The environment title.",
ValidateFunc: validation.StringMatch(environmentTitleRegex, fmt.Sprintf("environment title must matches %v", environmentTitleRegex)),
},
"name": {
@@ -104,36 +104,47 @@ func resourceEnvironmentCreate(ctx context.Context, d *schema.ResourceData, m in
}
}
- env, err := c.UpdateEnvironment(ctx, &v1pb.Environment{
- Name: environmentName,
- Title: title,
- Order: int32(order),
- Tier: tier,
- }, []string{"title", "order", "tier"})
- if err != nil {
- diags = append(diags, diag.Diagnostic{
- Severity: diag.Error,
- Summary: "Failed to update environment",
- Detail: fmt.Sprintf("Update environment %s failed, error: %v", environmentName, err),
- })
- return diags
+ updateMasks := []string{}
+ if title != "" && title != existedEnv.Title {
+ updateMasks = append(updateMasks, "title")
+ }
+ if order != int(existedEnv.Order) {
+ updateMasks = append(updateMasks, "order")
+ }
+ if tier != existedEnv.Tier {
+ updateMasks = append(updateMasks, "tier")
}
- d.SetId(env.Name)
+ if len(updateMasks) > 0 {
+ if _, err := c.UpdateEnvironment(ctx, &v1pb.Environment{
+ Name: environmentName,
+ Title: title,
+ Order: int32(order),
+ Tier: tier,
+ State: v1pb.State_ACTIVE,
+ }, updateMasks); err != nil {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Failed to update environment",
+ Detail: fmt.Sprintf("Update environment %s failed, error: %v", environmentName, err),
+ })
+ return diags
+ }
+ }
} else {
- env, err := c.CreateEnvironment(ctx, environmentID, &v1pb.Environment{
+ if _, err := c.CreateEnvironment(ctx, environmentID, &v1pb.Environment{
Name: environmentName,
Title: title,
Order: int32(order),
Tier: tier,
- })
- if err != nil {
+ State: v1pb.State_ACTIVE,
+ }); err != nil {
return diag.FromErr(err)
}
-
- d.SetId(env.Name)
}
+ d.SetId(environmentName)
+
diag := resourceEnvironmentRead(ctx, d, m)
if diag != nil {
diags = append(diags, diag...)
@@ -197,17 +208,20 @@ func resourceEnvironmentUpdate(ctx context.Context, d *schema.ResourceData, m in
paths = append(paths, "tier")
}
- title := d.Get("title").(string)
- order := d.Get("order").(int)
- tier := v1pb.EnvironmentTier(v1pb.EnvironmentTier_value[d.Get("environment_tier_policy").(string)])
+ if len(paths) > 0 {
+ title := d.Get("title").(string)
+ order := d.Get("order").(int)
+ tier := v1pb.EnvironmentTier(v1pb.EnvironmentTier_value[d.Get("environment_tier_policy").(string)])
- if _, err := c.UpdateEnvironment(ctx, &v1pb.Environment{
- Name: environmentName,
- Title: title,
- Order: int32(order),
- Tier: tier,
- }, paths); err != nil {
- return diag.FromErr(err)
+ if _, err := c.UpdateEnvironment(ctx, &v1pb.Environment{
+ Name: environmentName,
+ Title: title,
+ Order: int32(order),
+ Tier: tier,
+ State: v1pb.State_ACTIVE,
+ }, paths); err != nil {
+ return diag.FromErr(err)
+ }
}
diag := resourceEnvironmentRead(ctx, d, m)
diff --git a/provider/resource_instance.go b/provider/resource_instance.go
index 948d77a..cbd2b91 100644
--- a/provider/resource_instance.go
+++ b/provider/resource_instance.go
@@ -175,6 +175,8 @@ func resourceInstanceCreate(ctx context.Context, d *schema.ResourceData, m inter
instanceID := d.Get("resource_id").(string)
instanceName := fmt.Sprintf("%s%s", internal.InstanceNamePrefix, instanceID)
+ title := d.Get("title").(string)
+ externalLink := d.Get("external_link").(string)
engineString := d.Get("engine").(string)
engineValue, ok := v1pb.Engine_value[engineString]
@@ -221,21 +223,32 @@ func resourceInstanceCreate(ctx context.Context, d *schema.ResourceData, m inter
}
}
- title := d.Get("title").(string)
- externalLink := d.Get("external_link").(string)
- if _, err := c.UpdateInstance(ctx, &v1pb.Instance{
- Name: instanceName,
- Title: title,
- ExternalLink: externalLink,
- DataSources: dataSourceList,
- State: existedInstance.State,
- }, []string{"title", "external_link", "data_sources"}); err != nil {
- diags = append(diags, diag.Diagnostic{
- Severity: diag.Error,
- Summary: "Failed to update instance",
- Detail: fmt.Sprintf("Update instance %s failed, error: %v", instanceName, err),
- })
- return diags
+ updateMasks := []string{}
+ if title != "" && title != existedInstance.Title {
+ updateMasks = append(updateMasks, "title")
+ }
+ if externalLink != "" && externalLink != existedInstance.ExternalLink {
+ updateMasks = append(updateMasks, "external_link")
+ }
+ if len(dataSourceList) > 0 {
+ updateMasks = append(updateMasks, "data_sources")
+ }
+
+ if len(updateMasks) > 0 {
+ if _, err := c.UpdateInstance(ctx, &v1pb.Instance{
+ Name: instanceName,
+ Title: title,
+ ExternalLink: externalLink,
+ DataSources: dataSourceList,
+ State: v1pb.State_ACTIVE,
+ }, updateMasks); err != nil {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Failed to update instance",
+ Detail: fmt.Sprintf("Update instance %s failed, error: %v", instanceName, err),
+ })
+ return diags
+ }
}
} else {
if _, err := c.CreateInstance(ctx, instanceID, &v1pb.Instance{
@@ -333,21 +346,23 @@ func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, m inter
paths = append(paths, "data_sources")
}
- if _, err := c.UpdateInstance(ctx, &v1pb.Instance{
- Name: instanceName,
- Title: d.Get("title").(string),
- ExternalLink: d.Get("external_link").(string),
- DataSources: dataSourceList,
- State: existedInstance.State,
- }, paths); err != nil {
- return diag.FromErr(err)
- }
- if err := c.SyncInstanceSchema(ctx, instanceName); err != nil {
- diags = append(diags, diag.Diagnostic{
- Severity: diag.Warning,
- Summary: "Instance schema sync failed",
- Detail: fmt.Sprintf("Failed to sync schema for instance %s with error: %v. You can try to trigger the sync manually via Bytebase UI.", instanceName, err.Error()),
- })
+ if len(paths) > 0 {
+ if _, err := c.UpdateInstance(ctx, &v1pb.Instance{
+ Name: instanceName,
+ Title: d.Get("title").(string),
+ ExternalLink: d.Get("external_link").(string),
+ DataSources: dataSourceList,
+ State: v1pb.State_ACTIVE,
+ }, paths); err != nil {
+ return diag.FromErr(err)
+ }
+ if err := c.SyncInstanceSchema(ctx, instanceName); err != nil {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Warning,
+ Summary: "Instance schema sync failed",
+ Detail: fmt.Sprintf("Failed to sync schema for instance %s with error: %v. You can try to trigger the sync manually via Bytebase UI.", instanceName, err.Error()),
+ })
+ }
}
diag := resourceInstanceRead(ctx, d, m)
diff --git a/provider/resource_policy.go b/provider/resource_policy.go
index 18ccffa..35b373e 100644
--- a/provider/resource_policy.go
+++ b/provider/resource_policy.go
@@ -209,13 +209,15 @@ func resourcePolicyUpdate(ctx context.Context, d *schema.ResourceData, m interfa
}
var diags diag.Diagnostics
- if _, err := c.UpsertPolicy(ctx, patch, updateMasks); err != nil {
- diags = append(diags, diag.Diagnostic{
- Severity: diag.Error,
- Summary: "Failed to upsert policy",
- Detail: fmt.Sprintf("Upsert policy %s failed, error: %v", policyName, err),
- })
- return diags
+ if len(updateMasks) > 0 {
+ if _, err := c.UpsertPolicy(ctx, patch, updateMasks); err != nil {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Failed to upsert policy",
+ Detail: fmt.Sprintf("Upsert policy %s failed, error: %v", policyName, err),
+ })
+ return diags
+ }
}
diag := resourcePolicyRead(ctx, d, m)
diff --git a/provider/resource_project.go b/provider/resource_project.go
index 5fd849b..7f46dd8 100644
--- a/provider/resource_project.go
+++ b/provider/resource_project.go
@@ -141,38 +141,44 @@ func resourceProjectCreate(ctx context.Context, d *schema.ResourceData, m interf
}
}
- project, err := c.UpdateProject(ctx, &v1pb.Project{
- Name: projectName,
- Title: title,
- Key: key,
- State: existedProject.State,
- Workflow: existedProject.Workflow,
- }, []string{"title", "key"})
- if err != nil {
- diags = append(diags, diag.Diagnostic{
- Severity: diag.Error,
- Summary: "Failed to update project",
- Detail: fmt.Sprintf("Update project %s failed, error: %v", projectName, err),
- })
- return diags
+ updateMasks := []string{}
+ if title != "" && title != existedProject.Title {
+ updateMasks = append(updateMasks, "title")
+ }
+ if key != "" && key != existedProject.Key {
+ updateMasks = append(updateMasks, "key")
}
- d.SetId(project.Name)
+ if len(updateMasks) > 0 {
+ if _, err := c.UpdateProject(ctx, &v1pb.Project{
+ Name: projectName,
+ Title: title,
+ Key: key,
+ State: v1pb.State_ACTIVE,
+ Workflow: existedProject.Workflow,
+ }, updateMasks); err != nil {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Failed to update project",
+ Detail: fmt.Sprintf("Update project %s failed, error: %v", projectName, err),
+ })
+ return diags
+ }
+ }
} else {
- project, err := c.CreateProject(ctx, projectID, &v1pb.Project{
+ if _, err := c.CreateProject(ctx, projectID, &v1pb.Project{
Name: projectName,
Title: title,
Key: key,
State: v1pb.State_ACTIVE,
Workflow: v1pb.Workflow_UI,
- })
- if err != nil {
+ }); err != nil {
return diag.FromErr(err)
}
-
- d.SetId(project.Name)
}
+ d.SetId(projectName)
+
if diag := updateDatabasesInProject(ctx, d, c, d.Id()); diag != nil {
diags = append(diags, diag...)
return diags
@@ -225,15 +231,17 @@ func resourceProjectUpdate(ctx context.Context, d *schema.ResourceData, m interf
paths = append(paths, "key")
}
- if _, err := c.UpdateProject(ctx, &v1pb.Project{
- Name: projectName,
- Title: d.Get("title").(string),
- Key: d.Get("key").(string),
- State: existedProject.State,
- Workflow: existedProject.Workflow,
- }, paths); err != nil {
- diags = append(diags, diag.FromErr(err)...)
- return diags
+ if len(paths) > 0 {
+ if _, err := c.UpdateProject(ctx, &v1pb.Project{
+ Name: projectName,
+ Title: d.Get("title").(string),
+ Key: d.Get("key").(string),
+ State: v1pb.State_ACTIVE,
+ Workflow: existedProject.Workflow,
+ }, paths); err != nil {
+ diags = append(diags, diag.FromErr(err)...)
+ return diags
+ }
}
if d.HasChange("databases") {
diff --git a/provider/resource_vcs_connector.go b/provider/resource_vcs_connector.go
new file mode 100644
index 0000000..2702225
--- /dev/null
+++ b/provider/resource_vcs_connector.go
@@ -0,0 +1,298 @@
+package provider
+
+import (
+ "context"
+ "fmt"
+ "regexp"
+
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "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 resourceVCSConnector() *schema.Resource {
+ return &schema.Resource{
+ Description: "The vcs connector resource.",
+ CreateContext: resourceVCSConnectorCreate,
+ ReadContext: resourceVCSConnectorRead,
+ UpdateContext: resourceVCSConnectorUpdate,
+ DeleteContext: resourceVCSConnectorDelete,
+ Importer: &schema.ResourceImporter{
+ StateContext: schema.ImportStatePassthroughContext,
+ },
+ Schema: map[string]*schema.Schema{
+ "resource_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: internal.ResourceIDValidation,
+ Description: "The vcs connector unique resource id.",
+ },
+ "project": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateDiagFunc: internal.ResourceNameValidation(regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.ProjectNamePrefix, internal.ResourceIDPattern))),
+ Description: "The project name in projects/{resource id} format.",
+ },
+ "name": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector full name in projects/{project}/vcsConnector/{resource id} format.",
+ },
+ "title": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: validation.StringIsNotEmpty,
+ Description: "The vcs connector title.",
+ },
+ "creator": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector creator in users/{email} format.",
+ },
+ "create_time": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector create time in YYYY-MM-DDThh:mm:ss.000Z format",
+ },
+ "updater": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector updater in users/{email} format.",
+ },
+ "update_time": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs connector update time in YYYY-MM-DDThh:mm:ss.000Z format",
+ },
+ "vcs_provider": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateDiagFunc: internal.ResourceNameValidation(regexp.MustCompile(fmt.Sprintf("^%s%s$", internal.VCSProviderNamePrefix, internal.ResourceIDPattern))),
+ Description: "The vcs provider full name in vcsProviders/{resource id} format.",
+ },
+ "database_group": {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: nil,
+ Description: "Apply changes to the database group.",
+ },
+ "repository_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: validation.StringIsNotEmpty,
+ Description: "The connected repository id in vcs provider.",
+ },
+ "repository_path": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: validation.StringIsNotEmpty,
+ Description: "The connected repository path in vcs provider.",
+ },
+ "repository_directory": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: validation.StringIsNotEmpty,
+ Description: "The connected repository directory in vcs provider.",
+ },
+ "repository_branch": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: validation.StringIsNotEmpty,
+ Description: "The connected repository branch in vcs provider.",
+ },
+ "repository_url": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: validation.StringIsNotEmpty,
+ Description: "The connected repository url in vcs provider.",
+ },
+ },
+ }
+}
+
+func resourceVCSConnectorRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ c := m.(api.Client)
+
+ fullName := d.Id()
+ connector, err := c.GetVCSConnector(ctx, fullName)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ return setVCSConnector(d, connector)
+}
+
+func resourceVCSConnectorDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ c := m.(api.Client)
+ fullName := d.Id()
+
+ // Warning or errors can be collected in a slice type
+ var diags diag.Diagnostics
+
+ if err := c.DeleteVCSConnector(ctx, fullName); err != nil {
+ return diag.FromErr(err)
+ }
+
+ d.SetId("")
+
+ return diags
+}
+
+func resourceVCSConnectorCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ c := m.(api.Client)
+
+ connectorID := d.Get("resource_id").(string)
+ projectName := d.Get("project").(string)
+ connectorName := fmt.Sprintf("%s/%s%s", projectName, internal.VCSConnectorNamePrefix, connectorID)
+
+ existedConnector, err := c.GetVCSConnector(ctx, connectorName)
+ if err != nil {
+ tflog.Debug(ctx, fmt.Sprintf("get vcs connector %s failed with error: %v", connectorName, err))
+ }
+
+ title := d.Get("title").(string)
+ vcsProviderName := d.Get("vcs_provider").(string)
+ databaseGroup := d.Get("database_group").(string)
+ repositoryID := d.Get("repository_id").(string)
+ repositoryPath := d.Get("repository_path").(string)
+ repositoryDirectory := d.Get("repository_directory").(string)
+ repositoryBranch := d.Get("repository_branch").(string)
+ repositoryURL := d.Get("repository_url").(string)
+
+ var diags diag.Diagnostics
+ if existedConnector != nil && err == nil {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Warning,
+ Summary: "VCS connector already exists",
+ Detail: fmt.Sprintf("VCS connector %s already exists, try to exec the update operation", connectorName),
+ })
+
+ updateMasks := []string{}
+ if repositoryBranch != "" && repositoryBranch != existedConnector.Branch {
+ updateMasks = append(updateMasks, "branch")
+ }
+ if repositoryDirectory != "" && repositoryDirectory != existedConnector.BaseDirectory {
+ updateMasks = append(updateMasks, "base_directory")
+ }
+ if databaseGroup != "" && databaseGroup != existedConnector.DatabaseGroup {
+ updateMasks = append(updateMasks, "database_group")
+ }
+
+ if len(updateMasks) > 0 {
+ if _, err := c.UpdateVCSConnector(ctx, &v1pb.VCSConnector{
+ Name: connectorName,
+ Title: title,
+ VcsProvider: existedConnector.VcsProvider,
+ DatabaseGroup: databaseGroup,
+ ExternalId: existedConnector.ExternalId,
+ FullPath: existedConnector.FullPath,
+ BaseDirectory: repositoryDirectory,
+ Branch: repositoryBranch,
+ WebUrl: existedConnector.WebUrl,
+ }, updateMasks); err != nil {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Failed to update vcs connector",
+ Detail: fmt.Sprintf("Update vcs connector %s failed, error: %v", connectorName, err),
+ })
+ return diags
+ }
+ }
+ } else {
+ if _, err := c.CreateVCSConnector(ctx, projectName, connectorID, &v1pb.VCSConnector{
+ Name: connectorName,
+ Title: title,
+ VcsProvider: vcsProviderName,
+ DatabaseGroup: databaseGroup,
+ ExternalId: repositoryID,
+ FullPath: repositoryPath,
+ BaseDirectory: repositoryDirectory,
+ Branch: repositoryBranch,
+ WebUrl: repositoryURL,
+ }); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+
+ d.SetId(connectorName)
+
+ diag := resourceVCSConnectorRead(ctx, d, m)
+ if diag != nil {
+ diags = append(diags, diag...)
+ }
+
+ return diags
+}
+
+func resourceVCSConnectorUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ if d.HasChange("resource_id") {
+ return diag.Errorf("cannot change the resource id")
+ }
+ if d.HasChange("project") {
+ return diag.Errorf("cannot change the project")
+ }
+ if d.HasChange("repository_id") {
+ return diag.Errorf("cannot change the repository_id")
+ }
+ if d.HasChange("repository_path") {
+ return diag.Errorf("cannot change the repository_path")
+ }
+ if d.HasChange("repository_url") {
+ return diag.Errorf("cannot change the repository_url")
+ }
+
+ c := m.(api.Client)
+ connectorName := d.Id()
+
+ existedConnector, err := c.GetVCSConnector(ctx, connectorName)
+ if err != nil {
+ tflog.Debug(ctx, fmt.Sprintf("get vcs connector %s failed with error: %v", connectorName, err))
+ return diag.FromErr(err)
+ }
+
+ paths := []string{}
+ if d.HasChange("database_group") {
+ paths = append(paths, "database_group")
+ }
+ if d.HasChange("repository_directory") {
+ paths = append(paths, "base_directory")
+ }
+ if d.HasChange("repository_branch") {
+ paths = append(paths, "branch")
+ }
+
+ var diags diag.Diagnostics
+ if len(paths) > 0 {
+ if _, err := c.UpdateVCSConnector(ctx, &v1pb.VCSConnector{
+ Name: connectorName,
+ Title: existedConnector.Title,
+ VcsProvider: existedConnector.VcsProvider,
+ DatabaseGroup: d.Get("database_group").(string),
+ ExternalId: existedConnector.ExternalId,
+ FullPath: existedConnector.FullPath,
+ BaseDirectory: d.Get("repository_directory").(string),
+ Branch: d.Get("repository_branch").(string),
+ WebUrl: existedConnector.WebUrl,
+ }, paths); err != nil {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Failed to update vcs connector",
+ Detail: fmt.Sprintf("Update vcs connector %s failed, error: %v", connectorName, err),
+ })
+ return diags
+ }
+ }
+
+ diag := resourceVCSConnectorRead(ctx, d, m)
+ if diag != nil {
+ diags = append(diags, diag...)
+ }
+
+ return diags
+}
diff --git a/provider/resource_vcs_provider.go b/provider/resource_vcs_provider.go
new file mode 100644
index 0000000..fd88f1b
--- /dev/null
+++ b/provider/resource_vcs_provider.go
@@ -0,0 +1,237 @@
+package provider
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "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 resourceVCSProvider() *schema.Resource {
+ return &schema.Resource{
+ Description: "The vcs provider resource.",
+ CreateContext: resourceVCSProviderCreate,
+ ReadContext: resourceVCSProviderRead,
+ UpdateContext: resourceVCSProviderUpdate,
+ DeleteContext: resourceVCSProviderDelete,
+ Importer: &schema.ResourceImporter{
+ StateContext: schema.ImportStatePassthroughContext,
+ },
+ Schema: map[string]*schema.Schema{
+ "resource_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: internal.ResourceIDValidation,
+ Description: "The vcs provider unique resource id.",
+ },
+ "title": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The vcs provider title.",
+ ValidateFunc: validation.StringIsNotEmpty,
+ },
+ "name": {
+ Type: schema.TypeString,
+ Computed: true,
+ Description: "The vcs provider full name in vcsProviders/{resource id} format.",
+ },
+ "url": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "The vcs provider url. You need to provide the url if you're using the self-host GitLab or self-host GitHub.",
+ },
+ "type": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The vcs provider type.",
+ ValidateFunc: validation.StringInSlice([]string{
+ v1pb.VCSType_GITHUB.String(),
+ v1pb.VCSType_GITLAB.String(),
+ v1pb.VCSType_BITBUCKET.String(),
+ v1pb.VCSType_AZURE_DEVOPS.String(),
+ }, false),
+ },
+ "access_token": {
+ Type: schema.TypeString,
+ Required: true,
+ Sensitive: true,
+ ValidateFunc: validation.StringIsNotEmpty,
+ Description: "The vcs provider token. Check the docs https://bytebase.cc/docs/vcs-integration/add-git-provider for details.",
+ },
+ },
+ }
+}
+
+func resourceVCSProviderRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ c := m.(api.Client)
+
+ fullName := d.Id()
+ provider, err := c.GetVCSProvider(ctx, fullName)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ return setVCSProvider(d, provider)
+}
+
+func resourceVCSProviderDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ c := m.(api.Client)
+ fullName := d.Id()
+
+ // Warning or errors can be collected in a slice type
+ var diags diag.Diagnostics
+
+ if err := c.DeleteVCSProvider(ctx, fullName); err != nil {
+ return diag.FromErr(err)
+ }
+
+ d.SetId("")
+
+ return diags
+}
+
+func resourceVCSProviderCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ c := m.(api.Client)
+
+ providerID := d.Get("resource_id").(string)
+ providerName := fmt.Sprintf("%s%s", internal.VCSProviderNamePrefix, providerID)
+
+ existedProvider, err := c.GetVCSProvider(ctx, providerName)
+ if err != nil {
+ tflog.Debug(ctx, fmt.Sprintf("get vcs provider %s failed with error: %v", providerName, err))
+ }
+
+ title := d.Get("title").(string)
+ url := d.Get("url").(string)
+ vcsType := v1pb.VCSType(v1pb.VCSType_value[d.Get("type").(string)])
+ token := d.Get("access_token").(string)
+
+ var diags diag.Diagnostics
+ if existedProvider != nil && err == nil {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Warning,
+ Summary: "VCS provider already exists",
+ Detail: fmt.Sprintf("VCS provider %s already exists, try to exec the update operation", providerName),
+ })
+
+ updateMasks := []string{}
+ if token != "" {
+ updateMasks = append(updateMasks, "access_token")
+ }
+ if title != "" && title != existedProvider.Title {
+ updateMasks = append(updateMasks, "title")
+ }
+
+ if len(updateMasks) > 0 {
+ if _, err := c.UpdateVCSProvider(ctx, &v1pb.VCSProvider{
+ Name: providerName,
+ Title: title,
+ Url: existedProvider.Url,
+ Type: existedProvider.Type,
+ AccessToken: token,
+ }, updateMasks); err != nil {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Failed to update vcs provider",
+ Detail: fmt.Sprintf("Update vcs provider %s failed, error: %v", providerName, err),
+ })
+ return diags
+ }
+ }
+ } else {
+ switch vcsType {
+ case v1pb.VCSType_GITHUB:
+ if url == "" {
+ url = "https://github.com"
+ }
+ case v1pb.VCSType_AZURE_DEVOPS:
+ url = "https://dev.azure.com"
+ case v1pb.VCSType_GITLAB:
+ if url == "" {
+ url = "https://gitlab.com"
+ }
+ case v1pb.VCSType_BITBUCKET:
+ url = "https://bitbucket.org"
+ }
+ if url == "" {
+ return diag.Errorf("missing url")
+ }
+ if _, err := c.CreateVCSProvider(ctx, providerID, &v1pb.VCSProvider{
+ Name: providerName,
+ Title: title,
+ Url: url,
+ Type: vcsType,
+ AccessToken: token,
+ }); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+
+ d.SetId(providerName)
+
+ diag := resourceVCSProviderRead(ctx, d, m)
+ if diag != nil {
+ diags = append(diags, diag...)
+ }
+
+ return diags
+}
+
+func resourceVCSProviderUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ if d.HasChange("resource_id") {
+ return diag.Errorf("cannot change the resource id")
+ }
+
+ c := m.(api.Client)
+ providerName := d.Id()
+
+ existedProvider, err := c.GetVCSProvider(ctx, providerName)
+ if err != nil {
+ tflog.Debug(ctx, fmt.Sprintf("get vcs provider %s failed with error: %v", providerName, err))
+ return diag.FromErr(err)
+ }
+
+ paths := []string{}
+ if d.HasChange("type") {
+ return diag.Errorf("cannot change the vcs provider type")
+ }
+ if d.HasChange("title") {
+ paths = append(paths, "title")
+ }
+ if d.HasChange("access_token") {
+ paths = append(paths, "access_token")
+ }
+
+ var diags diag.Diagnostics
+ if len(paths) > 0 {
+ if _, err := c.UpdateVCSProvider(ctx, &v1pb.VCSProvider{
+ Name: providerName,
+ Title: d.Get("title").(string),
+ Url: existedProvider.Url,
+ Type: existedProvider.Type,
+ AccessToken: d.Get("access_token").(string),
+ }, paths); err != nil {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Failed to update vcs provider",
+ Detail: fmt.Sprintf("Update vcs provider %s failed, error: %v", providerName, err),
+ })
+ return diags
+ }
+ }
+
+ diag := resourceVCSProviderRead(ctx, d, m)
+ if diag != nil {
+ diags = append(diags, diag...)
+ }
+
+ return diags
+}