Skip to content

Commit 29a46b0

Browse files
rak16looslaandrewsomething
authored
DOCR-1201: Add new RegistriesService to support methods for multiple-registry open beta (#730)
* docr: Update interface to add methods for multi-registry open beta * docr: Add TotalStorageUsageBytes field to registriesRoot struct * docr: Create new Registries service for multiple-registry endpoints * docr: Fix indentation --------- Co-authored-by: Anna Lushnikova <[email protected]> Co-authored-by: Andrew Starr-Bochicchio <[email protected]>
1 parent 2654a9d commit 29a46b0

File tree

3 files changed

+303
-0
lines changed

3 files changed

+303
-0
lines changed

godo.go

+2
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ type Client struct {
8181
Projects ProjectsService
8282
Regions RegionsService
8383
Registry RegistryService
84+
Registries RegistriesService
8485
ReservedIPs ReservedIPsService
8586
ReservedIPActions ReservedIPActionsService
8687
Sizes SizesService
@@ -292,6 +293,7 @@ func NewClient(httpClient *http.Client) *Client {
292293
c.Projects = &ProjectsServiceOp{client: c}
293294
c.Regions = &RegionsServiceOp{client: c}
294295
c.Registry = &RegistryServiceOp{client: c}
296+
c.Registries = &RegistriesServiceOp{client: c}
295297
c.ReservedIPs = &ReservedIPsServiceOp{client: c}
296298
c.ReservedIPActions = &ReservedIPActionsServiceOp{client: c}
297299
c.Sizes = &SizesServiceOp{client: c}

registry.go

+120
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ const (
1414
registryPath = "/v2/registry"
1515
// RegistryServer is the hostname of the DigitalOcean registry service
1616
RegistryServer = "registry.digitalocean.com"
17+
18+
// Multi-registry Open Beta API constants
19+
registriesPath = "/v2/registries"
1720
)
1821

1922
// RegistryService is an interface for interfacing with the Registry endpoints
@@ -240,6 +243,19 @@ type RegistryValidateNameRequest struct {
240243
Name string `json:"name"`
241244
}
242245

246+
// Multi-registry Open Beta API structs
247+
248+
type registriesRoot struct {
249+
Registries []*Registry `json:"registries,omitempty"`
250+
TotalStorageUsageBytes uint64 `json:"total_storage_usage_bytes,omitempty"`
251+
}
252+
253+
// RegistriesCreateRequest represents a request to create a secondary registry.
254+
type RegistriesCreateRequest struct {
255+
Name string `json:"name,omitempty"`
256+
Region string `json:"region,omitempty"`
257+
}
258+
243259
// Get retrieves the details of a Registry.
244260
func (svc *RegistryServiceOp) Get(ctx context.Context) (*Registry, *Response, error) {
245261
req, err := svc.client.NewRequest(ctx, http.MethodGet, registryPath, nil)
@@ -610,3 +626,107 @@ func (svc *RegistryServiceOp) ValidateName(ctx context.Context, request *Registr
610626
}
611627
return resp, nil
612628
}
629+
630+
// RegistriesService is an interface for interfacing with the new multiple-registry beta endpoints
631+
// of the DigitalOcean API.
632+
//
633+
// We are creating a separate Service in alignment with the new /v2/registries endpoints.
634+
type RegistriesService interface {
635+
Get(context.Context, string) (*Registry, *Response, error)
636+
List(context.Context) ([]*Registry, *Response, error)
637+
Create(context.Context, *RegistriesCreateRequest) (*Registry, *Response, error)
638+
Delete(context.Context, string) (*Response, error)
639+
DockerCredentials(context.Context, string, *RegistryDockerCredentialsRequest) (*DockerCredentials, *Response, error)
640+
}
641+
642+
var _ RegistriesService = &RegistriesServiceOp{}
643+
644+
// RegistriesServiceOp handles communication with the multiple-registry beta methods.
645+
type RegistriesServiceOp struct {
646+
client *Client
647+
}
648+
649+
// Get returns the details of a named Registry.
650+
func (svc *RegistriesServiceOp) Get(ctx context.Context, registry string) (*Registry, *Response, error) {
651+
path := fmt.Sprintf("%s/%s", registriesPath, registry)
652+
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
653+
if err != nil {
654+
return nil, nil, err
655+
}
656+
root := new(registryRoot)
657+
resp, err := svc.client.Do(ctx, req, root)
658+
if err != nil {
659+
return nil, resp, err
660+
}
661+
return root.Registry, resp, nil
662+
}
663+
664+
// List returns a list of the named Registries.
665+
func (svc *RegistriesServiceOp) List(ctx context.Context) ([]*Registry, *Response, error) {
666+
req, err := svc.client.NewRequest(ctx, http.MethodGet, registriesPath, nil)
667+
if err != nil {
668+
return nil, nil, err
669+
}
670+
root := new(registriesRoot)
671+
resp, err := svc.client.Do(ctx, req, root)
672+
if err != nil {
673+
return nil, resp, err
674+
}
675+
return root.Registries, resp, nil
676+
}
677+
678+
// Create creates a named Registry.
679+
func (svc *RegistriesServiceOp) Create(ctx context.Context, create *RegistriesCreateRequest) (*Registry, *Response, error) {
680+
req, err := svc.client.NewRequest(ctx, http.MethodPost, registriesPath, create)
681+
if err != nil {
682+
return nil, nil, err
683+
}
684+
root := new(registryRoot)
685+
resp, err := svc.client.Do(ctx, req, root)
686+
if err != nil {
687+
return nil, resp, err
688+
}
689+
return root.Registry, resp, nil
690+
}
691+
692+
// Delete deletes a named Registry. There is no way to recover a Registry once it has
693+
// been destroyed.
694+
func (svc *RegistriesServiceOp) Delete(ctx context.Context, registry string) (*Response, error) {
695+
path := fmt.Sprintf("%s/%s", registriesPath, registry)
696+
req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil)
697+
if err != nil {
698+
return nil, err
699+
}
700+
resp, err := svc.client.Do(ctx, req, nil)
701+
if err != nil {
702+
return resp, err
703+
}
704+
return resp, nil
705+
}
706+
707+
// DockerCredentials retrieves a Docker config file containing named Registry's credentials.
708+
func (svc *RegistriesServiceOp) DockerCredentials(ctx context.Context, registry string, request *RegistryDockerCredentialsRequest) (*DockerCredentials, *Response, error) {
709+
path := fmt.Sprintf("%s/%s/%s", registriesPath, registry, "docker-credentials")
710+
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
711+
if err != nil {
712+
return nil, nil, err
713+
}
714+
715+
q := req.URL.Query()
716+
q.Add("read_write", strconv.FormatBool(request.ReadWrite))
717+
if request.ExpirySeconds != nil {
718+
q.Add("expiry_seconds", strconv.Itoa(*request.ExpirySeconds))
719+
}
720+
req.URL.RawQuery = q.Encode()
721+
722+
var buf bytes.Buffer
723+
resp, err := svc.client.Do(ctx, req, &buf)
724+
if err != nil {
725+
return nil, resp, err
726+
}
727+
728+
dc := &DockerCredentials{
729+
DockerConfigJSON: buf.Bytes(),
730+
}
731+
return dc, resp, nil
732+
}

registry_test.go

+181
Original file line numberDiff line numberDiff line change
@@ -965,3 +965,184 @@ func TestRegistry_ValidateName(t *testing.T) {
965965
_, err := client.Registry.ValidateName(ctx, validateNameRequest)
966966
require.NoError(t, err)
967967
}
968+
969+
// Tests for Registries service methods
970+
func TestRegistries_Get(t *testing.T) {
971+
setup()
972+
defer teardown()
973+
974+
want := &Registry{
975+
Name: testRegistry,
976+
StorageUsageBytes: 0,
977+
StorageUsageBytesUpdatedAt: testTime,
978+
CreatedAt: testTime,
979+
Region: testRegion,
980+
}
981+
982+
// We return `read_only` and `type` (only for multi-regsitry) -- check if we need to do this or not -- older tests don't add `read_only` to the response
983+
getResponseJSON := `
984+
{
985+
"registry": {
986+
"name": "` + testRegistry + `",
987+
"storage_usage_bytes": 0,
988+
"storage_usage_bytes_updated_at": "` + testTimeString + `",
989+
"created_at": "` + testTimeString + `",
990+
"region": "` + testRegion + `"
991+
}
992+
}`
993+
994+
mux.HandleFunc(fmt.Sprintf("/v2/registries/%s", testRegistry), func(w http.ResponseWriter, r *http.Request) {
995+
testMethod(t, r, http.MethodGet)
996+
fmt.Fprint(w, getResponseJSON)
997+
})
998+
got, _, err := client.Registries.Get(ctx, testRegistry)
999+
require.NoError(t, err)
1000+
require.Equal(t, want, got)
1001+
}
1002+
1003+
func TestRegistries_List(t *testing.T) {
1004+
setup()
1005+
defer teardown()
1006+
1007+
wantRegistries := []*Registry{
1008+
{
1009+
Name: testRegistry,
1010+
StorageUsageBytes: 0,
1011+
StorageUsageBytesUpdatedAt: testTime,
1012+
CreatedAt: testTime,
1013+
Region: testRegion,
1014+
},
1015+
}
1016+
getResponseJSON := `
1017+
{
1018+
"registries": [
1019+
{
1020+
"name": "` + testRegistry + `",
1021+
"storage_usage_bytes": 0,
1022+
"storage_usage_bytes_updated_at": "` + testTimeString + `",
1023+
"created_at": "` + testTimeString + `",
1024+
"region": "` + testRegion + `"
1025+
}
1026+
]
1027+
}`
1028+
1029+
mux.HandleFunc("/v2/registries", func(w http.ResponseWriter, r *http.Request) {
1030+
testMethod(t, r, http.MethodGet)
1031+
fmt.Printf("Returning: %v", getResponseJSON)
1032+
fmt.Fprint(w, getResponseJSON)
1033+
})
1034+
got, _, err := client.Registries.List(ctx)
1035+
require.NoError(t, err)
1036+
fmt.Printf("Expected: %+v\n", wantRegistries)
1037+
fmt.Printf("Got: %+v\n", got)
1038+
require.Equal(t, wantRegistries, got)
1039+
}
1040+
1041+
func TestRegistries_Create(t *testing.T) {
1042+
setup()
1043+
defer teardown()
1044+
1045+
want := &Registry{
1046+
Name: testRegistry,
1047+
StorageUsageBytes: 0,
1048+
StorageUsageBytesUpdatedAt: testTime,
1049+
CreatedAt: testTime,
1050+
Region: testRegion,
1051+
}
1052+
1053+
createRequest := &RegistriesCreateRequest{
1054+
Name: want.Name,
1055+
Region: testRegion,
1056+
}
1057+
1058+
createResponseJSON := `
1059+
{
1060+
"registry": {
1061+
"name": "` + testRegistry + `",
1062+
"storage_usage_bytes": 0,
1063+
"storage_usage_bytes_updated_at": "` + testTimeString + `",
1064+
"created_at": "` + testTimeString + `",
1065+
"region": "` + testRegion + `"
1066+
}
1067+
}`
1068+
1069+
mux.HandleFunc("/v2/registries", func(w http.ResponseWriter, r *http.Request) {
1070+
v := new(RegistriesCreateRequest)
1071+
err := json.NewDecoder(r.Body).Decode(v)
1072+
if err != nil {
1073+
t.Fatal(err)
1074+
}
1075+
1076+
testMethod(t, r, http.MethodPost)
1077+
require.Equal(t, v, createRequest)
1078+
fmt.Fprint(w, createResponseJSON)
1079+
})
1080+
1081+
got, _, err := client.Registries.Create(ctx, createRequest)
1082+
require.NoError(t, err)
1083+
require.Equal(t, want, got)
1084+
}
1085+
1086+
func TestRegistries_Delete(t *testing.T) {
1087+
setup()
1088+
defer teardown()
1089+
1090+
mux.HandleFunc(fmt.Sprintf("/v2/registries/%s", testRegistry), func(w http.ResponseWriter, r *http.Request) {
1091+
testMethod(t, r, http.MethodDelete)
1092+
})
1093+
1094+
_, err := client.Registries.Delete(ctx, testRegistry)
1095+
require.NoError(t, err)
1096+
}
1097+
1098+
func TestRegistries_DockerCredentials(t *testing.T) {
1099+
returnedConfig := "this could be a docker config"
1100+
tests := []struct {
1101+
name string
1102+
params *RegistryDockerCredentialsRequest
1103+
expectedReadWrite string
1104+
expectedExpirySeconds string
1105+
}{
1106+
{
1107+
name: "read-only (default)",
1108+
params: &RegistryDockerCredentialsRequest{},
1109+
expectedReadWrite: "false",
1110+
},
1111+
{
1112+
name: "read/write",
1113+
params: &RegistryDockerCredentialsRequest{ReadWrite: true},
1114+
expectedReadWrite: "true",
1115+
},
1116+
{
1117+
name: "read-only + custom expiry",
1118+
params: &RegistryDockerCredentialsRequest{ExpirySeconds: PtrTo(60 * 60)},
1119+
expectedReadWrite: "false",
1120+
expectedExpirySeconds: "3600",
1121+
},
1122+
{
1123+
name: "read/write + custom expiry",
1124+
params: &RegistryDockerCredentialsRequest{ReadWrite: true, ExpirySeconds: PtrTo(60 * 60)},
1125+
expectedReadWrite: "true",
1126+
expectedExpirySeconds: "3600",
1127+
},
1128+
}
1129+
1130+
for _, test := range tests {
1131+
t.Run(test.name, func(t *testing.T) {
1132+
setup()
1133+
defer teardown()
1134+
1135+
mux.HandleFunc(fmt.Sprintf("/v2/registries/%s/docker-credentials", testRegistry), func(w http.ResponseWriter, r *http.Request) {
1136+
require.Equal(t, test.expectedReadWrite, r.URL.Query().Get("read_write"))
1137+
require.Equal(t, test.expectedExpirySeconds, r.URL.Query().Get("expiry_seconds"))
1138+
testMethod(t, r, http.MethodGet)
1139+
fmt.Fprint(w, returnedConfig)
1140+
})
1141+
1142+
got, _, err := client.Registries.DockerCredentials(ctx, testRegistry, test.params)
1143+
fmt.Println(returnedConfig)
1144+
require.NoError(t, err)
1145+
require.Equal(t, []byte(returnedConfig), got.DockerConfigJSON)
1146+
})
1147+
}
1148+
}

0 commit comments

Comments
 (0)