Skip to content

Commit

Permalink
allow read-only creds for container registry login (#1379)
Browse files Browse the repository at this point in the history
* allow read-only creds for container registry login

Addresses #1375. Adds a new flag to login to the container registry
generating a read-only API token.

Still WIP.

* add tests for read-only registry login creds
  • Loading branch information
bentranter committed May 18, 2023
1 parent ebc57cc commit 6defab4
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 3 deletions.
2 changes: 2 additions & 0 deletions args.go
Expand Up @@ -379,6 +379,8 @@ const (
ArgRegistry = "registry"
// ArgRegistryExpirySeconds indicates the length of time the token will be valid in seconds.
ArgRegistryExpirySeconds = "expiry-seconds"
// ArgRegistryReadOnly indicates that a generated registry API token should be read-only.
ArgRegistryReadOnly = "read-only"
// ArgSubscriptionTier is a subscription tier slug.
ArgSubscriptionTier = "subscription-tier"
// ArgGCIncludeUntaggedManifests indicates that a garbage collection should delete
Expand Down
9 changes: 8 additions & 1 deletion commands/registry.go
Expand Up @@ -84,6 +84,8 @@ func Registry() *Command {
loginRegDesc, Writer)
AddIntFlag(cmdRegistryLogin, doctl.ArgRegistryExpirySeconds, "", 0,
"The length of time the registry credentials will be valid for in seconds. By default, the credentials do not expire.")
AddBoolFlag(cmdRegistryLogin, doctl.ArgRegistryReadOnly, "", false,
"If true, the DigitalOcean API token generated by the login command will be read-only, causing any push operations to fail. By default, the API token is read-write.")

logoutRegDesc := "This command logs Docker out of the private container registry, revoking access to it."
cmdRunRegistryLogout := CmdBuilder(cmd, RunRegistryLogout, "logout", "Log out Docker from a container registry",
Expand Down Expand Up @@ -379,8 +381,13 @@ func RunRegistryLogin(c *CmdConfig) error {
if err != nil {
return err
}
readOnly, err := c.Doit.GetBool(c.NS, doctl.ArgRegistryReadOnly)
if err != nil {
return err
}

regCredReq := godo.RegistryDockerCredentialsRequest{
ReadWrite: true,
ReadWrite: !readOnly,
}
if expirySeconds != 0 {
regCredReq.ExpirySeconds = godo.Int(expirySeconds)
Expand Down
13 changes: 13 additions & 0 deletions commands/registry_test.go
Expand Up @@ -639,6 +639,7 @@ func TestRegistryLogin(t *testing.T) {
tests := []struct {
name string
expirySeconds int
readOnly bool
expect func(m *mocks.MockRegistryService)
}{
{
Expand All @@ -662,6 +663,17 @@ func TestRegistryLogin(t *testing.T) {
}).Return(testDockerCredentials, nil)
},
},
{
name: "with-read-only",
expirySeconds: 0,
readOnly: true,
expect: func(m *mocks.MockRegistryService) {
m.EXPECT().Endpoint().Return(do.RegistryHostname)
m.EXPECT().DockerCredentials(&godo.RegistryDockerCredentialsRequest{
ReadWrite: false,
}).Return(testDockerCredentials, nil)
},
},
}

for _, test := range tests {
Expand All @@ -672,6 +684,7 @@ func TestRegistryLogin(t *testing.T) {
}

config.Doit.Set(config.NS, doctl.ArgRegistryExpirySeconds, test.expirySeconds)
config.Doit.Set(config.NS, doctl.ArgRegistryReadOnly, test.readOnly)

config.Out = os.Stderr
err := RunRegistryLogin(config)
Expand Down
44 changes: 42 additions & 2 deletions integration/registry_login_test.go
Expand Up @@ -47,11 +47,16 @@ var _ = suite("registry/login", func(t *testing.T, when spec.G, it spec.S) {
return
}

readWriteParam := req.URL.Query().Get("read_write")
expiryParam := req.URL.Query().Get("expiry_seconds")
if expiryParam == "3600" {
w.Write([]byte(registryDockerCredentialsExpiryResponse))
} else if expiryParam == "" {
w.Write([]byte(registryDockerCredentialsResponse))
if readWriteParam == "false" {
w.Write([]byte(registryDockerCredentialsReadOnlyRegistryResponse))
} else {
w.Write([]byte(registryDockerCredentialsResponse))
}
} else {
t.Fatalf("received unknown value: %s", expiryParam)
}
Expand Down Expand Up @@ -131,8 +136,43 @@ var _ = suite("registry/login", func(t *testing.T, when spec.G, it spec.S) {
}
})
})

when("read-only flag is passed", func() {
it("add the correct query parameter", func() {
tmpDir := t.TempDir()

config := filepath.Join(tmpDir, "config.json")

cmd := exec.Command(builtBinaryPath,
"-t", "some-magic-token",
"-u", server.URL,
"registry",
"login",
"--read-only",
"true",
)
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, fmt.Sprintf("DOCKER_CONFIG=%s", tmpDir))

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

fileBytes, err := ioutil.ReadFile(config)
expect.NoError(err)

var dc dockerConfig
err = json.Unmarshal(fileBytes, &dc)
expect.NoError(err)

expect.Equal("Logging Docker in to registry.digitalocean.com\n", string(output))
for host := range dc.Auths {
expect.Equal("readonlyregistry.registry.com", host)
}
})
})
})

const (
registryDockerCredentialsExpiryResponse = `{"auths":{"expiring.registry.com":{"auth":"Y3JlZGVudGlhbHM6dGhhdGV4cGlyZQ=="}}}`
registryDockerCredentialsExpiryResponse = `{"auths":{"expiring.registry.com":{"auth":"Y3JlZGVudGlhbHM6dGhhdGV4cGlyZQ=="}}}`
registryDockerCredentialsReadOnlyRegistryResponse = `{"auths":{"readonlyregistry.registry.com":{"auth":"Y3JlZGVudGlhbHM6dGhhdGV4cGlyZQ=="}}}`
)

0 comments on commit 6defab4

Please sign in to comment.