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

110 changes: 110 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 All @@ -38,6 +41,13 @@ type RegistryService interface {
GetSubscription(context.Context) (*RegistrySubscription, *Response, error)
UpdateSubscription(context.Context, *RegistrySubscriptionUpdateRequest) (*RegistrySubscription, *Response, error)
ValidateName(context.Context, *RegistryValidateNameRequest) (*Response, error)

// Multi-registry Open Beta API methods
GetBeta(context.Context, string) (*Registry, *Response, error)
ListBeta(context.Context) ([]*Registry, *Response, error)
CreateBeta(context.Context, *RegistriesCreateRequest) (*Registry, *Response, error)
DeleteBeta(context.Context, string) (*Response, error)
DockerCredentialsBeta(context.Context, string, *RegistryDockerCredentialsRequest) (*DockerCredentials, *Response, error)
rak16 marked this conversation as resolved.
Show resolved Hide resolved
}

var _ RegistryService = &RegistryServiceOp{}
Expand Down Expand Up @@ -240,6 +250,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 +633,90 @@ func (svc *RegistryServiceOp) ValidateName(ctx context.Context, request *Registr
}
return resp, nil
}

// Multi-registry Open Beta API endpoints

// GetBeta returns the details of a named Registry.
func (svc *RegistryServiceOp) GetBeta(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
}

// ListBeta returns a list of the named Registries.
func (svc *RegistryServiceOp) ListBeta(ctx context.Context) ([]*Registry, *Response, error) {
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
}

// CreateBeta creates a named Registry.
func (svc *RegistryServiceOp) CreateBeta(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 *RegistryServiceOp) DeleteBeta(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 *RegistryServiceOp) DockerCredentialsBeta(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)
}

func TestRegistries_GetBeta(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.Registry.GetBeta(ctx, testRegistry)
require.NoError(t, err)
require.Equal(t, want, got)
}

// TODO: Fix following test
func TestRegistries_ListBeta(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.Registry.ListBeta(ctx)
require.NoError(t, err)
fmt.Printf("Expected: %+v\n", wantRegistries)
fmt.Printf("Got: %+v\n", got)
require.Equal(t, wantRegistries, got)
}

func TestRegistries_CreateBeta(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 + `",
rak16 marked this conversation as resolved.
Show resolved Hide resolved
"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.Registry.CreateBeta(ctx, createRequest)
require.NoError(t, err)
require.Equal(t, want, got)
}

func TestRegistries_DeleteBeta(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.Registry.DeleteBeta(ctx, testRegistry)
require.NoError(t, err)
}

func TestRegistries_DockerCredentialsBeta(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.Registry.DockerCredentialsBeta(ctx, testRegistry, test.params)
fmt.Println(returnedConfig)
require.NoError(t, err)
require.Equal(t, []byte(returnedConfig), got.DockerConfigJSON)
})
}
}
Loading