Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DOCR-1201: Add new RegistriesService to support methods for multiple-registry open beta #730

2 changes: 2 additions & 0 deletions godo.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ type Client struct {
Projects ProjectsService
Regions RegionsService
Registry RegistryService
Registries RegistriesService
ReservedIPs ReservedIPsService
ReservedIPActions ReservedIPActionsService
Sizes SizesService
Expand Down Expand Up @@ -292,6 +293,7 @@ func NewClient(httpClient *http.Client) *Client {
c.Projects = &ProjectsServiceOp{client: c}
c.Regions = &RegionsServiceOp{client: c}
c.Registry = &RegistryServiceOp{client: c}
c.Registries = &RegistriesServiceOp{client: c}
c.ReservedIPs = &ReservedIPsServiceOp{client: c}
c.ReservedIPActions = &ReservedIPActionsServiceOp{client: c}
c.Sizes = &SizesServiceOp{client: c}
Expand Down
120 changes: 120 additions & 0 deletions registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const (
registryPath = "/v2/registry"
// RegistryServer is the hostname of the DigitalOcean registry service
RegistryServer = "registry.digitalocean.com"

// Multi-registry Open Beta API constants
registriesPath = "/v2/registries"
)

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

// Multi-registry Open Beta API structs

type registriesRoot struct {
Registries []*Registry `json:"registries,omitempty"`
TotalStorageUsageBytes uint64 `json:"total_storage_usage_bytes,omitempty"`
}

// RegistriesCreateRequest represents a request to create a secondary registry.
type RegistriesCreateRequest struct {
Name string `json:"name,omitempty"`
Region string `json:"region,omitempty"`
}

// Get retrieves the details of a Registry.
func (svc *RegistryServiceOp) Get(ctx context.Context) (*Registry, *Response, error) {
req, err := svc.client.NewRequest(ctx, http.MethodGet, registryPath, nil)
Expand Down Expand Up @@ -610,3 +626,107 @@ func (svc *RegistryServiceOp) ValidateName(ctx context.Context, request *Registr
}
return resp, nil
}

// RegistriesService is an interface for interfacing with the new multiple-registry beta endpoints
// of the DigitalOcean API.
//
// We are creating a separate Service in alignment with the new /v2/registries endpoints.
type RegistriesService interface {
Get(context.Context, string) (*Registry, *Response, error)
List(context.Context) ([]*Registry, *Response, error)
Create(context.Context, *RegistriesCreateRequest) (*Registry, *Response, error)
Delete(context.Context, string) (*Response, error)
DockerCredentials(context.Context, string, *RegistryDockerCredentialsRequest) (*DockerCredentials, *Response, error)
}

var _ RegistriesService = &RegistriesServiceOp{}

// RegistriesServiceOp handles communication with the multiple-registry beta methods.
type RegistriesServiceOp struct {
client *Client
}

// Get returns the details of a named Registry.
func (svc *RegistriesServiceOp) Get(ctx context.Context, registry string) (*Registry, *Response, error) {
path := fmt.Sprintf("%s/%s", registriesPath, registry)
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(registryRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Registry, resp, nil
}

// List returns a list of the named Registries.
func (svc *RegistriesServiceOp) List(ctx context.Context) ([]*Registry, *Response, error) {
andrewsomething marked this conversation as resolved.
Show resolved Hide resolved
req, err := svc.client.NewRequest(ctx, http.MethodGet, registriesPath, nil)
if err != nil {
return nil, nil, err
}
root := new(registriesRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Registries, resp, nil
}

// Create creates a named Registry.
func (svc *RegistriesServiceOp) Create(ctx context.Context, create *RegistriesCreateRequest) (*Registry, *Response, error) {
req, err := svc.client.NewRequest(ctx, http.MethodPost, registriesPath, create)
if err != nil {
return nil, nil, err
}
root := new(registryRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.Registry, resp, nil
}

// Delete deletes a named Registry. There is no way to recover a Registry once it has
// been destroyed.
func (svc *RegistriesServiceOp) Delete(ctx context.Context, registry string) (*Response, error) {
path := fmt.Sprintf("%s/%s", registriesPath, registry)
req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil)
if err != nil {
return nil, err
}
resp, err := svc.client.Do(ctx, req, nil)
if err != nil {
return resp, err
}
return resp, nil
}

// DockerCredentials retrieves a Docker config file containing named Registry's credentials.
func (svc *RegistriesServiceOp) DockerCredentials(ctx context.Context, registry string, request *RegistryDockerCredentialsRequest) (*DockerCredentials, *Response, error) {
path := fmt.Sprintf("%s/%s/%s", registriesPath, registry, "docker-credentials")
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}

q := req.URL.Query()
q.Add("read_write", strconv.FormatBool(request.ReadWrite))
if request.ExpirySeconds != nil {
q.Add("expiry_seconds", strconv.Itoa(*request.ExpirySeconds))
}
req.URL.RawQuery = q.Encode()

var buf bytes.Buffer
resp, err := svc.client.Do(ctx, req, &buf)
if err != nil {
return nil, resp, err
}

dc := &DockerCredentials{
DockerConfigJSON: buf.Bytes(),
}
return dc, resp, nil
}
181 changes: 181 additions & 0 deletions registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -965,3 +965,184 @@ func TestRegistry_ValidateName(t *testing.T) {
_, err := client.Registry.ValidateName(ctx, validateNameRequest)
require.NoError(t, err)
}

// Tests for Registries service methods
func TestRegistries_Get(t *testing.T) {
setup()
defer teardown()

want := &Registry{
Name: testRegistry,
StorageUsageBytes: 0,
StorageUsageBytesUpdatedAt: testTime,
CreatedAt: testTime,
Region: testRegion,
}

// 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
getResponseJSON := `
{
"registry": {
"name": "` + testRegistry + `",
"storage_usage_bytes": 0,
"storage_usage_bytes_updated_at": "` + testTimeString + `",
"created_at": "` + testTimeString + `",
"region": "` + testRegion + `"
}
}`

mux.HandleFunc(fmt.Sprintf("/v2/registries/%s", testRegistry), func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
fmt.Fprint(w, getResponseJSON)
})
got, _, err := client.Registries.Get(ctx, testRegistry)
require.NoError(t, err)
require.Equal(t, want, got)
}

func TestRegistries_List(t *testing.T) {
setup()
defer teardown()

wantRegistries := []*Registry{
{
Name: testRegistry,
StorageUsageBytes: 0,
StorageUsageBytesUpdatedAt: testTime,
CreatedAt: testTime,
Region: testRegion,
},
}
getResponseJSON := `
{
"registries": [
{
"name": "` + testRegistry + `",
"storage_usage_bytes": 0,
"storage_usage_bytes_updated_at": "` + testTimeString + `",
"created_at": "` + testTimeString + `",
"region": "` + testRegion + `"
}
]
}`

mux.HandleFunc("/v2/registries", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
fmt.Printf("Returning: %v", getResponseJSON)
fmt.Fprint(w, getResponseJSON)
})
got, _, err := client.Registries.List(ctx)
require.NoError(t, err)
fmt.Printf("Expected: %+v\n", wantRegistries)
fmt.Printf("Got: %+v\n", got)
require.Equal(t, wantRegistries, got)
}

func TestRegistries_Create(t *testing.T) {
setup()
defer teardown()

want := &Registry{
Name: testRegistry,
StorageUsageBytes: 0,
StorageUsageBytesUpdatedAt: testTime,
CreatedAt: testTime,
Region: testRegion,
}

createRequest := &RegistriesCreateRequest{
Name: want.Name,
Region: testRegion,
}

createResponseJSON := `
{
"registry": {
"name": "` + testRegistry + `",
"storage_usage_bytes": 0,
"storage_usage_bytes_updated_at": "` + testTimeString + `",
"created_at": "` + testTimeString + `",
"region": "` + testRegion + `"
}
}`

mux.HandleFunc("/v2/registries", func(w http.ResponseWriter, r *http.Request) {
v := new(RegistriesCreateRequest)
err := json.NewDecoder(r.Body).Decode(v)
if err != nil {
t.Fatal(err)
}

testMethod(t, r, http.MethodPost)
require.Equal(t, v, createRequest)
fmt.Fprint(w, createResponseJSON)
})

got, _, err := client.Registries.Create(ctx, createRequest)
require.NoError(t, err)
require.Equal(t, want, got)
}

func TestRegistries_Delete(t *testing.T) {
setup()
defer teardown()

mux.HandleFunc(fmt.Sprintf("/v2/registries/%s", testRegistry), func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodDelete)
})

_, err := client.Registries.Delete(ctx, testRegistry)
require.NoError(t, err)
}

func TestRegistries_DockerCredentials(t *testing.T) {
returnedConfig := "this could be a docker config"
tests := []struct {
name string
params *RegistryDockerCredentialsRequest
expectedReadWrite string
expectedExpirySeconds string
}{
{
name: "read-only (default)",
params: &RegistryDockerCredentialsRequest{},
expectedReadWrite: "false",
},
{
name: "read/write",
params: &RegistryDockerCredentialsRequest{ReadWrite: true},
expectedReadWrite: "true",
},
{
name: "read-only + custom expiry",
params: &RegistryDockerCredentialsRequest{ExpirySeconds: PtrTo(60 * 60)},
expectedReadWrite: "false",
expectedExpirySeconds: "3600",
},
{
name: "read/write + custom expiry",
params: &RegistryDockerCredentialsRequest{ReadWrite: true, ExpirySeconds: PtrTo(60 * 60)},
expectedReadWrite: "true",
expectedExpirySeconds: "3600",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
setup()
defer teardown()

mux.HandleFunc(fmt.Sprintf("/v2/registries/%s/docker-credentials", testRegistry), func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, test.expectedReadWrite, r.URL.Query().Get("read_write"))
require.Equal(t, test.expectedExpirySeconds, r.URL.Query().Get("expiry_seconds"))
testMethod(t, r, http.MethodGet)
fmt.Fprint(w, returnedConfig)
})

got, _, err := client.Registries.DockerCredentials(ctx, testRegistry, test.params)
fmt.Println(returnedConfig)
require.NoError(t, err)
require.Equal(t, []byte(returnedConfig), got.DockerConfigJSON)
})
}
}
Loading