Skip to content

Commit

Permalink
docr: Update interface to add methods for multi-registry open beta
Browse files Browse the repository at this point in the history
  • Loading branch information
rak16 committed Sep 24, 2024
1 parent 57fbfeb commit 17531c1
Show file tree
Hide file tree
Showing 2 changed files with 290 additions and 0 deletions.
109 changes: 109 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)
}

var _ RegistryService = &RegistryServiceOp{}
Expand Down Expand Up @@ -240,6 +250,18 @@ type RegistryValidateNameRequest struct {
Name string `json:"name"`
}

// Multi-registry Open Beta API structs

type registriesRoot struct {
Registries []*Registry `json:"registries,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 +632,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 + `",
"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)
})
}
}

0 comments on commit 17531c1

Please sign in to comment.