Skip to content

Commit a8ea6a4

Browse files
committed
chore: support instance resource
1 parent a5b8a5f commit a8ea6a4

File tree

5 files changed

+282
-20
lines changed

5 files changed

+282
-20
lines changed

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
}

examples/main.tf

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
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 destory` to delete the test resources
810
terraform {
911
required_providers {
1012
bytebase = {
1113
version = "0.0.1"
12-
# The source is only used in this local example.
14+
# The source is only used in the local example.
1315
source = "registry.terraform.io/bytebase/bytebase"
1416
}
1517
}
1618
}
1719

1820
provider "bytebase" {
19-
# You need to replace the email and password with your own bytebase account.
21+
# You need to replace the email and password with your own Bytebase account.
2022
2123
password = "ed"
2224
bytebase_url = "http://localhost:8080/v1"
@@ -25,9 +27,23 @@ provider "bytebase" {
2527
# Create a new environment named "dev"
2628
resource "bytebase_environment" "dev" {
2729
name = "dev"
30+
# You can specific the environment order
31+
# order = 1
2832
}
2933

3034
# Print the new environment
3135
output "staging_environment" {
3236
value = bytebase_environment.dev
3337
}
38+
39+
# Create a new instance named "dev instance"
40+
resource "bytebase_instance" "dev_instance" {
41+
name = "dev instance"
42+
engine = "POSTGRES"
43+
host = "127.0.0.1"
44+
environment = bytebase_environment.dev.name
45+
# You can also provide the port, username, password
46+
# port = 5432
47+
# username = "username"
48+
# password = "password"
49+
}

provider/internal/mock_client.go

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,21 +113,58 @@ func (*mockClient) ListInstance() ([]*api.Instance, error) {
113113
}
114114

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

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

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

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

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_instance.go

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package provider
2+
3+
import (
4+
"context"
5+
"strconv"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
10+
"github.com/bytebase/terraform-provider-bytebase/api"
11+
)
12+
13+
func resourceInstance() *schema.Resource {
14+
return &schema.Resource{
15+
CreateContext: resourceInstanceCreate,
16+
ReadContext: resourceInstanceRead,
17+
UpdateContext: resourceInstanceUpdate,
18+
DeleteContext: resourceInstanceDelete,
19+
Importer: &schema.ResourceImporter{
20+
StateContext: schema.ImportStatePassthroughContext,
21+
},
22+
Schema: map[string]*schema.Schema{
23+
"name": {
24+
Type: schema.TypeString,
25+
Required: true,
26+
},
27+
"engine": {
28+
// TODO: validation
29+
Type: schema.TypeString,
30+
Required: true,
31+
},
32+
"engine_version": {
33+
Type: schema.TypeString,
34+
Computed: true,
35+
},
36+
"external_link": {
37+
Type: schema.TypeString,
38+
Optional: true,
39+
Computed: true,
40+
},
41+
"host": {
42+
Type: schema.TypeString,
43+
Required: true,
44+
},
45+
"port": {
46+
Type: schema.TypeString,
47+
Optional: true,
48+
},
49+
"username": {
50+
Type: schema.TypeString,
51+
Optional: true,
52+
Computed: false,
53+
},
54+
"password": {
55+
Type: schema.TypeString,
56+
Optional: true,
57+
Computed: false,
58+
Sensitive: true,
59+
},
60+
"ssl_ca": {
61+
Type: schema.TypeString,
62+
Optional: true,
63+
},
64+
"ssl_cert": {
65+
Type: schema.TypeString,
66+
Optional: true,
67+
},
68+
"ssl_key": {
69+
Type: schema.TypeString,
70+
Optional: true,
71+
},
72+
"environment": {
73+
Type: schema.TypeString,
74+
Required: true,
75+
},
76+
},
77+
}
78+
}
79+
80+
func resourceInstanceCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
81+
c := m.(api.Client)
82+
83+
instance, err := c.CreateInstance(&api.InstanceCreate{
84+
Environment: d.Get("environment").(string),
85+
Name: d.Get("name").(string),
86+
Engine: d.Get("engine").(string),
87+
ExternalLink: d.Get("external_link").(string),
88+
Host: d.Get("host").(string),
89+
Port: d.Get("port").(string),
90+
Username: d.Get("username").(string),
91+
Password: d.Get("password").(string),
92+
SslCa: d.Get("ssl_ca").(string),
93+
SslCert: d.Get("ssl_cert").(string),
94+
SslKey: d.Get("ssl_key").(string),
95+
})
96+
if err != nil {
97+
return diag.FromErr(err)
98+
}
99+
100+
d.SetId(strconv.Itoa(instance.ID))
101+
102+
return resourceInstanceRead(ctx, d, m)
103+
}
104+
105+
func resourceInstanceRead(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
106+
c := m.(api.Client)
107+
108+
instanceID, err := strconv.Atoi(d.Id())
109+
if err != nil {
110+
return diag.FromErr(err)
111+
}
112+
113+
instance, err := c.GetInstance(instanceID)
114+
if err != nil {
115+
return diag.FromErr(err)
116+
}
117+
118+
return setInstance(d, instance)
119+
}
120+
121+
func resourceInstanceUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
122+
c := m.(api.Client)
123+
124+
instanceID, err := strconv.Atoi(d.Id())
125+
if err != nil {
126+
return diag.FromErr(err)
127+
}
128+
129+
patch := &api.InstancePatch{}
130+
if d.HasChange("name") {
131+
if name, ok := d.GetOk("name"); ok {
132+
val := name.(string)
133+
patch.Name = &val
134+
}
135+
}
136+
if d.HasChange("external_link") {
137+
if link, ok := d.GetOk("external_link"); ok {
138+
val := link.(string)
139+
patch.ExternalLink = &val
140+
}
141+
}
142+
if d.HasChange("host") {
143+
if host, ok := d.GetOk("host"); ok {
144+
val := host.(string)
145+
patch.Host = &val
146+
}
147+
}
148+
if d.HasChange("port") {
149+
if port, ok := d.GetOk("port"); ok {
150+
val := port.(string)
151+
patch.Port = &val
152+
}
153+
}
154+
155+
if patch.HasChange() {
156+
if _, err := c.UpdateInstance(instanceID, patch); err != nil {
157+
return diag.FromErr(err)
158+
}
159+
}
160+
161+
return resourceInstanceRead(ctx, d, m)
162+
}
163+
164+
func resourceInstanceDelete(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
165+
c := m.(api.Client)
166+
167+
// Warning or errors can be collected in a slice type
168+
var diags diag.Diagnostics
169+
170+
instanceID, err := strconv.Atoi(d.Id())
171+
if err != nil {
172+
return diag.FromErr(err)
173+
}
174+
175+
if err := c.DeleteInstance(instanceID); err != nil {
176+
return diag.FromErr(err)
177+
}
178+
179+
d.SetId("")
180+
181+
return diags
182+
}
183+
184+
func setInstance(d *schema.ResourceData, instance *api.Instance) diag.Diagnostics {
185+
if err := d.Set("name", instance.Name); err != nil {
186+
return diag.Errorf("cannot set name for instance: %s", err.Error())
187+
}
188+
if err := d.Set("engine", instance.Engine); err != nil {
189+
return diag.Errorf("cannot set engine for instance: %s", err.Error())
190+
}
191+
if err := d.Set("engine_version", instance.EngineVersion); err != nil {
192+
return diag.Errorf("cannot set engine_version for instance: %s", err.Error())
193+
}
194+
if err := d.Set("external_link", instance.ExternalLink); err != nil {
195+
return diag.Errorf("cannot set external_link for instance: %s", err.Error())
196+
}
197+
if err := d.Set("host", instance.Host); err != nil {
198+
return diag.Errorf("cannot set host for instance: %s", err.Error())
199+
}
200+
if err := d.Set("port", instance.Port); err != nil {
201+
return diag.Errorf("cannot set port for instance: %s", err.Error())
202+
}
203+
if err := d.Set("environment", instance.Environment); err != nil {
204+
return diag.Errorf("cannot set environment for instance: %s", err.Error())
205+
}
206+
207+
return nil
208+
}

0 commit comments

Comments
 (0)