Skip to content

Commit 45fc923

Browse files
author
Phani Raj
authored
Merge pull request #86 from planetscale/add-passwords
Add module to manage passwords for a database branch.
2 parents 244d21c + b4fe200 commit 45fc923

File tree

3 files changed

+340
-0
lines changed

3 files changed

+340
-0
lines changed

Diff for: planetscale/client.go

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type Client struct {
4444
Certificates CertificatesService
4545
DatabaseBranches DatabaseBranchesService
4646
Organizations OrganizationsService
47+
Passwords PasswordsService
4748
Regions RegionsService
4849
DeployRequests DeployRequestsService
4950
ServiceTokens ServiceTokenService
@@ -138,6 +139,7 @@ func NewClient(opts ...ClientOption) (*Client, error) {
138139
c.Certificates = &certificatesService{client: c}
139140
c.DatabaseBranches = &databaseBranchesService{client: c}
140141
c.Organizations = &organizationsService{client: c}
142+
c.Passwords = &passwordsService{client: c}
141143
c.Regions = &regionsService{client: c}
142144
c.DeployRequests = &deployRequestsService{client: c}
143145
c.ServiceTokens = &serviceTokenService{client: c}

Diff for: planetscale/passwords.go

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package planetscale
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"time"
8+
9+
"github.com/pkg/errors"
10+
)
11+
12+
type ConnectionStrings struct {
13+
DotNet string `json:"dotnet"`
14+
General string `json:"general"`
15+
MySQLCLI string `json:"mysqlcli"`
16+
PHP string `json:"php"`
17+
Prisma string `json:"prisma"`
18+
Rails string `json:"rails"`
19+
Go string `json:"go"`
20+
Java string `json:"java"`
21+
Rust string `json:"rust"`
22+
}
23+
24+
type DatabaseBranchPassword struct {
25+
PublicID string `json:"id"`
26+
Name string `json:"display_name"`
27+
UserName string `json:"username"`
28+
Role string `json:"role"`
29+
Branch DatabaseBranch `json:"database_branch"`
30+
CreatedAt time.Time `json:"created_at"`
31+
DeletedAt time.Time `json:"deleted_at"`
32+
PlainText string `json:"plain_text"`
33+
ConnectionStrings ConnectionStrings `json:"connection_strings"`
34+
}
35+
36+
// DatabaseBranchPasswordRequest encapsulates the request for creating/getting/deleting a
37+
// database branch password.
38+
type DatabaseBranchPasswordRequest struct {
39+
Organization string `json:"-"`
40+
Database string `json:"-"`
41+
Branch string `json:"-"`
42+
DisplayName string `json:"display_name"`
43+
}
44+
45+
// ListDatabaseBranchPasswordRequest encapsulates the request for listing all passwords
46+
// for a given database branch.
47+
type ListDatabaseBranchPasswordRequest struct {
48+
Organization string
49+
Database string
50+
Branch string
51+
}
52+
53+
// GetDatabaseBranchPasswordRequest encapsulates the request for listing all passwords
54+
// for a given database branch.
55+
type GetDatabaseBranchPasswordRequest struct {
56+
Organization string `json:"-"`
57+
Database string `json:"-"`
58+
Branch string `json:"-"`
59+
DisplayName string `json:"display_name"`
60+
PasswordId string
61+
}
62+
63+
// DeleteDatabaseBranchPasswordRequest encapsulates the request for deleting a password
64+
// for a given database branch.
65+
type DeleteDatabaseBranchPasswordRequest struct {
66+
Organization string `json:"-"`
67+
Database string `json:"-"`
68+
Branch string `json:"-"`
69+
DisplayName string `json:"display_name"`
70+
PasswordId string
71+
}
72+
73+
// DatabaseBranchPasswordsService is an interface for communicating with the PlanetScale
74+
// Database Branch Passwords API endpoint.
75+
type PasswordsService interface {
76+
Create(context.Context, *DatabaseBranchPasswordRequest) (*DatabaseBranchPassword, error)
77+
List(context.Context, *ListDatabaseBranchPasswordRequest) ([]*DatabaseBranchPassword, error)
78+
Get(context.Context, *GetDatabaseBranchPasswordRequest) (*DatabaseBranchPassword, error)
79+
Delete(context.Context, *DeleteDatabaseBranchPasswordRequest) error
80+
}
81+
82+
type passwordsService struct {
83+
client *Client
84+
}
85+
86+
type passwordsResponse struct {
87+
Passwords []*DatabaseBranchPassword `json:"data"`
88+
}
89+
90+
var _ PasswordsService = &passwordsService{}
91+
92+
func NewPasswordsService(client *Client) *passwordsService {
93+
return &passwordsService{
94+
client: client,
95+
}
96+
}
97+
98+
// Creates a new password for a branch.
99+
func (d *passwordsService) Create(ctx context.Context, createReq *DatabaseBranchPasswordRequest) (*DatabaseBranchPassword, error) {
100+
path := passwordsAPIPath(createReq.Organization, createReq.Database, createReq.Branch)
101+
req, err := d.client.newRequest(http.MethodPost, path, createReq)
102+
if err != nil {
103+
return nil, errors.Wrap(err, "error creating http request")
104+
}
105+
106+
password := &DatabaseBranchPassword{}
107+
if err := d.client.do(ctx, req, &password); err != nil {
108+
return nil, err
109+
}
110+
111+
return password, nil
112+
}
113+
114+
// Delete an existing password for a branch.
115+
func (d *passwordsService) Delete(ctx context.Context, deleteReq *DeleteDatabaseBranchPasswordRequest) error {
116+
path := passwordAPIPath(deleteReq.Organization, deleteReq.Database, deleteReq.Branch, deleteReq.PasswordId)
117+
req, err := d.client.newRequest(http.MethodDelete, path, nil)
118+
if err != nil {
119+
return errors.Wrap(err, "error creating http request")
120+
}
121+
122+
err = d.client.do(ctx, req, nil)
123+
return err
124+
125+
}
126+
127+
// Get an existing password for a branch.
128+
func (d *passwordsService) Get(ctx context.Context, getReq *GetDatabaseBranchPasswordRequest) (*DatabaseBranchPassword, error) {
129+
path := passwordAPIPath(getReq.Organization, getReq.Database, getReq.Branch, getReq.PasswordId)
130+
req, err := d.client.newRequest(http.MethodGet, path, nil)
131+
if err != nil {
132+
return nil, errors.Wrap(err, "error creating http request")
133+
}
134+
135+
password := &DatabaseBranchPassword{}
136+
if err := d.client.do(ctx, req, &password); err != nil {
137+
return nil, err
138+
}
139+
140+
return password, nil
141+
142+
}
143+
144+
// List all existing passwords for a branch.
145+
func (d *passwordsService) List(ctx context.Context, listReq *ListDatabaseBranchPasswordRequest) ([]*DatabaseBranchPassword, error) {
146+
req, err := d.client.newRequest(http.MethodGet, passwordsAPIPath(listReq.Organization, listReq.Database, listReq.Branch), nil)
147+
if err != nil {
148+
return nil, errors.Wrap(err, "error creating http request to list passwords")
149+
}
150+
151+
passwordsResp := &passwordsResponse{}
152+
if err := d.client.do(ctx, req, &passwordsResp); err != nil {
153+
return nil, err
154+
}
155+
156+
return passwordsResp.Passwords, nil
157+
}
158+
159+
func passwordAPIPath(org, db, branch, password string) string {
160+
return fmt.Sprintf("%s/%s", passwordsAPIPath(org, db, branch), password)
161+
}
162+
163+
func passwordsAPIPath(org, db, branch string) string {
164+
return fmt.Sprintf("%s/passwords", databaseBranchAPIPath(org, db, branch))
165+
}

Diff for: planetscale/passwords_test.go

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package planetscale
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"net/http/httptest"
8+
"testing"
9+
"time"
10+
11+
qt "github.com/frankban/quicktest"
12+
)
13+
14+
const testPasswordID = "planetscale-go-test-password"
15+
16+
func TestPasswords_Create(t *testing.T) {
17+
c := qt.New(t)
18+
plainText := "plain-text-password"
19+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
20+
w.WriteHeader(200)
21+
out := fmt.Sprintf(`{
22+
"id": "%s",
23+
"username": "%s",
24+
"role": "writer",
25+
"plain_text": "%s",
26+
"display_name": "planetscale-go-test-password",
27+
"created_at": "2021-01-14T10:19:23.000Z"
28+
}`, testPasswordID, testPasswordID, plainText)
29+
_, err := w.Write([]byte(out))
30+
c.Assert(err, qt.IsNil)
31+
}))
32+
33+
client, err := NewClient(WithBaseURL(ts.URL))
34+
c.Assert(err, qt.IsNil)
35+
36+
ctx := context.Background()
37+
org := "my-org"
38+
db := "my-db"
39+
branch := "my-branch"
40+
41+
password, err := client.Passwords.Create(ctx, &DatabaseBranchPasswordRequest{
42+
Organization: org,
43+
Database: db,
44+
Branch: branch,
45+
})
46+
47+
want := &DatabaseBranchPassword{
48+
Name: testPasswordID,
49+
PublicID: testPasswordID,
50+
UserName: testPasswordID,
51+
52+
CreatedAt: time.Date(2021, time.January, 14, 10, 19, 23, 000, time.UTC),
53+
Role: "writer",
54+
PlainText: plainText,
55+
}
56+
57+
c.Assert(err, qt.IsNil)
58+
c.Assert(password, qt.DeepEquals, want)
59+
}
60+
61+
func TestPasswords_List(t *testing.T) {
62+
c := qt.New(t)
63+
64+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
65+
w.WriteHeader(200)
66+
out := `{
67+
"data":
68+
[
69+
{
70+
"id": "planetscale-go-test-password",
71+
"display_name": "planetscale-go-test-password",
72+
"created_at": "2021-01-14T10:19:23.000Z"
73+
}
74+
]
75+
}`
76+
_, err := w.Write([]byte(out))
77+
c.Assert(err, qt.IsNil)
78+
}))
79+
80+
client, err := NewClient(WithBaseURL(ts.URL))
81+
c.Assert(err, qt.IsNil)
82+
83+
ctx := context.Background()
84+
org := "my-org"
85+
db := "planetscale-go-test-db"
86+
branch := "my-branch"
87+
88+
passwords, err := client.Passwords.List(ctx, &ListDatabaseBranchPasswordRequest{
89+
Organization: org,
90+
Database: db,
91+
Branch: branch,
92+
})
93+
94+
want := []*DatabaseBranchPassword{{
95+
Name: testPasswordID,
96+
PublicID: testPasswordID,
97+
CreatedAt: time.Date(2021, time.January, 14, 10, 19, 23, 000, time.UTC),
98+
}}
99+
100+
c.Assert(err, qt.IsNil)
101+
c.Assert(passwords, qt.DeepEquals, want)
102+
}
103+
104+
func TestPasswords_ListEmpty(t *testing.T) {
105+
c := qt.New(t)
106+
107+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
108+
w.WriteHeader(200)
109+
out := `{"data":[]}`
110+
_, err := w.Write([]byte(out))
111+
c.Assert(err, qt.IsNil)
112+
}))
113+
114+
client, err := NewClient(WithBaseURL(ts.URL))
115+
c.Assert(err, qt.IsNil)
116+
117+
ctx := context.Background()
118+
org := "my-org"
119+
db := "planetscale-go-test-db"
120+
branch := "my-branch"
121+
122+
passwords, err := client.Passwords.List(ctx, &ListDatabaseBranchPasswordRequest{
123+
Organization: org,
124+
Database: db,
125+
Branch: branch,
126+
})
127+
128+
c.Assert(err, qt.IsNil)
129+
c.Assert(passwords, qt.HasLen, 0)
130+
}
131+
132+
func TestPasswords_Get(t *testing.T) {
133+
c := qt.New(t)
134+
135+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
136+
w.WriteHeader(200)
137+
out := fmt.Sprintf(`{
138+
"id": "%s",
139+
"username": "%s",
140+
"role": "writer",
141+
"display_name": "planetscale-go-test-password",
142+
"created_at": "2021-01-14T10:19:23.000Z"
143+
}`, testPasswordID, testPasswordID)
144+
_, err := w.Write([]byte(out))
145+
c.Assert(err, qt.IsNil)
146+
}))
147+
148+
client, err := NewClient(WithBaseURL(ts.URL))
149+
c.Assert(err, qt.IsNil)
150+
151+
ctx := context.Background()
152+
org := "my-org"
153+
db := "planetscale-go-test-db"
154+
branch := "my-branch"
155+
156+
password, err := client.Passwords.Get(ctx, &GetDatabaseBranchPasswordRequest{
157+
Organization: org,
158+
Database: db,
159+
Branch: branch,
160+
PasswordId: testPasswordID,
161+
})
162+
163+
want := &DatabaseBranchPassword{
164+
Name: testPasswordID,
165+
UserName: testPasswordID,
166+
PublicID: testPasswordID,
167+
CreatedAt: time.Date(2021, time.January, 14, 10, 19, 23, 000, time.UTC),
168+
Role: "writer",
169+
}
170+
171+
c.Assert(err, qt.IsNil)
172+
c.Assert(password, qt.DeepEquals, want)
173+
}

0 commit comments

Comments
 (0)