gogreen/registry.go

254 lines
7.8 KiB
Go

package godo
import (
"bytes"
"context"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
)
const (
registryPath = "/v2/registry"
// RegistryServer is the hostname of the DigitalOcean registry service
RegistryServer = "registry.digitalocean.com"
)
// RegistryService is an interface for interfacing with the Registry endpoints
// of the DigitalOcean API.
// See: https://developers.digitalocean.com/documentation/v2#registry
type RegistryService interface {
Create(context.Context, *RegistryCreateRequest) (*Registry, *Response, error)
Get(context.Context) (*Registry, *Response, error)
Delete(context.Context) (*Response, error)
DockerCredentials(context.Context, *RegistryDockerCredentialsRequest) (*DockerCredentials, *Response, error)
ListRepositories(context.Context, string, *ListOptions) ([]*Repository, *Response, error)
ListRepositoryTags(context.Context, string, string, *ListOptions) ([]*RepositoryTag, *Response, error)
DeleteTag(context.Context, string, string, string) (*Response, error)
DeleteManifest(context.Context, string, string, string) (*Response, error)
}
var _ RegistryService = &RegistryServiceOp{}
// RegistryServiceOp handles communication with Registry methods of the DigitalOcean API.
type RegistryServiceOp struct {
client *Client
}
// RegistryCreateRequest represents a request to create a registry.
type RegistryCreateRequest struct {
Name string `json:"name,omitempty"`
}
// RegistryDockerCredentialsRequest represents a request to retrieve docker
// credentials for a registry.
type RegistryDockerCredentialsRequest struct {
ReadWrite bool `json:"read_write"`
ExpirySeconds *int `json:"expiry_seconds,omitempty"`
}
// Registry represents a registry.
type Registry struct {
Name string `json:"name,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
}
// Repository represents a repository
type Repository struct {
RegistryName string `json:"registry_name,omitempty"`
Name string `json:"name,omitempty"`
LatestTag *RepositoryTag `json:"latest_tag,omitempty"`
TagCount uint64 `json:"tag_count,omitempty"`
}
// RepositoryTag represents a repository tag
type RepositoryTag struct {
RegistryName string `json:"registry_name,omitempty"`
Repository string `json:"repository,omitempty"`
Tag string `json:"tag,omitempty"`
ManifestDigest string `json:"manifest_digest,omitempty"`
CompressedSizeBytes uint64 `json:"compressed_size_bytes,omitempty"`
SizeBytes uint64 `json:"size_bytes,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
type registryRoot struct {
Registry *Registry `json:"registry,omitempty"`
}
type repositoriesRoot struct {
Repositories []*Repository `json:"repositories,omitempty"`
Links *Links `json:"links,omitempty"`
Meta *Meta `json:"meta"`
}
type repositoryTagsRoot struct {
Tags []*RepositoryTag `json:"tags,omitempty"`
Links *Links `json:"links,omitempty"`
Meta *Meta `json:"meta"`
}
// 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)
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
}
// Create creates a registry.
func (svc *RegistryServiceOp) Create(ctx context.Context, create *RegistryCreateRequest) (*Registry, *Response, error) {
req, err := svc.client.NewRequest(ctx, http.MethodPost, registryPath, 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 registry. There is no way to recover a registry once it has
// been destroyed.
func (svc *RegistryServiceOp) Delete(ctx context.Context) (*Response, error) {
req, err := svc.client.NewRequest(ctx, http.MethodDelete, registryPath, 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 is the content of a Docker config file
// that is used by the docker CLI
// See: https://docs.docker.com/engine/reference/commandline/cli/#configjson-properties
type DockerCredentials struct {
DockerConfigJSON []byte
}
// DockerCredentials retrieves a Docker config file containing the registry's credentials.
func (svc *RegistryServiceOp) DockerCredentials(ctx context.Context, request *RegistryDockerCredentialsRequest) (*DockerCredentials, *Response, error) {
path := fmt.Sprintf("%s/%s", registryPath, "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
}
// ListRepositories returns a list of the Repositories visible with the registry's credentials.
func (svc *RegistryServiceOp) ListRepositories(ctx context.Context, registry string, opts *ListOptions) ([]*Repository, *Response, error) {
path := fmt.Sprintf("%s/%s/repositories", registryPath, registry)
path, err := addOptions(path, opts)
if err != nil {
return nil, nil, err
}
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(repositoriesRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
if l := root.Links; l != nil {
resp.Links = l
}
if m := root.Meta; m != nil {
resp.Meta = m
}
return root.Repositories, resp, nil
}
// ListRepositoryTags returns a list of the RepositoryTags available within the given repository.
func (svc *RegistryServiceOp) ListRepositoryTags(ctx context.Context, registry, repository string, opts *ListOptions) ([]*RepositoryTag, *Response, error) {
path := fmt.Sprintf("%s/%s/repositories/%s/tags", registryPath, registry, url.PathEscape(repository))
path, err := addOptions(path, opts)
if err != nil {
return nil, nil, err
}
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(repositoryTagsRoot)
resp, err := svc.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
if l := root.Links; l != nil {
resp.Links = l
}
if m := root.Meta; m != nil {
resp.Meta = m
}
return root.Tags, resp, nil
}
// DeleteTag deletes a tag within a given repository.
func (svc *RegistryServiceOp) DeleteTag(ctx context.Context, registry, repository, tag string) (*Response, error) {
path := fmt.Sprintf("%s/%s/repositories/%s/tags/%s", registryPath, registry, url.PathEscape(repository), tag)
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
}
// DeleteManifest deletes a manifest by its digest within a given repository.
func (svc *RegistryServiceOp) DeleteManifest(ctx context.Context, registry, repository, digest string) (*Response, error) {
path := fmt.Sprintf("%s/%s/repositories/%s/digests/%s", registryPath, registry, url.PathEscape(repository), digest)
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
}