From 8b498b7d489b33d462827fc191d2824acc8e5b97 Mon Sep 17 00:00:00 2001 From: Ben Tranter Date: Mon, 30 Mar 2020 12:29:42 -0400 Subject: [PATCH 01/23] Prepare to release v1.34.0 --- CHANGELOG.md | 4 ++++ godo.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abc8d7e..e13c149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## unreleased +## [v1.34.0] - 2020-03-30 + +- #320 Add VPC v3 attributes - @viola + ## [v1.33.1] - 2020-03-23 - #318 upgrade github.com/stretchr/objx past 0.1.1 - @hilary diff --git a/godo.go b/godo.go index bd20cbe..70caacb 100644 --- a/godo.go +++ b/godo.go @@ -18,7 +18,7 @@ import ( ) const ( - libraryVersion = "1.33.1" + libraryVersion = "1.34.0" defaultBaseURL = "https://api.digitalocean.com/" userAgent = "godo/" + libraryVersion mediaType = "application/json" From 60ef1725ced43e5cfabf38b8e644ce33f44e3997 Mon Sep 17 00:00:00 2001 From: Hilary Holz Date: Mon, 30 Mar 2020 18:52:00 -0700 Subject: [PATCH 02/23] update CONTRIBUTING --- CONTRIBUTING.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 67cd6bd..33f0313 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 From 73f7c94c97b2bd94f724caa68cba98f06f62af9b Mon Sep 17 00:00:00 2001 From: Hilary Holz Date: Tue, 31 Mar 2020 10:12:20 -0700 Subject: [PATCH 03/23] switch CI from travis to github actions --- .github/workflows/ci.yml | 25 +++++++++++++++++++++++++ .travis.yml | 15 --------------- 2 files changed, 25 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..21b4ca3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +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: shoukoo/golang-pipeline/go1.11/test@master + + - name: go1.12 test + uses: shoukoo/golang-pipeline/go1.12/test@master + + - name: go1.13 test + uses: shoukoo/golang-pipeline/go1.13/test@master diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2099787..0000000 --- a/.travis.yml +++ /dev/null @@ -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 From e980c8dc5488e1b029e7f7e9b8e750ba3b53b726 Mon Sep 17 00:00:00 2001 From: Ben Tranter Date: Wed, 8 Apr 2020 11:49:03 -0400 Subject: [PATCH 04/23] Upgrade godo to Go 1.14 Upgrades godo to Go 1.14, and updates a few dependencies. This can't be merged until support for Go 1.14 is added to the GitHub Action we depend on. --- .github/workflows/ci.yml | 6 ++++++ go.mod | 7 +++++-- go.sum | 17 +++++++++++++++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21b4ca3..058bbd0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,3 +23,9 @@ jobs: - name: go1.13 test uses: shoukoo/golang-pipeline/go1.13/test@master + + # Support for go1.14 is waiting on + # https://github.com/shoukoo/golang-pipeline/pull/7 + # + # - name: go1.14 test + # uses: shoukoo/golang-pipeline/go1.14/test@master diff --git a/go.mod b/go.mod index c7e16a1..1b3626e 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,14 @@ 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 diff --git a/go.sum b/go.sum index 617c0b3..a4264d7 100644 --- a/go.sum +++ b/go.sum @@ -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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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/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 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= 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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= 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= From 2e903b10dc1c95fd556f92a55086cdb13b87dc18 Mon Sep 17 00:00:00 2001 From: Ben Tranter Date: Thu, 16 Apr 2020 17:29:49 -0400 Subject: [PATCH 05/23] Use DigitalOcean owned golang-pipeline Updates our CI workflow to use the DigitalOcean owned `golang-pipeline`, since it includes support for Go 1.14. --- .github/workflows/ci.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 058bbd0..c557202 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,16 +16,13 @@ jobs: uses: actions/checkout@v2 - name: go1.11 test - uses: shoukoo/golang-pipeline/go1.11/test@master + uses: digitalocean/golang-pipeline/go1.11/test@master - name: go1.12 test - uses: shoukoo/golang-pipeline/go1.12/test@master + uses: digitalocean/golang-pipeline/go1.12/test@master - name: go1.13 test - uses: shoukoo/golang-pipeline/go1.13/test@master + uses: digitalocean/golang-pipeline/go1.13/test@master - # Support for go1.14 is waiting on - # https://github.com/shoukoo/golang-pipeline/pull/7 - # - # - name: go1.14 test - # uses: shoukoo/golang-pipeline/go1.14/test@master + - name: go1.14 test + uses: digitalocean/golang-pipeline/go1.14/test@master From e3d9d25849b9b1cd7da147b7435e7f9cb5225e25 Mon Sep 17 00:00:00 2001 From: Ben Tranter Date: Fri, 17 Apr 2020 12:37:26 -0400 Subject: [PATCH 06/23] Run go mod vendor and go mod tidy --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index a4264d7..0f02ed7 100644 --- a/go.sum +++ b/go.sum @@ -25,14 +25,12 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7 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 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= 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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= 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= From 790f5201304eeaf3570b760a259d61ee18d18ed1 Mon Sep 17 00:00:00 2001 From: Nicholas Tate Date: Tue, 14 Apr 2020 13:33:09 -0700 Subject: [PATCH 07/23] Add support for registry/ListRepos and registry/ListRepoTags --- registry.go | 88 +++++++++++++++++++++++++ registry_test.go | 163 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 243 insertions(+), 8 deletions(-) diff --git a/registry.go b/registry.go index d20d0c3..bc50242 100644 --- a/registry.go +++ b/registry.go @@ -22,6 +22,8 @@ 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) } var _ RegistryService = &RegistryServiceOp{} @@ -48,10 +50,40 @@ 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"` +} + +// 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) @@ -121,3 +153,59 @@ 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, 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 +} diff --git a/registry_test.go b/registry_test.go index adaab74..e906278 100644 --- a/registry_test.go +++ b/registry_test.go @@ -7,18 +7,31 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +const ( + testRegistry = "test-registry" + testRepository = "test-repository" + 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 +41,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 +68,13 @@ func TestRegistry_Get(t *testing.T) { defer teardown() want := &Registry{ - Name: "foo", + Name: testRegistry, } getResponseJSON := ` { "registry": { - "name": "foo" + "name": "` + testRegistry + `" } }` @@ -122,3 +135,137 @@ func TestRegistry_DockerCredentials(t *testing.T) { }) } } + +func TestRepository_List(t *testing.T) { + setup() + defer teardown() + + wantRepositories := []*Repository{ + { + RegistryName: testRegistry, + Name: testRepository, + LatestTag: &RepositoryTag{ + RegistryName: testRegistry, + Repository: testRepository, + Tag: testTag, + ManifestDigest: testDigest, + CompressedSizeBytes: testCompressedSize, + SizeBytes: testSize, + UpdatedAt: testTime, + }, + }, + } + getResponseJSON := `{ + "repositories": [ + { + "registry_name": "` + testRegistry + `", + "name": "` + testRepository + `", + "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/` + testRepository + `/tags?page=2", + "last": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/repositories/` + testRepository + `/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, testRepository), + Last: fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositories/%s/tags?page=2", testRegistry, testRepository), + }, + } + assert.Equal(t, wantRespLinks, gotRespLinks) + + gotRespMeta := response.Meta + wantRespMeta := &Meta{ + Total: 2, + } + assert.Equal(t, wantRespMeta, gotRespMeta) +} From 2cc365a087bb5ecb531753292358784cb36972d0 Mon Sep 17 00:00:00 2001 From: Nicholas Tate Date: Tue, 14 Apr 2020 13:45:06 -0700 Subject: [PATCH 08/23] Add support for registry/DeleteTag and registry/DeleteManifest --- registry.go | 32 ++++++++++++++++++++++++++++++++ registry_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/registry.go b/registry.go index bc50242..ea94072 100644 --- a/registry.go +++ b/registry.go @@ -24,6 +24,8 @@ type RegistryService interface { 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{} @@ -209,3 +211,33 @@ func (svc *RegistryServiceOp) ListRepositoryTags(ctx context.Context, registry, 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, 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, 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 +} diff --git a/registry_test.go b/registry_test.go index e906278..b11d4bf 100644 --- a/registry_test.go +++ b/registry_test.go @@ -269,3 +269,27 @@ func TestRepository_ListTags(t *testing.T) { } 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) +} From 82e7afa35f7964e06979acc69209acf00c9946f4 Mon Sep 17 00:00:00 2001 From: Nicholas Tate Date: Mon, 20 Apr 2020 14:38:18 -0700 Subject: [PATCH 09/23] Add TagCount field to registry/Repository --- registry.go | 1 + registry_test.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/registry.go b/registry.go index ea94072..3f8c957 100644 --- a/registry.go +++ b/registry.go @@ -57,6 +57,7 @@ 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 diff --git a/registry_test.go b/registry_test.go index b11d4bf..8870b7a 100644 --- a/registry_test.go +++ b/registry_test.go @@ -144,6 +144,7 @@ func TestRepository_List(t *testing.T) { { RegistryName: testRegistry, Name: testRepository, + TagCount: 1, LatestTag: &RepositoryTag{ RegistryName: testRegistry, Repository: testRepository, @@ -160,6 +161,7 @@ func TestRepository_List(t *testing.T) { { "registry_name": "` + testRegistry + `", "name": "` + testRepository + `", + "tag_count": 1, "latest_tag": { "registry_name": "` + testRegistry + `", "repository": "` + testRepository + `", From d6f1faf2405dde2d4efbcfda3ca13fb60b980939 Mon Sep 17 00:00:00 2001 From: Nicholas Tate Date: Mon, 20 Apr 2020 15:27:32 -0700 Subject: [PATCH 10/23] Prepare v1.35.0 release --- CHANGELOG.md | 6 ++++++ godo.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e13c149..d86cc62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## unreleased +## [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 diff --git a/godo.go b/godo.go index 70caacb..6438b7b 100644 --- a/godo.go +++ b/godo.go @@ -18,7 +18,7 @@ import ( ) const ( - libraryVersion = "1.34.0" + libraryVersion = "1.35.0" defaultBaseURL = "https://api.digitalocean.com/" userAgent = "godo/" + libraryVersion mediaType = "application/json" From eeae08cdad206118c0a766861de59a1e39434e5d Mon Sep 17 00:00:00 2001 From: Ben Tranter Date: Tue, 21 Apr 2020 12:33:22 -0400 Subject: [PATCH 11/23] Update vulnerable x/crypto dependency Updates the vulnerable `golang.org/x/crypto` dependency. The `golang.org/x/crypto` package is affected by https://nvd.nist.gov/vuln/detail/CVE-2019-11840. This PR bumps the library to an updated version that doesn't contain the vulnerability. --- go.mod | 2 ++ go.sum | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 1b3626e..36753b1 100644 --- a/go.mod +++ b/go.mod @@ -12,3 +12,5 @@ require ( ) 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 diff --git a/go.sum b/go.sum index 0f02ed7..ccd0f08 100644 --- a/go.sum +++ b/go.sum @@ -16,10 +16,11 @@ 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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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/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= @@ -27,6 +28,7 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BG 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= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= From 2323bdd6120f8cd4ec5691456f3c28142a6390f0 Mon Sep 17 00:00:00 2001 From: Ben Tranter Date: Tue, 21 Apr 2020 13:10:26 -0400 Subject: [PATCH 12/23] Prepare to release v1.35.1 --- CHANGELOG.md | 4 ++++ godo.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d86cc62..77b97f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## unreleased +## [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 diff --git a/godo.go b/godo.go index 6438b7b..4b02850 100644 --- a/godo.go +++ b/godo.go @@ -18,7 +18,7 @@ import ( ) const ( - libraryVersion = "1.35.0" + libraryVersion = "1.35.1" defaultBaseURL = "https://api.digitalocean.com/" userAgent = "godo/" + libraryVersion mediaType = "application/json" From 5e553cdf279c0538b06e53965fa07c241e95e5ad Mon Sep 17 00:00:00 2001 From: Ben Tranter Date: Tue, 21 Apr 2020 13:27:45 -0400 Subject: [PATCH 13/23] Fix typo in CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77b97f0..0d31956 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ## [v1.35.1] - 2020-04-21 -= #328 Update vulnerable x/crypto dependency - @bentranter +- #328 Update vulnerable x/crypto dependency - @bentranter ## [v1.35.0] - 2020-04-20 From 758ae6ca3d97db8b3f53d490b08524b743a8c71d Mon Sep 17 00:00:00 2001 From: Andrew Starr-Bochicchio Date: Tue, 28 Apr 2020 15:00:35 -0400 Subject: [PATCH 14/23] Expose expiry_seconds for Registry.DockerCredentials. --- registry.go | 14 +++++++++++--- registry_test.go | 20 +++++++++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/registry.go b/registry.go index 3f8c957..3c17887 100644 --- a/registry.go +++ b/registry.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "net/http" + "strconv" "time" ) @@ -43,7 +44,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. @@ -138,13 +140,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 { diff --git a/registry_test.go b/registry_test.go index 8870b7a..e407026 100644 --- a/registry_test.go +++ b/registry_test.go @@ -102,9 +102,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)", @@ -116,6 +117,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 { @@ -125,6 +138,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) }) From 859aab72af6f62e261f20f97bd33cb5e5c0ba794 Mon Sep 17 00:00:00 2001 From: Andrew Starr-Bochicchio Date: Tue, 12 May 2020 16:01:35 -0400 Subject: [PATCH 15/23] Prepare v1.36.0 release. --- CHANGELOG.md | 4 +++- godo.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d31956..8b79129 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Change Log -## unreleased +## [v1.36.0] - 2020-05-12 + +- #331 Expose expiry_seconds for Registry.DockerCredentials. @andrewsomething ## [v1.35.1] - 2020-04-21 diff --git a/godo.go b/godo.go index 4b02850..9a21c8d 100644 --- a/godo.go +++ b/godo.go @@ -18,7 +18,7 @@ import ( ) const ( - libraryVersion = "1.35.1" + libraryVersion = "1.36.0" defaultBaseURL = "https://api.digitalocean.com/" userAgent = "godo/" + libraryVersion mediaType = "application/json" From d2202c277b7a2b381468ad74d429613f6778cad2 Mon Sep 17 00:00:00 2001 From: Adam Wolfe Gordon Date: Fri, 29 May 2020 11:21:04 -0600 Subject: [PATCH 16/23] registry: URL encode repository names when building URLs (#336) It's legal for image repositories to have `/` characters in their names. Our API handles this by allowing for URL-encoded repository names - i.e., to fetch tags for a repository called `bar/baz` in registry `foo` you make a request to `/v2/registry/foo/repositories/bar%2Fbaz/tags`. --- registry.go | 7 ++++--- registry_test.go | 21 +++++++++++---------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/registry.go b/registry.go index 3c17887..1b5c40b 100644 --- a/registry.go +++ b/registry.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "net/http" + "net/url" "strconv" "time" ) @@ -195,7 +196,7 @@ func (svc *RegistryServiceOp) ListRepositories(ctx context.Context, registry str // 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, repository) + 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 @@ -223,7 +224,7 @@ func (svc *RegistryServiceOp) ListRepositoryTags(ctx context.Context, registry, // 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, repository, tag) + 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 @@ -238,7 +239,7 @@ func (svc *RegistryServiceOp) DeleteTag(ctx context.Context, registry, repositor // 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, repository, digest) + 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 diff --git a/registry_test.go b/registry_test.go index e407026..898d614 100644 --- a/registry_test.go +++ b/registry_test.go @@ -12,12 +12,13 @@ import ( ) const ( - testRegistry = "test-registry" - testRepository = "test-repository" - testTag = "test-tag" - testDigest = "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f" - testCompressedSize = 2789669 - testSize = 5843968 + testRegistry = "test-registry" + testRepository = "test/repository" + testEncodedRepository = "test%2Frepository" + testTag = "test-tag" + testDigest = "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f" + testCompressedSize = 2789669 + testSize = 5843968 ) var ( @@ -252,8 +253,8 @@ func TestRepository_ListTags(t *testing.T) { ], "links": { "pages": { - "next": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/repositories/` + testRepository + `/tags?page=2", - "last": "https://api.digitalocean.com/v2/registry/` + testRegistry + `/repositories/` + testRepository + `/tags?page=2" + "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": { @@ -273,8 +274,8 @@ func TestRepository_ListTags(t *testing.T) { gotRespLinks := response.Links wantRespLinks := &Links{ Pages: &Pages{ - Next: fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositories/%s/tags?page=2", testRegistry, testRepository), - Last: fmt.Sprintf("https://api.digitalocean.com/v2/registry/%s/repositories/%s/tags?page=2", testRegistry, testRepository), + 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) From e8d8ac6401bf6037e051649e926f296c5aaeefcd Mon Sep 17 00:00:00 2001 From: Scott Crawford Date: Thu, 28 May 2020 17:29:52 -0400 Subject: [PATCH 17/23] add 1-click service and request --- 1-click.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 1-click_test.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++ godo.go | 2 ++ 3 files changed, 103 insertions(+) create mode 100644 1-click.go create mode 100644 1-click_test.go diff --git a/1-click.go b/1-click.go new file mode 100644 index 0000000..faa2558 --- /dev/null +++ b/1-click.go @@ -0,0 +1,52 @@ +package godo + +import ( + "context" + "fmt" + "net/http" +) + +const oneClickBasePath = "/v2/1-click" + +// OneClickService is an interface for interacting with 1-clicks with the +// DigitalOcean API. +// See: https://developers.digitalocean.com/documentation/v2#vpcs +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:"list"` +} + +// List returns a list of the caller's VPCs, with optional pagination. +func (v *OneClickServiceOp) List(ctx context.Context, oneClickType string) ([]*OneClick, *Response, error) { + path := fmt.Sprintf(`%s?type=%s`, oneClickBasePath, oneClickType) + + req, err := v.client.NewRequest(ctx, http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(OneClicksRoot) + resp, err := v.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.List, resp, nil +} diff --git a/1-click_test.go b/1-click_test.go new file mode 100644 index 0000000..0109d05 --- /dev/null +++ b/1-click_test.go @@ -0,0 +1,49 @@ +package godo + +import ( + "fmt" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var vTestOneClick = &OneClick{ + Slug: "test-slug", + Type: "droplet", +} + +var vTestOneClickJSON = ` + { + "slug":"test-slug", + "type":"droplet" + } +` + +func TestOneClick_List(t *testing.T) { + setup() + defer teardown() + + svc := client.OneClick + path := "/v2/1-click" + want := []*OneClick{ + vTestOneClick, + } + + jsonBlob := ` +{ + "list": [ +` + vTestOneClickJSON + ` + ] +} +` + 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) +} diff --git a/godo.go b/godo.go index 9a21c8d..b4b5e1f 100644 --- a/godo.go +++ b/godo.go @@ -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 } From 21e7d88687f5dd5be6bb43be5881fe079ed9df5a Mon Sep 17 00:00:00 2001 From: Scott Crawford Date: Fri, 29 May 2020 10:33:18 -0400 Subject: [PATCH 18/23] remove bad link and preceeding slash --- 1-click.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/1-click.go b/1-click.go index faa2558..d76bba3 100644 --- a/1-click.go +++ b/1-click.go @@ -6,11 +6,10 @@ import ( "net/http" ) -const oneClickBasePath = "/v2/1-click" +const oneClickBasePath = "v2/1-click" // OneClickService is an interface for interacting with 1-clicks with the // DigitalOcean API. -// See: https://developers.digitalocean.com/documentation/v2#vpcs type OneClickService interface { List(context.Context, string) ([]*OneClick, *Response, error) } From a5e3abb3991e6f479e61b2593ac3f46da55ecb37 Mon Sep 17 00:00:00 2001 From: Scott Crawford Date: Fri, 29 May 2020 10:49:58 -0400 Subject: [PATCH 19/23] clean up comments and variables --- 1-click.go | 8 ++++---- 1-click_test.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/1-click.go b/1-click.go index d76bba3..5d6310f 100644 --- a/1-click.go +++ b/1-click.go @@ -32,17 +32,17 @@ type OneClicksRoot struct { List []*OneClick `json:"list"` } -// List returns a list of the caller's VPCs, with optional pagination. -func (v *OneClickServiceOp) List(ctx context.Context, oneClickType string) ([]*OneClick, *Response, error) { +// 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 := v.client.NewRequest(ctx, http.MethodGet, path, nil) + req, err := ocs.client.NewRequest(ctx, http.MethodGet, path, nil) if err != nil { return nil, nil, err } root := new(OneClicksRoot) - resp, err := v.client.Do(ctx, req, root) + resp, err := ocs.client.Do(ctx, req, root) if err != nil { return nil, resp, err } diff --git a/1-click_test.go b/1-click_test.go index 0109d05..5d9c9e3 100644 --- a/1-click_test.go +++ b/1-click_test.go @@ -9,12 +9,12 @@ import ( "github.com/stretchr/testify/require" ) -var vTestOneClick = &OneClick{ +var testOneClick = &OneClick{ Slug: "test-slug", Type: "droplet", } -var vTestOneClickJSON = ` +var testOneClickJSON = ` { "slug":"test-slug", "type":"droplet" @@ -28,13 +28,13 @@ func TestOneClick_List(t *testing.T) { svc := client.OneClick path := "/v2/1-click" want := []*OneClick{ - vTestOneClick, + testOneClick, } jsonBlob := ` { "list": [ -` + vTestOneClickJSON + ` +` + testOneClickJSON + ` ] } ` From 550d20bc87c9e2f30e6a1cf9ca25281dc03c41fc Mon Sep 17 00:00:00 2001 From: Scott Crawford Date: Mon, 1 Jun 2020 16:43:32 -0400 Subject: [PATCH 20/23] update route and json payload --- 1-click.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/1-click.go b/1-click.go index 5d6310f..edec77f 100644 --- a/1-click.go +++ b/1-click.go @@ -6,10 +6,11 @@ import ( "net/http" ) -const oneClickBasePath = "v2/1-click" +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-clicks type OneClickService interface { List(context.Context, string) ([]*OneClick, *Response, error) } @@ -29,7 +30,7 @@ type OneClick struct { // OneClicksRoot is the root of the json payload that contains a list of 1-clicks type OneClicksRoot struct { - List []*OneClick `json:"list"` + List []*OneClick `json:"1_clicks"` } // List returns a list of the available 1-click applications. From 6e9396814bb057f00c1ec7c6926fba5b8ab70f23 Mon Sep 17 00:00:00 2001 From: Scott Crawford Date: Mon, 1 Jun 2020 16:46:30 -0400 Subject: [PATCH 21/23] update tests --- 1-click_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/1-click_test.go b/1-click_test.go index 5d9c9e3..7b11f40 100644 --- a/1-click_test.go +++ b/1-click_test.go @@ -26,14 +26,14 @@ func TestOneClick_List(t *testing.T) { defer teardown() svc := client.OneClick - path := "/v2/1-click" + path := "/v2/1-clicks" want := []*OneClick{ testOneClick, } jsonBlob := ` { - "list": [ + "1_clicks": [ ` + testOneClickJSON + ` ] } From 04d89bc887c5341131d3de80ff005b4920ff1478 Mon Sep 17 00:00:00 2001 From: Scott Crawford Date: Mon, 1 Jun 2020 16:49:33 -0400 Subject: [PATCH 22/23] update api docs link --- 1-click.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/1-click.go b/1-click.go index edec77f..fab04fe 100644 --- a/1-click.go +++ b/1-click.go @@ -10,7 +10,7 @@ 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-clicks +// See: https://developers.digitalocean.com/documentation/v2/#1-click-applications type OneClickService interface { List(context.Context, string) ([]*OneClick, *Response, error) } From 6c3f52d4d7f15e04456df3dad502b3e93af46103 Mon Sep 17 00:00:00 2001 From: Andrew Starr-Bochicchio Date: Mon, 1 Jun 2020 17:15:44 -0400 Subject: [PATCH 23/23] Prepare v1.37.0 release. (#338) --- CHANGELOG.md | 5 +++++ godo.go | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b79129..c826205 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## [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 diff --git a/godo.go b/godo.go index b4b5e1f..c6dde8e 100644 --- a/godo.go +++ b/godo.go @@ -18,7 +18,7 @@ import ( ) const ( - libraryVersion = "1.36.0" + libraryVersion = "1.37.0" defaultBaseURL = "https://api.digitalocean.com/" userAgent = "godo/" + libraryVersion mediaType = "application/json"