Skip to content

Commit 0c444c5

Browse files
Merge pull request #1260 from dhiltgen/ce_q3
Add CLI support for running dockerd in a container on containerd
2 parents 3f7c6c8 + fd2f1b3 commit 0c444c5

408 files changed

Lines changed: 83935 additions & 75 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ clean: ## remove build artifacts
1212

1313
.PHONY: test-unit
1414
test-unit: ## run unit test
15-
./scripts/test/unit $(shell go list ./... | grep -vE '/vendor/|/e2e/')
15+
./scripts/test/unit $(shell go list ./... | grep -vE '/vendor/|/e2e/|/e2eengine/')
1616

1717
.PHONY: test
1818
test: test-unit ## run tests
1919

2020
.PHONY: test-coverage
2121
test-coverage: ## run test coverage
22-
./scripts/test/unit-with-coverage $(shell go list ./... | grep -vE '/vendor/|/e2e/')
22+
./scripts/test/unit-with-coverage $(shell go list ./... | grep -vE '/vendor/|/e2e/|/e2eengine/')
2323

2424
.PHONY: lint
2525
lint: ## run all the lint tools

cli/command/cli.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
manifeststore "github.com/docker/cli/cli/manifest/store"
2020
registryclient "github.com/docker/cli/cli/registry/client"
2121
"github.com/docker/cli/cli/trust"
22+
"github.com/docker/cli/internal/containerizedengine"
2223
dopts "github.com/docker/cli/opts"
2324
"github.com/docker/docker/api"
2425
"github.com/docker/docker/api/types"
@@ -54,6 +55,7 @@ type Cli interface {
5455
ManifestStore() manifeststore.Store
5556
RegistryClient(bool) registryclient.RegistryClient
5657
ContentTrustEnabled() bool
58+
NewContainerizedEngineClient(sockPath string) (containerizedengine.Client, error)
5759
}
5860

5961
// DockerCli is an instance the docker command line client.
@@ -229,6 +231,11 @@ func (cli *DockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions
229231
return trust.GetNotaryRepository(cli.In(), cli.Out(), UserAgent(), imgRefAndAuth.RepoInfo(), imgRefAndAuth.AuthConfig(), actions...)
230232
}
231233

234+
// NewContainerizedEngineClient returns a containerized engine client
235+
func (cli *DockerCli) NewContainerizedEngineClient(sockPath string) (containerizedengine.Client, error) {
236+
return containerizedengine.NewClient(sockPath)
237+
}
238+
232239
// ServerInfo stores details about the supported features and platform of the
233240
// server
234241
type ServerInfo struct {

cli/command/commands/commands.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/docker/cli/cli/command/checkpoint"
99
"github.com/docker/cli/cli/command/config"
1010
"github.com/docker/cli/cli/command/container"
11+
"github.com/docker/cli/cli/command/engine"
1112
"github.com/docker/cli/cli/command/image"
1213
"github.com/docker/cli/cli/command/manifest"
1314
"github.com/docker/cli/cli/command/network"
@@ -84,6 +85,9 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) {
8485
// volume
8586
volume.NewVolumeCommand(dockerCli),
8687

88+
// engine
89+
engine.NewEngineCommand(dockerCli),
90+
8791
// legacy commands may be hidden
8892
hide(system.NewEventsCommand(dockerCli)),
8993
hide(system.NewInfoCommand(dockerCli)),

cli/command/engine/activate.go

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package engine
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/docker/cli/cli/command"
8+
"github.com/docker/cli/cli/command/formatter"
9+
"github.com/docker/cli/internal/containerizedengine"
10+
"github.com/docker/cli/internal/licenseutils"
11+
"github.com/docker/docker/api/types"
12+
"github.com/docker/licensing/model"
13+
"github.com/pkg/errors"
14+
"github.com/spf13/cobra"
15+
)
16+
17+
type activateOptions struct {
18+
licenseFile string
19+
version string
20+
registryPrefix string
21+
format string
22+
image string
23+
quiet bool
24+
displayOnly bool
25+
sockPath string
26+
}
27+
28+
// newActivateCommand creates a new `docker engine activate` command
29+
func newActivateCommand(dockerCli command.Cli) *cobra.Command {
30+
var options activateOptions
31+
32+
cmd := &cobra.Command{
33+
Use: "activate [OPTIONS]",
34+
Short: "Activate Enterprise Edition",
35+
Long: `Activate Enterprise Edition.
36+
37+
With this command you may apply an existing Docker enterprise license, or
38+
interactively download one from Docker. In the interactive exchange, you can
39+
sign up for a new trial, or download an existing license. If you are
40+
currently running a Community Edition engine, the daemon will be updated to
41+
the Enterprise Edition Docker engine with additional capabilities and long
42+
term support.
43+
44+
For more information about different Docker Enterprise license types visit
45+
https://www.docker.com/licenses
46+
47+
For non-interactive scriptable deployments, download your license from
48+
https://hub.docker.com/ then specify the file with the '--license' flag.
49+
`,
50+
RunE: func(cmd *cobra.Command, args []string) error {
51+
return runActivate(dockerCli, options)
52+
},
53+
}
54+
55+
flags := cmd.Flags()
56+
57+
flags.StringVar(&options.licenseFile, "license", "", "License File")
58+
flags.StringVar(&options.version, "version", "", "Specify engine version (default is to use currently running version)")
59+
flags.StringVar(&options.registryPrefix, "registry-prefix", "docker.io/docker", "Override the default location where engine images are pulled")
60+
flags.StringVar(&options.image, "engine-image", containerizedengine.EnterpriseEngineImage, "Specify engine image")
61+
flags.StringVar(&options.format, "format", "", "Pretty-print licenses using a Go template")
62+
flags.BoolVar(&options.displayOnly, "display-only", false, "only display the available licenses and exit")
63+
flags.BoolVar(&options.quiet, "quiet", false, "Only display available licenses by ID")
64+
flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")
65+
66+
return cmd
67+
}
68+
69+
func runActivate(cli command.Cli, options activateOptions) error {
70+
ctx := context.Background()
71+
client, err := cli.NewContainerizedEngineClient(options.sockPath)
72+
if err != nil {
73+
return errors.Wrap(err, "unable to access local containerd")
74+
}
75+
defer client.Close()
76+
77+
authConfig, err := getRegistryAuth(cli, options.registryPrefix)
78+
if err != nil {
79+
return err
80+
}
81+
82+
var license *model.IssuedLicense
83+
84+
// Lookup on hub if no license provided via params
85+
if options.licenseFile == "" {
86+
if license, err = getLicenses(ctx, authConfig, cli, options); err != nil {
87+
return err
88+
}
89+
if options.displayOnly {
90+
return nil
91+
}
92+
} else {
93+
if license, err = licenseutils.LoadLocalIssuedLicense(ctx, options.licenseFile); err != nil {
94+
return err
95+
}
96+
}
97+
if err = licenseutils.ApplyLicense(ctx, cli.Client(), license); err != nil {
98+
return err
99+
}
100+
101+
opts := containerizedengine.EngineInitOptions{
102+
RegistryPrefix: options.registryPrefix,
103+
EngineImage: options.image,
104+
EngineVersion: options.version,
105+
}
106+
107+
return client.ActivateEngine(ctx, opts, cli.Out(), authConfig,
108+
func(ctx context.Context) error {
109+
client := cli.Client()
110+
_, err := client.Ping(ctx)
111+
return err
112+
})
113+
}
114+
115+
func getLicenses(ctx context.Context, authConfig *types.AuthConfig, cli command.Cli, options activateOptions) (*model.IssuedLicense, error) {
116+
user, err := licenseutils.Login(ctx, authConfig)
117+
if err != nil {
118+
return nil, err
119+
}
120+
fmt.Fprintf(cli.Out(), "Looking for existing licenses for %s...\n", user.User.Username)
121+
subs, err := user.GetAvailableLicenses(ctx)
122+
if err != nil {
123+
return nil, err
124+
}
125+
if len(subs) == 0 {
126+
return doTrialFlow(ctx, cli, user)
127+
}
128+
129+
format := options.format
130+
if len(format) == 0 {
131+
format = formatter.TableFormatKey
132+
}
133+
134+
updatesCtx := formatter.Context{
135+
Output: cli.Out(),
136+
Format: formatter.NewSubscriptionsFormat(format, options.quiet),
137+
Trunc: false,
138+
}
139+
if err := formatter.SubscriptionsWrite(updatesCtx, subs); err != nil {
140+
return nil, err
141+
}
142+
if options.displayOnly {
143+
return nil, nil
144+
}
145+
fmt.Fprintf(cli.Out(), "Please pick a license by number: ")
146+
var num int
147+
if _, err := fmt.Fscan(cli.In(), &num); err != nil {
148+
return nil, errors.Wrap(err, "failed to read user input")
149+
}
150+
if num < 0 || num >= len(subs) {
151+
return nil, fmt.Errorf("invalid choice")
152+
}
153+
return user.GetIssuedLicense(ctx, subs[num].ID)
154+
}
155+
156+
func doTrialFlow(ctx context.Context, cli command.Cli, user licenseutils.HubUser) (*model.IssuedLicense, error) {
157+
if !command.PromptForConfirmation(cli.In(), cli.Out(),
158+
"No existing licenses found, would you like to set up a new Enterprise Basic Trial license?") {
159+
return nil, fmt.Errorf("you must have an existing enterprise license or generate a new trial to use the Enterprise Docker Engine")
160+
}
161+
targetID := user.User.ID
162+
// If the user is a member of any organizations, allow trials generated against them
163+
if len(user.Orgs) > 0 {
164+
fmt.Fprintf(cli.Out(), "%d\t%s\n", 0, user.User.Username)
165+
for i, org := range user.Orgs {
166+
fmt.Fprintf(cli.Out(), "%d\t%s\n", i+1, org.Orgname)
167+
}
168+
fmt.Fprintf(cli.Out(), "Please choose an account to generate the trial in:")
169+
var num int
170+
if _, err := fmt.Fscan(cli.In(), &num); err != nil {
171+
return nil, errors.Wrap(err, "failed to read user input")
172+
}
173+
if num < 0 || num > len(user.Orgs) {
174+
return nil, fmt.Errorf("invalid choice")
175+
}
176+
if num > 0 {
177+
targetID = user.Orgs[num-1].ID
178+
}
179+
}
180+
return user.GenerateTrialLicense(ctx, targetID)
181+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package engine
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/docker/cli/internal/containerizedengine"
8+
"gotest.tools/assert"
9+
)
10+
11+
func TestActivateNoContainerd(t *testing.T) {
12+
testCli.SetContainerizedEngineClient(
13+
func(string) (containerizedengine.Client, error) {
14+
return nil, fmt.Errorf("some error")
15+
},
16+
)
17+
cmd := newActivateCommand(testCli)
18+
cmd.Flags().Set("license", "invalidpath")
19+
cmd.SilenceUsage = true
20+
cmd.SilenceErrors = true
21+
err := cmd.Execute()
22+
assert.ErrorContains(t, err, "unable to access local containerd")
23+
}
24+
25+
func TestActivateBadLicense(t *testing.T) {
26+
testCli.SetContainerizedEngineClient(
27+
func(string) (containerizedengine.Client, error) {
28+
return &fakeContainerizedEngineClient{}, nil
29+
},
30+
)
31+
cmd := newActivateCommand(testCli)
32+
cmd.SilenceUsage = true
33+
cmd.SilenceErrors = true
34+
cmd.Flags().Set("license", "invalidpath")
35+
err := cmd.Execute()
36+
assert.Error(t, err, "open invalidpath: no such file or directory")
37+
}

cli/command/engine/auth.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package engine
2+
3+
import (
4+
"context"
5+
6+
"github.com/docker/cli/cli/command"
7+
"github.com/docker/cli/cli/trust"
8+
"github.com/docker/distribution/reference"
9+
"github.com/docker/docker/api/types"
10+
registrytypes "github.com/docker/docker/api/types/registry"
11+
"github.com/pkg/errors"
12+
)
13+
14+
func getRegistryAuth(cli command.Cli, registryPrefix string) (*types.AuthConfig, error) {
15+
if registryPrefix == "" {
16+
registryPrefix = "docker.io/docker"
17+
}
18+
distributionRef, err := reference.ParseNormalizedNamed(registryPrefix)
19+
if err != nil {
20+
return nil, errors.Wrapf(err, "failed to parse image name: %s", registryPrefix)
21+
}
22+
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(context.Background(), nil, authResolver(cli), distributionRef.String())
23+
if err != nil {
24+
return nil, errors.Wrap(err, "failed to get imgRefAndAuth")
25+
}
26+
return imgRefAndAuth.AuthConfig(), nil
27+
}
28+
29+
func authResolver(cli command.Cli) func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
30+
return func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
31+
return command.ResolveAuthConfig(ctx, cli, index)
32+
}
33+
}

0 commit comments

Comments
 (0)