Skip to content

Commit 8633841

Browse files
authored
Merge pull request #3 from bytebase/feat/api
feat: support instance resource
2 parents a5b8a5f + 5c10c13 commit 8633841

File tree

11 files changed

+505
-44
lines changed

11 files changed

+505
-44
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor
2+
examples/.terraform*
3+
examples/terraform.*

api/auth.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package api
22

3-
// AuthResponse is the API message for user login.
3+
// Login is the API message for user login.
4+
type Login struct {
5+
Email string `json:"email"`
6+
Password string `json:"password"`
7+
}
8+
9+
// AuthResponse is the API message for user login response.
410
type AuthResponse struct {
511
UserID int `json:"userId"`
612
Username string `json:"username"`

api/instance.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ type InstanceCreate struct {
3838
// InstancePatch is the API message for patching an instance.
3939
type InstancePatch struct {
4040
// Domain specific fields
41-
Name string `json:"name,omitempty"`
42-
ExternalLink string `json:"externalLink,omitempty"`
43-
Host string `json:"host,omitempty"`
44-
Port string `json:"port,omitempty"`
41+
Name *string `json:"name,omitempty"`
42+
ExternalLink *string `json:"externalLink,omitempty"`
43+
Host *string `json:"host,omitempty"`
44+
Port *string `json:"port,omitempty"`
4545
}
4646

4747
// HasChange returns if the patch struct has the value to update.
4848
func (p *InstancePatch) HasChange() bool {
49-
return p.Name != "" || p.ExternalLink != "" || p.Host != "" || p.Port != ""
49+
return p.Name != nil || p.ExternalLink != nil || p.Host != nil || p.Port != nil
5050
}

client/client.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,7 @@ type client struct {
1717
HostURL string
1818
HTTPClient *http.Client
1919
Token string
20-
Auth *authStruct
21-
}
22-
23-
type authStruct struct {
24-
Email string `json:"email"`
25-
Password string `json:"password"`
20+
Auth *api.Login
2621
}
2722

2823
// NewClient returns the new Bytebase API client.
@@ -32,7 +27,7 @@ func NewClient(url, email, password string) (api.Client, error) {
3227
HostURL: url,
3328
}
3429

35-
c.Auth = &authStruct{
30+
c.Auth = &api.Login{
3631
Email: email,
3732
Password: password,
3833
}

examples/main.tf

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
1-
# This is an example for using bytebase terraform provider to manage your resource.
1+
# This is an example for using Bytebase Terraform provider to manage your resource.
22
# To run this provider in your local machine,
3-
# 1. Run your bytebase service, then you can access the OpenAPI via http://localhost:8080/v1
4-
# 2. Replace the email and password with your account
3+
# 1. Run your Bytebase service, then you can access the OpenAPI via http://localhost:8080/v1
4+
# 2. Replace the email and password with your own Bytebase account
55
# 3. Run `make install` under terraform-provider-bytebase folder
6-
# 4. Run `terraform init` under terraform-provider-bytebase/examples folder
7-
# 5. Run terraform plan or terraform apply
6+
# 4. Run `cd examples && terraform init`
7+
# 5. Run `terraform plan` to check the changes
8+
# 6. Run `terraform apply` to apply the changes
9+
# 7. Run `terraform output` to find the outputs
10+
# 8. Run `terraform destory` to delete the test resources
811
terraform {
912
required_providers {
1013
bytebase = {
1114
version = "0.0.1"
12-
# The source is only used in this local example.
15+
# The source is only used in the local example.
1316
source = "registry.terraform.io/bytebase/bytebase"
1417
}
1518
}
1619
}
1720

1821
provider "bytebase" {
19-
# You need to replace the email and password with your own bytebase account.
22+
# You need to replace the email and password with your own Bytebase account.
2023
2124
password = "ed"
2225
bytebase_url = "http://localhost:8080/v1"
@@ -25,9 +28,31 @@ provider "bytebase" {
2528
# Create a new environment named "dev"
2629
resource "bytebase_environment" "dev" {
2730
name = "dev"
31+
# You can specific the environment order
32+
# order = 1
2833
}
2934

3035
# Print the new environment
3136
output "staging_environment" {
3237
value = bytebase_environment.dev
3338
}
39+
40+
# Create a new instance named "dev instance"
41+
resource "bytebase_instance" "dev_instance" {
42+
name = "dev instance"
43+
engine = "POSTGRES"
44+
host = "127.0.0.1"
45+
environment = bytebase_environment.dev.name
46+
# You can also provide the port, username, password
47+
# port = 5432
48+
# username = "username"
49+
# password = "password"
50+
}
51+
52+
# Print the new instance
53+
output "dev_instance" {
54+
value = bytebase_instance.dev_instance
55+
# The password in instance is sensitive, so you cannot directly get its value from the output.
56+
# But we can still print the instance via `terraform output -json dev_instance`
57+
sensitive = true
58+
}

provider/internal/mock_client.go

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,15 @@ func (c *mockClient) UpdateEnvironment(environmentID int, patch *api.Environment
7676
}
7777

7878
if v := patch.Name; v != nil {
79+
if existed := c.findEnvironmentByName(*v); existed != nil {
80+
return nil, errors.Errorf("Environment %s already exists", env.Name)
81+
}
7982
env.Name = *v
8083
}
8184
if v := patch.Order; v != nil {
8285
env.Order = *v
8386
}
8487

85-
if existed := c.findEnvironmentByName(env.Name); existed != nil {
86-
return nil, errors.Errorf("Environment %s already exists", env.Name)
87-
}
88-
8988
delete(c.environmentMap, env.ID)
9089
c.environmentMap[env.ID] = env
9190

@@ -113,21 +112,58 @@ func (*mockClient) ListInstance() ([]*api.Instance, error) {
113112
}
114113

115114
// CreateInstance creates the instance.
116-
func (*mockClient) CreateInstance(_ *api.InstanceCreate) (*api.Instance, error) {
117-
return nil, errors.Errorf("CreateInstance is not implemented yet")
115+
func (c *mockClient) CreateInstance(create *api.InstanceCreate) (*api.Instance, error) {
116+
ins := &api.Instance{
117+
ID: len(c.instanceMap) + 1,
118+
Environment: create.Environment,
119+
Name: create.Name,
120+
Engine: create.Engine,
121+
ExternalLink: create.ExternalLink,
122+
Host: create.Host,
123+
Port: create.Port,
124+
Username: create.Username,
125+
}
126+
127+
c.instanceMap[ins.ID] = ins
128+
return ins, nil
118129
}
119130

120131
// GetInstance gets the instance by id.
121-
func (*mockClient) GetInstance(_ int) (*api.Instance, error) {
122-
return nil, errors.Errorf("GetInstance is not implemented yet")
132+
func (c *mockClient) GetInstance(instanceID int) (*api.Instance, error) {
133+
ins, ok := c.instanceMap[instanceID]
134+
if !ok {
135+
return nil, errors.Errorf("Cannot found instance with ID %d", instanceID)
136+
}
137+
138+
return ins, nil
123139
}
124140

125141
// UpdateInstance updates the instance.
126-
func (*mockClient) UpdateInstance(_ int, _ *api.InstancePatch) (*api.Instance, error) {
127-
return nil, errors.Errorf("UpdateInstance is not implemented yet")
142+
func (c *mockClient) UpdateInstance(instanceID int, patch *api.InstancePatch) (*api.Instance, error) {
143+
ins, err := c.GetInstance(instanceID)
144+
if err != nil {
145+
return nil, err
146+
}
147+
148+
if v := patch.Name; v != nil {
149+
ins.Name = *v
150+
}
151+
if v := patch.ExternalLink; v != nil {
152+
ins.ExternalLink = *v
153+
}
154+
if v := patch.Host; v != nil {
155+
ins.Host = *v
156+
}
157+
if v := patch.Port; v != nil {
158+
ins.Port = *v
159+
}
160+
161+
c.instanceMap[instanceID] = ins
162+
return ins, nil
128163
}
129164

130165
// DeleteInstance deletes the instance.
131-
func (*mockClient) DeleteInstance(_ int) error {
132-
return errors.Errorf("DeleteInstance is not implemented yet")
166+
func (c *mockClient) DeleteInstance(instanceID int) error {
167+
delete(c.instanceMap, instanceID)
168+
return nil
133169
}

provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ func NewProvider() *schema.Provider {
3535
DataSourcesMap: map[string]*schema.Resource{},
3636
ResourcesMap: map[string]*schema.Resource{
3737
"bytebase_environment": resourceEnvironment(),
38+
"bytebase_instance": resourceInstance(),
3839
},
3940
}
4041
}

provider/resource_environment.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
88
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
910

1011
"github.com/bytebase/terraform-provider-bytebase/api"
1112
)
@@ -21,14 +22,17 @@ func resourceEnvironment() *schema.Resource {
2122
},
2223
Schema: map[string]*schema.Schema{
2324
"name": {
24-
Type: schema.TypeString,
25-
Required: true,
25+
Type: schema.TypeString,
26+
Required: true,
27+
Description: "The environment unique name.",
28+
ValidateFunc: validation.StringIsNotEmpty,
2629
},
2730
"order": {
28-
Type: schema.TypeInt,
29-
Optional: true,
30-
Computed: true,
31-
Default: nil,
31+
Type: schema.TypeInt,
32+
Optional: true,
33+
Computed: true,
34+
Default: nil,
35+
Description: "The environment sorting order.",
3236
},
3337
},
3438
}

provider/resource_environment_test.go

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package provider
22

33
import (
44
"fmt"
5+
"regexp"
56
"strconv"
67
"testing"
78

@@ -13,7 +14,12 @@ import (
1314
)
1415

1516
func TestAccEnvironment(t *testing.T) {
16-
envName := "dev"
17+
identifier := "new_environment"
18+
resourceName := fmt.Sprintf("bytebase_environment.%s", identifier)
19+
20+
name := "dev"
21+
order := 1
22+
nameUpdated := fmt.Sprintf("%s-updated", name)
1723

1824
resource.Test(t, resource.TestCase{
1925
PreCheck: func() {
@@ -22,16 +28,47 @@ func TestAccEnvironment(t *testing.T) {
2228
Providers: testAccProviders,
2329
CheckDestroy: testAccCheckEnvironmentDestroy,
2430
Steps: []resource.TestStep{
31+
// resource create
32+
{
33+
Config: testAccCheckEnvironmentConfigBasic(identifier, name, order),
34+
Check: resource.ComposeTestCheckFunc(
35+
testAccCheckEnvironmentExists(resourceName),
36+
resource.TestCheckResourceAttr(resourceName, "name", name),
37+
resource.TestCheckResourceAttr(resourceName, "order", fmt.Sprintf("%d", order)),
38+
),
39+
},
40+
// resource update
2541
{
26-
Config: testAccCheckEnvironmentConfigBasic(envName),
42+
Config: testAccCheckEnvironmentConfigBasic(identifier, nameUpdated, order+1),
2743
Check: resource.ComposeTestCheckFunc(
28-
testAccCheckEnvironmentExists("bytebase_environment.new"),
44+
testAccCheckEnvironmentExists(resourceName),
45+
resource.TestCheckResourceAttr(resourceName, "name", nameUpdated),
46+
resource.TestCheckResourceAttr(resourceName, "order", fmt.Sprintf("%d", order+1)),
2947
),
3048
},
3149
},
3250
})
3351
}
3452

53+
func TestAccEnvironment_InvalidInput(t *testing.T) {
54+
identifier := "another_environment"
55+
56+
resource.Test(t, resource.TestCase{
57+
PreCheck: func() {
58+
testAccPreCheck(t)
59+
},
60+
Providers: testAccProviders,
61+
CheckDestroy: testAccCheckEnvironmentDestroy,
62+
Steps: []resource.TestStep{
63+
// Invalid environment name
64+
{
65+
Config: testAccCheckEnvironmentConfigBasic(identifier, "", 0),
66+
ExpectError: regexp.MustCompile("not be an empty string"),
67+
},
68+
},
69+
})
70+
}
71+
3572
func testAccCheckEnvironmentDestroy(s *terraform.State) error {
3673
c := testAccProvider.Meta().(api.Client)
3774

@@ -53,12 +90,13 @@ func testAccCheckEnvironmentDestroy(s *terraform.State) error {
5390
return nil
5491
}
5592

56-
func testAccCheckEnvironmentConfigBasic(envName string) string {
93+
func testAccCheckEnvironmentConfigBasic(identifier, envName string, order int) string {
5794
return fmt.Sprintf(`
58-
resource "bytebase_environment" "new" {
95+
resource "bytebase_environment" "%s" {
5996
name = "%s"
97+
order = %d
6098
}
61-
`, envName)
99+
`, identifier, envName, order)
62100
}
63101

64102
func testAccCheckEnvironmentExists(n string) resource.TestCheckFunc {

0 commit comments

Comments
 (0)