Skip to content

Commit

Permalink
Add engine commands built on containerd
Browse files Browse the repository at this point in the history
This new collection of commands supports initializing a local
engine using containerd, updating that engine, and activating
the EE product

Signed-off-by: Daniel Hiltgen <[email protected]>
  • Loading branch information
Daniel Hiltgen committed Aug 17, 2018
1 parent 2706511 commit da958f9
Show file tree
Hide file tree
Showing 67 changed files with 5,283 additions and 18 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ clean: ## remove build artifacts

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

.PHONY: test
test: test-unit ## run tests

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

.PHONY: lint
lint: ## run all the lint tools
Expand Down
7 changes: 7 additions & 0 deletions cli/command/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
manifeststore "github.com/docker/cli/cli/manifest/store"
registryclient "github.com/docker/cli/cli/registry/client"
"github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/containerizedengine"
dopts "github.com/docker/cli/opts"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
Expand Down Expand Up @@ -53,6 +54,7 @@ type Cli interface {
ManifestStore() manifeststore.Store
RegistryClient(bool) registryclient.RegistryClient
ContentTrustEnabled() bool
NewContainerizedEngineClient(sockPath string) (containerizedengine.Client, error)
}

// DockerCli is an instance the docker command line client.
Expand Down Expand Up @@ -228,6 +230,11 @@ func (cli *DockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions
return trust.GetNotaryRepository(cli.In(), cli.Out(), UserAgent(), imgRefAndAuth.RepoInfo(), imgRefAndAuth.AuthConfig(), actions...)
}

// NewContainerizedEngineClient returns a containerized engine client
func (cli *DockerCli) NewContainerizedEngineClient(sockPath string) (containerizedengine.Client, error) {
return containerizedengine.NewClient(sockPath)
}

// ServerInfo stores details about the supported features and platform of the
// server
type ServerInfo struct {
Expand Down
4 changes: 4 additions & 0 deletions cli/command/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/docker/cli/cli/command/checkpoint"
"github.com/docker/cli/cli/command/config"
"github.com/docker/cli/cli/command/container"
"github.com/docker/cli/cli/command/engine"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/command/manifest"
"github.com/docker/cli/cli/command/network"
Expand Down Expand Up @@ -80,6 +81,9 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) {
// volume
volume.NewVolumeCommand(dockerCli),

// engine
engine.NewEngineCommand(dockerCli),

// legacy commands may be hidden
hide(system.NewEventsCommand(dockerCli)),
hide(system.NewInfoCommand(dockerCli)),
Expand Down
181 changes: 181 additions & 0 deletions cli/command/engine/activate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package engine

import (
"context"
"fmt"

"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/internal/containerizedengine"
"github.com/docker/cli/internal/licenseutils"
"github.com/docker/docker/api/types"
"github.com/docker/licensing/model"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)

type activateOptions struct {
licenseFile string
version string
registryPrefix string
format string
image string
quiet bool
displayOnly bool
sockPath string
}

// newActivateCommand creates a new `docker engine activate` command
func newActivateCommand(dockerCli command.Cli) *cobra.Command {
var options activateOptions

cmd := &cobra.Command{
Use: "activate [OPTIONS]",
Short: "Activate Enterprise Edition",
Long: `Activate Enterprise Edition.
With this command you may apply an existing Docker enterprise license, or
interactively download one from Docker. In the interactive exchange, you can
sign up for a new trial, or download an existing license. If you are
currently running a Community Edition engine, the daemon will be updated to
the Enterprise Edition Docker engine with additional capabilities and long
term support.
For more information about different Docker Enterprise license types visit
https://www.docker.com/licenses
For non-interactive scriptable deployments, download your license from
https://hub.docker.com/ then specify the file with the '--license' flag.
`,
RunE: func(cmd *cobra.Command, args []string) error {
return runActivate(dockerCli, options)
},
}

flags := cmd.Flags()

flags.StringVar(&options.licenseFile, "license", "", "License File")
flags.StringVar(&options.version, "version", "", "Specify engine version (default is to use currently running version)")
flags.StringVar(&options.registryPrefix, "registry-prefix", "docker.io/docker", "Override the default location where engine images are pulled")
flags.StringVar(&options.image, "engine-image", containerizedengine.EnterpriseEngineImage, "Specify engine image")
flags.StringVar(&options.format, "format", "", "Pretty-print licenses using a Go template")
flags.BoolVar(&options.displayOnly, "display-only", false, "only display the available licenses and exit")
flags.BoolVar(&options.quiet, "quiet", false, "Only display available licenses by ID")
flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")

return cmd
}

func runActivate(cli command.Cli, options activateOptions) error {
ctx := context.Background()
client, err := cli.NewContainerizedEngineClient(options.sockPath)
if err != nil {
return errors.Wrap(err, "unable to access local containerd")
}
defer client.Close()

authConfig, err := getRegistryAuth(cli, options.registryPrefix)
if err != nil {
return err
}

var license *model.IssuedLicense

// Lookup on hub if no license provided via params
if options.licenseFile == "" {
if license, err = getLicenses(ctx, authConfig, cli, options); err != nil {
return err
}
if options.displayOnly {
return nil
}
} else {
if license, err = licenseutils.LoadLocalIssuedLicense(ctx, options.licenseFile); err != nil {
return err
}
}
if err = licenseutils.ApplyLicense(ctx, cli.Client(), license); err != nil {
return err
}

opts := containerizedengine.EngineInitOptions{
RegistryPrefix: options.registryPrefix,
EngineImage: options.image,
EngineVersion: options.version,
}

return client.ActivateEngine(ctx, opts, cli.Out(), authConfig,
func(ctx context.Context) error {
client := cli.Client()
_, err := client.Ping(ctx)
return err
})
}

func getLicenses(ctx context.Context, authConfig *types.AuthConfig, cli command.Cli, options activateOptions) (*model.IssuedLicense, error) {
user, err := licenseutils.Login(ctx, authConfig)
if err != nil {
return nil, err
}
fmt.Fprintf(cli.Out(), "Looking for existing licenses for %s...\n", user.User.Username)
subs, err := user.GetAvailableLicenses(ctx)
if err != nil {
return nil, err
}
if len(subs) == 0 {
return doTrialFlow(ctx, cli, user)
}

format := options.format
if len(format) == 0 {
format = formatter.TableFormatKey
}

updatesCtx := formatter.Context{
Output: cli.Out(),
Format: formatter.NewSubscriptionsFormat(format, options.quiet),
Trunc: false,
}
if err := formatter.SubscriptionsWrite(updatesCtx, subs); err != nil {
return nil, err
}
if options.displayOnly {
return nil, nil
}
fmt.Fprintf(cli.Out(), "Please pick a license by number: ")
var num int
if _, err := fmt.Fscan(cli.In(), &num); err != nil {
return nil, errors.Wrap(err, "failed to read user input")
}
if num < 0 || num >= len(subs) {
return nil, fmt.Errorf("invalid choice")
}
return user.GetIssuedLicense(ctx, subs[num].ID)
}

func doTrialFlow(ctx context.Context, cli command.Cli, user licenseutils.HubUser) (*model.IssuedLicense, error) {
if !command.PromptForConfirmation(cli.In(), cli.Out(),
"No existing licenses found, would you like to set up a new Enterprise Basic Trial license?") {
return nil, fmt.Errorf("you must have an existing enterprise license or generate a new trial to use the Enterprise Docker Engine")
}
targetID := user.User.ID
// If the user is a member of any organizations, allow trials generated against them
if len(user.Orgs) > 0 {
fmt.Fprintf(cli.Out(), "%d\t%s\n", 0, user.User.Username)
for i, org := range user.Orgs {
fmt.Fprintf(cli.Out(), "%d\t%s\n", i+1, org.Orgname)
}
fmt.Fprintf(cli.Out(), "Please choose an account to generate the trial in:")
var num int
if _, err := fmt.Fscan(cli.In(), &num); err != nil {
return nil, errors.Wrap(err, "failed to read user input")
}
if num < 0 || num > len(user.Orgs) {
return nil, fmt.Errorf("invalid choice")
}
if num > 0 {
targetID = user.Orgs[num-1].ID
}
}
return user.GenerateTrialLicense(ctx, targetID)
}
37 changes: 37 additions & 0 deletions cli/command/engine/activate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package engine

import (
"fmt"
"testing"

"github.com/docker/cli/internal/containerizedengine"
"gotest.tools/assert"
)

func TestActivateNoContainerd(t *testing.T) {
testCli.SetContainerizedEngineClient(
func(string) (containerizedengine.Client, error) {
return nil, fmt.Errorf("some error")
},
)
cmd := newActivateCommand(testCli)
cmd.Flags().Set("license", "invalidpath")
cmd.SilenceUsage = true
cmd.SilenceErrors = true
err := cmd.Execute()
assert.ErrorContains(t, err, "unable to access local containerd")
}

func TestActivateBadLicense(t *testing.T) {
testCli.SetContainerizedEngineClient(
func(string) (containerizedengine.Client, error) {
return &fakeContainerizedEngineClient{}, nil
},
)
cmd := newActivateCommand(testCli)
cmd.SilenceUsage = true
cmd.SilenceErrors = true
cmd.Flags().Set("license", "invalidpath")
err := cmd.Execute()
assert.Error(t, err, "open invalidpath: no such file or directory")
}
33 changes: 33 additions & 0 deletions cli/command/engine/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package engine

import (
"context"

"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/trust"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/pkg/errors"
)

func getRegistryAuth(cli command.Cli, registryPrefix string) (*types.AuthConfig, error) {
if registryPrefix == "" {
registryPrefix = "docker.io/docker"
}
distributionRef, err := reference.ParseNormalizedNamed(registryPrefix)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse image name: %s", registryPrefix)
}
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(context.Background(), nil, authResolver(cli), distributionRef.String())
if err != nil {
return nil, errors.Wrap(err, "failed to get imgRefAndAuth")
}
return imgRefAndAuth.AuthConfig(), nil
}

func authResolver(cli command.Cli) func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
return func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
return command.ResolveAuthConfig(ctx, cli, index)
}
}
Loading

0 comments on commit da958f9

Please sign in to comment.