-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add engine commands built on containerd
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
Showing
67 changed files
with
5,283 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Oops, something went wrong.