254 lines
7.8 KiB
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
|
||
|
}
|