Merge pull request #1 from digitalocean/master

Bring latest and greatest from head repo
This commit is contained in:
Wioletta Holownia 2020-06-08 13:14:39 -04:00 committed by GitHub
commit c247dbcbbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 528 additions and 55 deletions

28
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
go-pipeline:
name: test
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: go1.11 test
uses: digitalocean/golang-pipeline/go1.11/test@master
- name: go1.12 test
uses: digitalocean/golang-pipeline/go1.12/test@master
- name: go1.13 test
uses: digitalocean/golang-pipeline/go1.13/test@master
- name: go1.14 test
uses: digitalocean/golang-pipeline/go1.14/test@master

View File

@ -1,15 +0,0 @@
language: go
go:
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- 1.11.x
- 1.12.x
- 1.13.x
- tip
matrix:
allow_failures:
- go: tip

52
1-click.go Normal file
View File

@ -0,0 +1,52 @@
package godo
import (
"context"
"fmt"
"net/http"
)
const oneClickBasePath = "v2/1-clicks"
// OneClickService is an interface for interacting with 1-clicks with the
// DigitalOcean API.
// See: https://developers.digitalocean.com/documentation/v2/#1-click-applications
type OneClickService interface {
List(context.Context, string) ([]*OneClick, *Response, error)
}
var _ OneClickService = &OneClickServiceOp{}
// OneClickServiceOp interfaces with 1-click endpoints in the DigitalOcean API.
type OneClickServiceOp struct {
client *Client
}
// OneClick is the structure of a 1-click
type OneClick struct {
Slug string `json:"slug"`
Type string `json:"type"`
}
// OneClicksRoot is the root of the json payload that contains a list of 1-clicks
type OneClicksRoot struct {
List []*OneClick `json:"1_clicks"`
}
// List returns a list of the available 1-click applications.
func (ocs *OneClickServiceOp) List(ctx context.Context, oneClickType string) ([]*OneClick, *Response, error) {
path := fmt.Sprintf(`%s?type=%s`, oneClickBasePath, oneClickType)
req, err := ocs.client.NewRequest(ctx, http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
}
root := new(OneClicksRoot)
resp, err := ocs.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.List, resp, nil
}

49
1-click_test.go Normal file
View File

@ -0,0 +1,49 @@
package godo
import (
"fmt"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var testOneClick = &OneClick{
Slug: "test-slug",
Type: "droplet",
}
var testOneClickJSON = `
{
"slug":"test-slug",
"type":"droplet"
}
`
func TestOneClick_List(t *testing.T) {
setup()
defer teardown()
svc := client.OneClick
path := "/v2/1-clicks"
want := []*OneClick{
testOneClick,
}
jsonBlob := `
{
"1_clicks": [
` + testOneClickJSON + `
]
}
`
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
fmt.Fprint(w, jsonBlob)
})
got, _, err := svc.List(ctx, "")
require.NoError(t, err)
assert.Equal(t, want, got)
}

View File

@ -1,6 +1,27 @@
# Change Log
## unreleased
## [v1.37.0] - 2020-06-01
- #336 registry: URL encode repository names when building URLs. @adamwg
- #335 Add 1-click service and request. @scottcrawford03
## [v1.36.0] - 2020-05-12
- #331 Expose expiry_seconds for Registry.DockerCredentials. @andrewsomething
## [v1.35.1] - 2020-04-21
- #328 Update vulnerable x/crypto dependency - @bentranter
## [v1.35.0] - 2020-04-20
- #326 Add TagCount field to registry/Repository - @nicktate
- #325 Add DOCR EA routes - @nicktate
- #324 Upgrade godo to Go 1.14 - @bentranter
## [v1.34.0] - 2020-03-30
- #320 Add VPC v3 attributes - @viola
## [v1.33.1] - 2020-03-23

View File

@ -1,25 +1,17 @@
# Contributing
If you submit a pull request, please keep the following guidelines in mind:
We love contributions! You are welcome to open a pull request, but it's a good idea to
open an issue and discuss your idea with us first.
Once you are ready to open a PR, please keep the following guidelines in mind:
1. Code should be `go fmt` compliant.
2. Types, structs and funcs should be documented.
3. Tests pass.
1. Types, structs and funcs should be documented.
1. Tests pass.
## Getting set up
Assuming your `$GOPATH` is set up according to your desires, run:
```sh
go get github.com/digitalocean/godo
go get -u github.com/stretchr/testify/assert
```
If outside `$GOPATH`, just clone the repository:
```sh
git clone https://github.com/digitalocean/godo
```
`godo` uses go modules. Just fork this repo, clone your fork and off you go!
## Running tests
@ -31,13 +23,21 @@ go test -mod=vendor .
## Versioning
Godo follows [semver](https://www.semver.org) versioning semantics. New functionality should be accompanied by increment to the minor version number. The current strategy is to release often. Any code which is complete, tested, reviewed, and merged to master is worthy of release.
Godo follows [semver](https://www.semver.org) versioning semantics.
New functionality should be accompanied by increment to the minor
version number. Any code merged to master is subject to release.
## Releasing
Releasing a new version of godo is currently a manual process.
Releasing a new version of godo is currently a manual process.
1. Update the `CHANGELOG.md` with your changes. If a version header for the next (unreleased) version does not exist, create one. Include one bullet point for each piece of new functionality in the release, including the pull request ID, description, and author(s).
Submit a separate pull request for the version change from the pull
request with your changes.
1. Update the `CHANGELOG.md` with your changes. If a version header
for the next (unreleased) version does not exist, create one.
Include one bullet point for each piece of new functionality in the
release, including the pull request ID, description, and author(s).
```
## [v1.8.0] - 2019-03-13

9
go.mod
View File

@ -1,11 +1,16 @@
module github.com/digitalocean/godo
go 1.13
go 1.14
require (
github.com/golang/protobuf v1.3.5 // indirect
github.com/google/go-querystring v1.0.0
github.com/stretchr/testify v1.4.0
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
google.golang.org/appengine v1.6.5 // indirect
)
replace github.com/stretchr/objx => github.com/stretchr/objx v0.2.0
replace golang.org/x/crypto => golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a

21
go.sum
View File

@ -5,6 +5,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -13,16 +16,26 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=

View File

@ -18,7 +18,7 @@ import (
)
const (
libraryVersion = "1.33.1"
libraryVersion = "1.37.0"
defaultBaseURL = "https://api.digitalocean.com/"
userAgent = "godo/" + libraryVersion
mediaType = "application/json"
@ -72,6 +72,7 @@ type Client struct {
Registry RegistryService
Databases DatabasesService
VPCs VPCsService
OneClick OneClickService
// Optional function called after every successful request made to the DO APIs
onRequestCompleted RequestCompletionCallback
@ -211,6 +212,7 @@ func NewClient(httpClient *http.Client) *Client {
c.Registry = &RegistryServiceOp{client: c}
c.Databases = &DatabasesServiceOp{client: c}
c.VPCs = &VPCsServiceOp{client: c}
c.OneClick = &OneClickServiceOp{client: c}
return c
}

View File

@ -5,6 +5,8 @@ import (
"context"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
)
@ -22,6 +24,10 @@ type RegistryService interface {
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{}
@ -39,7 +45,8 @@ type RegistryCreateRequest struct {
// RegistryDockerCredentialsRequest represents a request to retrieve docker
// credentials for a registry.
type RegistryDockerCredentialsRequest struct {
ReadWrite bool `json:"read_write"`
ReadWrite bool `json:"read_write"`
ExpirySeconds *int `json:"expiry_seconds,omitempty"`
}
// Registry represents a registry.
@ -48,10 +55,41 @@ type Registry struct {
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)
@ -103,13 +141,19 @@ type DockerCredentials struct {
// 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?read_write=%t", registryPath, "docker-credentials", request.ReadWrite)
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 {
@ -121,3 +165,89 @@ func (svc *RegistryServiceOp) DockerCredentials(ctx context.Context, request *Re
}
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
}

View File

@ -7,18 +7,32 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
testRegistry = "test-registry"
testRepository = "test/repository"
testEncodedRepository = "test%2Frepository"
testTag = "test-tag"
testDigest = "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f"
testCompressedSize = 2789669
testSize = 5843968
)
var (
testTime = time.Date(2020, 4, 1, 0, 0, 0, 0, time.UTC)
testTimeString = testTime.Format(time.RFC3339)
)
func TestRegistry_Create(t *testing.T) {
setup()
defer teardown()
createdAt, err := time.Parse(time.RFC3339, "2020-01-24T20:24:31Z")
require.NoError(t, err)
want := &Registry{
Name: "foo",
CreatedAt: createdAt,
Name: testRegistry,
CreatedAt: testTime,
}
createRequest := &RegistryCreateRequest{
@ -28,8 +42,8 @@ func TestRegistry_Create(t *testing.T) {
createResponseJSON := `
{
"registry": {
"name": "foo",
"created_at": "2020-01-24T20:24:31Z"
"name": "` + testRegistry + `",
"created_at": "` + testTimeString + `"
}
}`
@ -55,13 +69,13 @@ func TestRegistry_Get(t *testing.T) {
defer teardown()
want := &Registry{
Name: "foo",
Name: testRegistry,
}
getResponseJSON := `
{
"registry": {
"name": "foo"
"name": "` + testRegistry + `"
}
}`
@ -89,9 +103,10 @@ func TestRegistry_Delete(t *testing.T) {
func TestRegistry_DockerCredentials(t *testing.T) {
returnedConfig := "this could be a docker config"
tests := []struct {
name string
params *RegistryDockerCredentialsRequest
expectedReadWrite string
name string
params *RegistryDockerCredentialsRequest
expectedReadWrite string
expectedExpirySeconds string
}{
{
name: "read-only (default)",
@ -103,6 +118,18 @@ func TestRegistry_DockerCredentials(t *testing.T) {
params: &RegistryDockerCredentialsRequest{ReadWrite: true},
expectedReadWrite: "true",
},
{
name: "read-only + custom expiry",
params: &RegistryDockerCredentialsRequest{ExpirySeconds: intPtr(60 * 60)},
expectedReadWrite: "false",
expectedExpirySeconds: "3600",
},
{
name: "read/write + custom expiry",
params: &RegistryDockerCredentialsRequest{ReadWrite: true, ExpirySeconds: intPtr(60 * 60)},
expectedReadWrite: "true",
expectedExpirySeconds: "3600",
},
}
for _, test := range tests {
@ -112,6 +139,7 @@ func TestRegistry_DockerCredentials(t *testing.T) {
mux.HandleFunc("/v2/registry/docker-credentials", 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)
})
@ -122,3 +150,163 @@ func TestRegistry_DockerCredentials(t *testing.T) {
})
}
}
func TestRepository_List(t *testing.T) {
setup()
defer teardown()
wantRepositories := []*Repository{
{
RegistryName: testRegistry,
Name: testRepository,
TagCount: 1,
LatestTag: &RepositoryTag{
RegistryName: testRegistry,
Repository: testRepository,
Tag: testTag,
ManifestDigest: testDigest,
CompressedSizeBytes: testCompressedSize,
SizeBytes: testSize,
UpdatedAt: testTime,
},
},
}
getResponseJSON := `{
"repositories": [
{
"registry_name": "` + testRegistry + `",
"name": "` + testRepository + `",
"tag_count": 1,
"latest_tag": {
"registry_name": "` + testRegistry + `",
"repository": "` + testRepository + `",
"tag": "` + testTag + `",
"manifest_digest": "` + testDigest + `",
"compressed_size_bytes": ` + fmt.Sprintf("%d", testCompressedSize) + `,
"size_bytes": ` + fmt.Sprintf("%d", testSize) + `,
"updated_at": "` + testTimeString + `"
}
}
],
"links": {
"pages": {
"next": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/repositories?page=2",
"last": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/repositories?page=2"
}
},
"meta": {
"total": 2
}
}`
mux.HandleFunc(fmt.Sprintf("/v2/registry/%s/repositories", testRegistry), func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
testFormValues(t, r, map[string]string{"page": "1", "per_page": "1"})
fmt.Fprint(w, getResponseJSON)
})
got, response, err := client.Registry.ListRepositories(ctx, testRegistry, &ListOptions{Page: 1, PerPage: 1})
require.NoError(t, err)
require.Equal(t, wantRepositories, got)
gotRespLinks := response.Links
wantRespLinks := &Links{
Pages: &Pages{
Next: fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositories?page=2", testRegistry),
Last: fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositories?page=2", testRegistry),
},
}
assert.Equal(t, wantRespLinks, gotRespLinks)
gotRespMeta := response.Meta
wantRespMeta := &Meta{
Total: 2,
}
assert.Equal(t, wantRespMeta, gotRespMeta)
}
func TestRepository_ListTags(t *testing.T) {
setup()
defer teardown()
wantTags := []*RepositoryTag{
{
RegistryName: testRegistry,
Repository: testRepository,
Tag: testTag,
ManifestDigest: testDigest,
CompressedSizeBytes: testCompressedSize,
SizeBytes: testSize,
UpdatedAt: testTime,
},
}
getResponseJSON := `{
"tags": [
{
"registry_name": "` + testRegistry + `",
"repository": "` + testRepository + `",
"tag": "` + testTag + `",
"manifest_digest": "` + testDigest + `",
"compressed_size_bytes": ` + fmt.Sprintf("%d", testCompressedSize) + `,
"size_bytes": ` + fmt.Sprintf("%d", testSize) + `,
"updated_at": "` + testTimeString + `"
}
],
"links": {
"pages": {
"next": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/repositories/` + testEncodedRepository + `/tags?page=2",
"last": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/repositories/` + testEncodedRepository + `/tags?page=2"
}
},
"meta": {
"total": 2
}
}`
mux.HandleFunc(fmt.Sprintf("/v2/registry/%s/repositories/%s/tags", testRegistry, testRepository), func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodGet)
testFormValues(t, r, map[string]string{"page": "1", "per_page": "1"})
fmt.Fprint(w, getResponseJSON)
})
got, response, err := client.Registry.ListRepositoryTags(ctx, testRegistry, testRepository, &ListOptions{Page: 1, PerPage: 1})
require.NoError(t, err)
require.Equal(t, wantTags, got)
gotRespLinks := response.Links
wantRespLinks := &Links{
Pages: &Pages{
Next: fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositories/%s/tags?page=2", testRegistry, testEncodedRepository),
Last: fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositories/%s/tags?page=2", testRegistry, testEncodedRepository),
},
}
assert.Equal(t, wantRespLinks, gotRespLinks)
gotRespMeta := response.Meta
wantRespMeta := &Meta{
Total: 2,
}
assert.Equal(t, wantRespMeta, gotRespMeta)
}
func TestRegistry_DeleteTag(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc(fmt.Sprintf("/v2/registry/%s/repositories/%s/tags/%s", testRegistry, testRepository, testTag), func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodDelete)
})
_, err := client.Registry.DeleteTag(ctx, testRegistry, testRepository, testTag)
require.NoError(t, err)
}
func TestRegistry_DeleteManifest(t *testing.T) {
setup()
defer teardown()
mux.HandleFunc(fmt.Sprintf("/v2/registry/%s/repositories/%s/digests/%s", testRegistry, testRepository, testDigest), func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, http.MethodDelete)
})
_, err := client.Registry.DeleteManifest(ctx, testRegistry, testRepository, testDigest)
require.NoError(t, err)
}