Skip to content
Permalink
Browse files
Support subscription tiers for DOCR (#911)
* registry: Remove [EA] specifier from registry commands

* vendor: Bump godo to 1.51.0

* registry: Add a command to list subscription tiers

* registry: Add a subscription tier argument to create registry
  • Loading branch information
adamwg committed Nov 3, 2020
1 parent b7eac68 commit e8181863f7e2ba614202d66f24922cd3e54a1f81
@@ -317,6 +317,8 @@ const (
ArgReadWrite = "read-write"
// ArgRegistryExpirySeconds indicates the length of time the token will be valid in seconds.
ArgRegistryExpirySeconds = "expiry-seconds"
// ArgSubscriptionTier is a subscription tier slug.
ArgSubscriptionTier = "subscription-tier"

// 1-Click Args

@@ -14,6 +14,7 @@ limitations under the License.
package displayers

import (
"fmt"
"io"

"github.com/digitalocean/doctl/do"
@@ -199,3 +200,59 @@ func (g *GarbageCollection) KV() []map[string]interface{} {

return out
}

type RegistrySubscriptionTiers struct {
SubscriptionTiers []do.RegistrySubscriptionTier
}

func (t *RegistrySubscriptionTiers) JSON(out io.Writer) error {
return writeJSON(t, out)
}

func (t *RegistrySubscriptionTiers) Cols() []string {
return []string{
"Name",
"Slug",
"IncludedRepositories",
"IncludedStorageBytes",
"AllowStorageOverage",
"IncludedBandwidthBytes",
"MonthlyPriceInCents",
"Eligible",
"EligibilityReasons",
}
}

func (t *RegistrySubscriptionTiers) ColMap() map[string]string {
return map[string]string{
"Name": "Name",
"Slug": "Slug",
"IncludedRepositories": "Included Repositories",
"IncludedStorageBytes": "Included Storage",
"AllowStorageOverage": "Storage Overage Allowed",
"IncludedBandwidthBytes": "Included Bandwidth",
"MonthlyPriceInCents": "Monthly Price",
"Eligible": "Eligible?",
"EligibilityReasons": "Not Eligible Because",
}
}

func (t *RegistrySubscriptionTiers) KV() []map[string]interface{} {
out := []map[string]interface{}{}

for _, tier := range t.SubscriptionTiers {
out = append(out, map[string]interface{}{
"Name": tier.Name,
"Slug": tier.Slug,
"IncludedRepositories": tier.IncludedRepositories,
"IncludedStorageBytes": BytesToHumanReadibleUnit(tier.IncludedStorageBytes),
"AllowStorageOverage": tier.AllowStorageOverage,
"IncludedBandwidthBytes": BytesToHumanReadibleUnit(tier.IncludedBandwidthBytes),
"MonthlyPriceInCents": fmt.Sprintf("$%d", tier.MonthlyPriceInCents / 100),
"Eligible": tier.Eligible,
"EligibilityReasons": tier.EligibilityReasons,
})
}

return out
}
@@ -52,10 +52,13 @@ func Registry() *Command {

cmd.AddCommand(Repository())
cmd.AddCommand(GarbageCollection())
cmd.AddCommand(RegistryOptions())

createRegDesc := "This command creates a new private container registry with the provided name."
CmdBuilder(cmd, RunRegistryCreate, "create <registry-name>",
cmdRunRegistryCreate := CmdBuilder(cmd, RunRegistryCreate, "create <registry-name>",
"Create a private container registry", createRegDesc, Writer)
AddStringFlag(cmdRunRegistryCreate, doctl.ArgSubscriptionTier, "", "basic",
"Subscription tier for the new registry. Possible values: see `doctl registry options subscription-tiers`", requiredOpt())

getRegDesc := "This command retrieves details about a private container registry including its name and the endpoint used to access it."
CmdBuilder(cmd, RunRegistryGet, "get", "Retrieve details about a container registry",
@@ -241,6 +244,23 @@ func GarbageCollection() *Command {
return cmd
}

// RegistryOptions creates the registry options subcommand
func RegistryOptions() *Command {
cmd := &Command{
Command: &cobra.Command{
Use: "options",
Aliases: []string{"opts", "o"},
Short: "List available container registry options",
Long: "This command lists options available when creating or updating a container registry.",
},
}

tiersDesc := "List available container registry subscription tiers"
CmdBuilder(cmd, RunRegistryOptionsTiers, "subscription-tiers", tiersDesc, tiersDesc, Writer, aliasOpt("tiers"))

return cmd
}

// Registry Run Commands

// RunRegistryCreate creates a registry
@@ -251,9 +271,17 @@ func RunRegistryCreate(c *CmdConfig) error {
}

name := c.Args[0]
subscriptionTier, err := c.Doit.GetString(c.NS, doctl.ArgSubscriptionTier)
if err != nil {
return err
}

rs := c.Registry()

rcr := &godo.RegistryCreateRequest{Name: name}
rcr := &godo.RegistryCreateRequest{
Name: name,
SubscriptionTierSlug: subscriptionTier,
}
r, err := rs.Create(rcr)
if err != nil {
return err
@@ -719,3 +747,15 @@ func displayGarbageCollections(c *CmdConfig, garbageCollections ...do.GarbageCol
}
return c.Display(item)
}

func RunRegistryOptionsTiers(c *CmdConfig) error {
tiers, err := c.Registry().GetSubscriptionTiers()
if err != nil {
return err
}

item := &displayers.RegistrySubscriptionTiers{
SubscriptionTiers: tiers,
}
return c.Display(item)
}
@@ -31,11 +31,12 @@ import (
)

var (
testRegistryName = "container-registry"
invalidRegistryName = "invalid-container-registry"
testRegistry = do.Registry{Registry: &godo.Registry{Name: testRegistryName}}
testRepoName = "test-repository"
testRepositoryTag = do.RepositoryTag{
testRegistryName = "container-registry"
testSubscriptionTier = "basic"
invalidRegistryName = "invalid-container-registry"
testRegistry = do.Registry{Registry: &godo.Registry{Name: testRegistryName}}
testRepoName = "test-repository"
testRepositoryTag = do.RepositoryTag{
RepositoryTag: &godo.RepositoryTag{
RegistryName: testRegistryName,
Repository: testRepoName,
@@ -80,7 +81,7 @@ var (
func TestRegistryCommand(t *testing.T) {
cmd := Registry()
assert.NotNil(t, cmd)
assertCommandNames(t, cmd, "create", "get", "delete", "login", "logout", "kubernetes-manifest", "repository", "docker-config", "garbage-collection")
assertCommandNames(t, cmd, "create", "get", "delete", "login", "logout", "options", "kubernetes-manifest", "repository", "docker-config", "garbage-collection")
}

func TestRepositoryCommand(t *testing.T) {
@@ -97,9 +98,13 @@ func TestGarbageCollectionCommand(t *testing.T) {

func TestRegistryCreate(t *testing.T) {
withTestClient(t, func(config *CmdConfig, tm *tcMocks) {
rcr := godo.RegistryCreateRequest{Name: testRegistryName}
rcr := godo.RegistryCreateRequest{
Name: testRegistryName,
SubscriptionTierSlug: testSubscriptionTier,
}
tm.registry.EXPECT().Create(&rcr).Return(&testRegistry, nil)
config.Args = append(config.Args, testRegistryName)
config.Doit.Set(config.NS, doctl.ArgSubscriptionTier, "basic")

err := RunRegistryCreate(config)
assert.NoError(t, err)

Some generated files are not rendered by default. Learn more.

@@ -43,6 +43,11 @@ type GarbageCollection struct {
*godo.GarbageCollection
}

// RegistrySubscriptionTier wraps a godo RegistrySubscriptionTier
type RegistrySubscriptionTier struct {
*godo.RegistrySubscriptionTier
}

// Endpoint returns the registry endpoint for image tagging
func (r *Registry) Endpoint() string {
return fmt.Sprintf("%s/%s", RegistryHostname, r.Registry.Name)
@@ -63,6 +68,7 @@ type RegistryService interface {
GetGarbageCollection(string) (*GarbageCollection, error)
ListGarbageCollections(string) ([]GarbageCollection, error)
CancelGarbageCollection(string, string) (*GarbageCollection, error)
GetSubscriptionTiers() ([]RegistrySubscriptionTier, error)
}

type registryService struct {
@@ -242,3 +248,17 @@ func (rs *registryService) CancelGarbageCollection(registry, garbageCollection s
func (rs *registryService) Endpoint() string {
return RegistryHostname
}

func (rs *registryService) GetSubscriptionTiers() ([]RegistrySubscriptionTier, error) {
opts, _, err := rs.client.Registry.GetOptions(rs.ctx)
if err != nil {
return nil, err
}

ret := make([]RegistrySubscriptionTier, len(opts.SubscriptionTiers))
for i, tier := range opts.SubscriptionTiers {
ret[i] = RegistrySubscriptionTier{tier}
}

return ret, nil
}
2 go.mod
@@ -8,7 +8,7 @@ require (
github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb // indirect
github.com/cpuguy83/go-md2man v1.0.10 // indirect
github.com/creack/pty v1.1.11
github.com/digitalocean/godo v1.50.0
github.com/digitalocean/godo v1.51.0
github.com/docker/cli v0.0.0-20200622130859-87db43814b48
github.com/docker/docker v17.12.0-ce-rc1.0.20200531234253-77e06fda0c94+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.3 // indirect
4 go.sum
@@ -95,8 +95,8 @@ 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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/digitalocean/godo v1.50.0 h1:ynl8HeBl77FCpuCSkBXgkzVLZNPumMGp7PEwYcNqJ1s=
github.com/digitalocean/godo v1.50.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU=
github.com/digitalocean/godo v1.51.0 h1:Hn+O5Hiy7SrRhuJXFaojkPe38OjgQU8L0sIV09ViI3U=
github.com/digitalocean/godo v1.51.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU=
github.com/docker/cli v0.0.0-20200622130859-87db43814b48 h1:AC8qbhi/SjYf4iN2W3jSsofZGHWPjG8pjf5P143KUM8=
github.com/docker/cli v0.0.0-20200622130859-87db43814b48/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v17.12.0-ce-rc1.0.20200531234253-77e06fda0c94+incompatible h1:PmGHHCZ43l6h8aZIi+Xa+z1SWe4dFImd5EK3TNp1jlo=
@@ -1,6 +1,7 @@
package integration

import (
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
@@ -15,8 +16,9 @@ import (

var _ = suite("registry/create", func(t *testing.T, when spec.G, it spec.S) {
var (
expect *require.Assertions
server *httptest.Server
expect *require.Assertions
server *httptest.Server
expectedTierSlug string
)

it.Before(func() {
@@ -41,7 +43,8 @@ var _ = suite("registry/create", func(t *testing.T, when spec.G, it spec.S) {
reqBody, err := ioutil.ReadAll(req.Body)
expect.NoError(err)

expect.JSONEq(registryCreateRequest, string(reqBody))
expectedJSON := fmt.Sprintf(registryCreateRequest, expectedTierSlug)
expect.JSONEq(expectedJSON, string(reqBody))
w.WriteHeader(http.StatusCreated)
w.Write([]byte(registryCreateResponse))
default:
@@ -63,6 +66,24 @@ var _ = suite("registry/create", func(t *testing.T, when spec.G, it spec.S) {
"create",
"my-registry",
)
expectedTierSlug = "basic"

output, err := cmd.CombinedOutput()
expect.NoError(err)

expect.Equal(strings.TrimSpace(registryGetOutput), strings.TrimSpace(string(output)))
})

it("creates a registry with subscription tier specified", func() {
cmd := exec.Command(builtBinaryPath,
"-t", "some-magic-token",
"-u", server.URL,
"registry",
"create",
"my-registry",
"--subscription-tier", "starter",
)
expectedTierSlug = "starter"

output, err := cmd.CombinedOutput()
expect.NoError(err)
@@ -74,7 +95,14 @@ var _ = suite("registry/create", func(t *testing.T, when spec.G, it spec.S) {
const (
registryCreateRequest = `
{
"name": "my-registry"
"name": "my-registry",
"subscription_tier_slug": "%s"
}
`
registryCreateRequestWithTier = `
{
"name": "my-registry",
"subscription_tier_slug": "basic"
}
`
registryCreateResponse = `

Some generated files are not rendered by default. Learn more.

Some generated files are not rendered by default. Learn more.

0 comments on commit e818186

Please sign in to comment.