mirror of https://github.com/docker/cli.git
Merge pull request #1387 from dhiltgen/activate_ux
[18.09] Expose licensing details before loading
This commit is contained in:
commit
aca3f2d382
|
@ -89,14 +89,19 @@ func runActivate(cli command.Cli, options activateOptions) error {
|
|||
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
|
||||
}
|
||||
}
|
||||
summary, err := licenseutils.GetLicenseSummary(ctx, *license)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(cli.Out(), "License: %s\n", summary)
|
||||
if options.displayOnly {
|
||||
return nil
|
||||
}
|
||||
if err = licenseutils.ApplyLicense(ctx, cli.Client(), license); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -4,13 +4,23 @@ import (
|
|||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/types"
|
||||
"github.com/docker/cli/internal/test"
|
||||
clitypes "github.com/docker/cli/types"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"gotest.tools/assert"
|
||||
"gotest.tools/fs"
|
||||
"gotest.tools/golden"
|
||||
)
|
||||
|
||||
const (
|
||||
// nolint: lll
|
||||
expiredLicense = `{"key_id":"irlYm3b9fdD8hMUXjazF39im7VQSSbAm9tfHK8cKUxJt","private_key":"aH5tTRDAVJpCRS2CRetTQVXIKgWUPfoCHODhDvNPvAbz","authorization":"ewogICAicGF5bG9hZCI6ICJleUpsZUhCcGNtRjBhVzl1SWpvaU1qQXhPQzB3TXkweE9GUXdOem93TURvd01Gb2lMQ0owYjJ0bGJpSTZJbkZtTVMxMlVtRmtialp5YjFaMldXdHJlVXN4VFdKMGNGUmpXR1ozVjA4MVRWZFFTM2cwUnpJd2NIYzlJaXdpYldGNFJXNW5hVzVsY3lJNk1Td2ljMk5oYm01cGJtZEZibUZpYkdWa0lqcDBjblZsTENKc2FXTmxibk5sVkhsd1pTSTZJazltWm14cGJtVWlMQ0owYVdWeUlqb2lVSEp2WkhWamRHbHZiaUo5IiwKICAgInNpZ25hdHVyZXMiOiBbCiAgICAgIHsKICAgICAgICAgImhlYWRlciI6IHsKICAgICAgICAgICAgImp3ayI6IHsKICAgICAgICAgICAgICAgImUiOiAiQVFBQiIsCiAgICAgICAgICAgICAgICJrZXlJRCI6ICJKN0xEOjY3VlI6TDVIWjpVN0JBOjJPNEc6NEFMMzpPRjJOOkpIR0I6RUZUSDo1Q1ZROk1GRU86QUVJVCIsCiAgICAgICAgICAgICAgICJraWQiOiAiSjdMRDo2N1ZSOkw1SFo6VTdCQToyTzRHOjRBTDM6T0YyTjpKSEdCOkVGVEg6NUNWUTpNRkVPOkFFSVQiLAogICAgICAgICAgICAgICAia3R5IjogIlJTQSIsCiAgICAgICAgICAgICAgICJuIjogInlkSXktbFU3bzdQY2VZLTQtcy1DUTVPRWdDeUY4Q3hJY1FJV3VLODRwSWlaY2lZNjczMHlDWW53TFNLVGx3LVU2VUNfUVJlV1Jpb01OTkU1RHM1VFlFWGJHRzZvbG0ycWRXYkJ3Y0NnLTJVVUhfT2NCOVd1UDZnUlBIcE1GTXN4RHpXd3ZheThKVXVIZ1lVTFVwbTFJdi1tcTdscDVuUV9SeHJUMEtaUkFRVFlMRU1FZkd3bTNoTU9fZ2VMUFMtaGdLUHRJSGxrZzZfV2NveFRHb0tQNzlkX3dhSFl4R05sN1doU25laUJTeGJwYlFBS2syMWxnNzk4WGI3dlp5RUFURE1yUlI5TWVFNkFkajVISnBZM0NveVJBUENtYUtHUkNLNHVvWlNvSXUwaEZWbEtVUHliYncwMDBHTy13YTJLTjhVd2dJSW0waTVJMXVXOUdrcTR6akJ5NXpoZ3F1VVhiRzliV1BBT1lycTVRYTgxRHhHY0JsSnlIWUFwLUREUEU5VEdnNHpZbVhqSm54WnFIRWR1R3FkZXZaOFhNSTB1a2ZrR0lJMTR3VU9pTUlJSXJYbEVjQmZfNDZJOGdRV0R6eHljWmVfSkdYLUxBdWF5WHJ5clVGZWhWTlVkWlVsOXdYTmFKQi1rYUNxejVRd2FSOTNzR3ctUVNmdEQwTnZMZTdDeU9ILUU2dmc2U3RfTmVUdmd2OFluaENpWElsWjhIT2ZJd05lN3RFRl9VY3o1T2JQeWttM3R5bHJOVWp0MFZ5QW10dGFjVkkyaUdpaGNVUHJtazRsVklaN1ZEX0xTVy1pN3lvU3VydHBzUFhjZTJwS0RJbzMwbEpHaE9fM0tVbWwyU1VaQ3F6SjF5RW1LcHlzSDVIRFc5Y3NJRkNBM2RlQWpmWlV2TjdVIgogICAgICAgICAgICB9LAogICAgICAgICAgICAiYWxnIjogIlJTMjU2IgogICAgICAgICB9LAogICAgICAgICAic2lnbmF0dXJlIjogIm5saTZIdzRrbW5KcTBSUmRXaGVfbkhZS2VJLVpKenM1U0d5SUpDakh1dWtnVzhBYklpVzFZYWJJR2NqWUt0QTY4dWN6T1hyUXZreGxWQXJLSlgzMDJzN0RpbzcxTlNPRzJVcnhsSjlibDFpd0F3a3ZyTEQ2T0p5MGxGLVg4WnRabXhPVmNQZmwzcmJwZFQ0dnlnWTdNcU1QRXdmb0IxTmlWZDYyZ1cxU2NSREZZcWw3R0FVaFVKNkp4QU15VzVaOXl5YVE0NV8wd0RMUk5mRjA5YWNXeVowTjRxVS1hZjhrUTZUUWZUX05ERzNCR3pRb2V3cHlEajRiMFBHb0diOFhLdDlwekpFdEdxM3lQM25VMFFBbk90a2gwTnZac1l1UFcyUnhDT3lRNEYzVlR3UkF2eF9HSTZrMVRpYmlKNnByUWluUy16Sjh6RE8zUjBuakE3OFBwNXcxcVpaUE9BdmtzZFNSYzJDcVMtcWhpTmF5YUhOVHpVNnpyOXlOZHR2S0o1QjNST0FmNUtjYXNiWURjTnVpeXBUNk90LUtqQ2I1dmYtWVpnc2FRNzJBdFBhSU4yeUpNREZHbmEwM0hpSjMxcTJRUlp5eTZrd3RYaGtwcDhTdEdIcHYxSWRaV09SVWttb0g5SFBzSGk4SExRLTZlM0tEY2x1RUQyMTNpZnljaVhtN0YzdHdaTTNHeDd1UXR1SldHaUlTZ2Z0QW9lVjZfUmI2VThkMmZxNzZuWHYxak5nckRRcE5waEZFd2tCdGRtZHZ2THByZVVYX3BWangza1AxN3pWbXFKNmNOOWkwWUc4WHg2VmRzcUxsRXUxQ2Rhd3Q0eko1M3VHMFlKTjRnUDZwc25yUS1uM0U1aFdlMDJ3d3dBZ3F3bGlPdmd4V1RTeXJyLXY2eDI0IiwKICAgICAgICAgInByb3RlY3RlZCI6ICJleUptYjNKdFlYUk1aVzVuZEdnaU9qRTNNeXdpWm05eWJXRjBWR0ZwYkNJNkltWlJJaXdpZEdsdFpTSTZJakl3TVRjdE1EVXRNRFZVTWpFNk5UYzZNek5hSW4wIgogICAgICB9CiAgIF0KfQ=="}`
|
||||
)
|
||||
|
||||
func TestActivateNoContainerd(t *testing.T) {
|
||||
testCli.SetContainerizedEngineClient(
|
||||
func(string) (types.ContainerizedClient, error) {
|
||||
func(string) (clitypes.ContainerizedClient, error) {
|
||||
return nil, fmt.Errorf("some error")
|
||||
},
|
||||
)
|
||||
|
@ -25,7 +35,7 @@ func TestActivateNoContainerd(t *testing.T) {
|
|||
|
||||
func TestActivateBadLicense(t *testing.T) {
|
||||
testCli.SetContainerizedEngineClient(
|
||||
func(string) (types.ContainerizedClient, error) {
|
||||
func(string) (clitypes.ContainerizedClient, error) {
|
||||
return &fakeContainerizedEngineClient{}, nil
|
||||
},
|
||||
)
|
||||
|
@ -37,3 +47,25 @@ func TestActivateBadLicense(t *testing.T) {
|
|||
err := cmd.Execute()
|
||||
assert.Error(t, err, "open invalidpath: no such file or directory")
|
||||
}
|
||||
|
||||
func TestActivateExpiredLicenseDryRun(t *testing.T) {
|
||||
dir := fs.NewDir(t, "license", fs.WithFile("docker.lic", expiredLicense, fs.WithMode(0644)))
|
||||
defer dir.Remove()
|
||||
filename := dir.Join("docker.lic")
|
||||
isRoot = func() bool { return true }
|
||||
c := test.NewFakeCli(&verClient{client.Client{}, types.Version{}, nil})
|
||||
c.SetContainerizedEngineClient(
|
||||
func(string) (clitypes.ContainerizedClient, error) {
|
||||
return &fakeContainerizedEngineClient{}, nil
|
||||
},
|
||||
)
|
||||
cmd := newActivateCommand(c)
|
||||
cmd.SilenceUsage = true
|
||||
cmd.SilenceErrors = true
|
||||
cmd.Flags().Set("license", filename)
|
||||
cmd.Flags().Set("display-only", "true")
|
||||
c.OutBuffer().Reset()
|
||||
err := cmd.Execute()
|
||||
assert.NilError(t, err)
|
||||
golden.Assert(t, c.OutBuffer().String(), "expired-license-display-only.golden")
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
License: Quantity: 1 Nodes Expiration date: 2018-03-18 Expired! You will no longer receive updates. Please renew at https://docker.com/licensing
|
|
@ -20,6 +20,7 @@ type (
|
|||
parseLicenseFunc func(license []byte) (parsedLicense *model.IssuedLicense, err error)
|
||||
storeLicenseFunc func(ctx context.Context, dclnt licensing.WrappedDockerClient, licenses *model.IssuedLicense, localRootDir string) error
|
||||
loadLocalLicenseFunc func(ctx context.Context, dclnt licensing.WrappedDockerClient) (*model.Subscription, error)
|
||||
summarizeLicenseFunc func(*model.CheckResponse, string) *model.Subscription
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -102,3 +103,10 @@ func (c *fakeLicensingClient) LoadLocalLicense(ctx context.Context, dclnt licens
|
|||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *fakeLicensingClient) SummarizeLicense(cr *model.CheckResponse, keyid string) *model.Subscription {
|
||||
if c.summarizeLicenseFunc != nil {
|
||||
return c.summarizeLicenseFunc(cr, keyid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package licenseutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -35,18 +36,22 @@ func (u HubUser) GetOrgByID(orgID string) (model.Org, error) {
|
|||
return model.Org{}, fmt.Errorf("org %s not found", orgID)
|
||||
}
|
||||
|
||||
// Login to the license server and return a client that can be used to look up and download license files or generate new trial licenses
|
||||
func Login(ctx context.Context, authConfig *types.AuthConfig) (HubUser, error) {
|
||||
func getClient() (licensing.Client, error) {
|
||||
baseURI, err := url.Parse(licensingDefaultBaseURI)
|
||||
if err != nil {
|
||||
return HubUser{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lclient, err := licensing.New(&licensing.Config{
|
||||
return licensing.New(&licensing.Config{
|
||||
BaseURI: *baseURI,
|
||||
HTTPClient: &http.Client{},
|
||||
PublicKeys: licensingPublicKeys,
|
||||
})
|
||||
}
|
||||
|
||||
// Login to the license server and return a client that can be used to look up and download license files or generate new trial licenses
|
||||
func Login(ctx context.Context, authConfig *types.AuthConfig) (HubUser, error) {
|
||||
lclient, err := getClient()
|
||||
if err != nil {
|
||||
return HubUser{}, err
|
||||
}
|
||||
|
@ -143,28 +148,36 @@ func (u HubUser) GetIssuedLicense(ctx context.Context, ID string) (*model.Issued
|
|||
|
||||
// LoadLocalIssuedLicense will load a local license file
|
||||
func LoadLocalIssuedLicense(ctx context.Context, filename string) (*model.IssuedLicense, error) {
|
||||
baseURI, err := url.Parse(licensingDefaultBaseURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lclient, err := licensing.New(&licensing.Config{
|
||||
BaseURI: *baseURI,
|
||||
HTTPClient: &http.Client{},
|
||||
PublicKeys: licensingPublicKeys,
|
||||
})
|
||||
lclient, err := getClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return doLoadLocalIssuedLicense(ctx, filename, lclient)
|
||||
}
|
||||
|
||||
// GetLicenseSummary summarizes the license for the user
|
||||
func GetLicenseSummary(ctx context.Context, license model.IssuedLicense) (string, error) {
|
||||
lclient, err := getClient()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cr, err := lclient.VerifyLicense(ctx, license)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return lclient.SummarizeLicense(cr, license.KeyID).String(), nil
|
||||
}
|
||||
|
||||
func doLoadLocalIssuedLicense(ctx context.Context, filename string, lclient licensing.Client) (*model.IssuedLicense, error) {
|
||||
var license model.IssuedLicense
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The file may contain a leading BOM, which will choke the
|
||||
// json deserializer.
|
||||
data = bytes.TrimPrefix(data, []byte("\xef\xbb\xbf"))
|
||||
|
||||
err = json.Unmarshal(data, &license)
|
||||
if err != nil {
|
||||
|
|
|
@ -22,7 +22,7 @@ github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
|
|||
github.com/docker/go-metrics d466d4f6fd960e01820085bd7e1a24426ee7ef18
|
||||
github.com/docker/go-units 47565b4f722fb6ceae66b95f853feed578a4a51c # v0.3.3
|
||||
github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||
github.com/docker/licensing 5c4c7b4
|
||||
github.com/docker/licensing f2eae57157a06681b024f1690923d03e414179a0
|
||||
github.com/docker/swarmkit cfa742c8abe6f8e922f6e4e920153c408e7d9c3b
|
||||
github.com/flynn-archive/go-shlex 3f9db97f856818214da2e1057f8ad84803971cff
|
||||
github.com/ghodss/yaml 0ca9ea5df5451ffdf184b4428c902747c2c11cd7 # v1.0.0
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package licensing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
|
@ -34,6 +35,7 @@ type Client interface {
|
|||
ParseLicense(license []byte) (parsedLicense *model.IssuedLicense, err error)
|
||||
StoreLicense(ctx context.Context, dclnt WrappedDockerClient, licenses *model.IssuedLicense, localRootDir string) error
|
||||
LoadLocalLicense(ctx context.Context, dclnt WrappedDockerClient) (*model.Subscription, error)
|
||||
SummarizeLicense(res *model.CheckResponse, keyID string) *model.Subscription
|
||||
}
|
||||
|
||||
func (c *client) LoginViaAuth(ctx context.Context, username, password string) (string, error) {
|
||||
|
@ -185,6 +187,10 @@ func (c *client) DownloadLicenseFromHub(ctx context.Context, authToken, subscrip
|
|||
|
||||
func (c *client) ParseLicense(license []byte) (*model.IssuedLicense, error) {
|
||||
parsedLicense := &model.IssuedLicense{}
|
||||
// The file may contain a leading BOM, which will choke the
|
||||
// json deserializer.
|
||||
license = bytes.Trim(license, "\xef\xbb\xbf")
|
||||
|
||||
if err := json.Unmarshal(license, &parsedLicense); err != nil {
|
||||
return nil, errors.WithMessage(err, "failed to parse license")
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ type Subscription struct {
|
|||
}
|
||||
|
||||
func (s *Subscription) String() string {
|
||||
storeURL := "https://store.docker.com"
|
||||
storeURL := "https://docker.com/licensing"
|
||||
|
||||
var nameMsg, expirationMsg, statusMsg string
|
||||
switch s.State {
|
||||
|
|
|
@ -87,18 +87,23 @@ func (c *client) LoadLocalLicense(ctx context.Context, clnt WrappedDockerClient)
|
|||
licenseData, err = readLicenseFromHost(ctx, info.DockerRootDir)
|
||||
} else {
|
||||
// Load the latest license index
|
||||
latestVersion, err := getLatestNamedConfig(clnt, licenseNamePrefix)
|
||||
var latestVersion int
|
||||
latestVersion, err = getLatestNamedConfig(clnt, licenseNamePrefix)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "not a swarm manager.") {
|
||||
return nil, ErrWorkerNode
|
||||
}
|
||||
return nil, fmt.Errorf("unable to get latest license version: %s", err)
|
||||
}
|
||||
cfg, _, err := clnt.ConfigInspectWithRaw(ctx, fmt.Sprintf("%s-%d", licenseNamePrefix, latestVersion))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load license from swarm config: %s", err)
|
||||
if latestVersion >= 0 {
|
||||
cfg, _, err := clnt.ConfigInspectWithRaw(ctx, fmt.Sprintf("%s-%d", licenseNamePrefix, latestVersion))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to load license from swarm config: %s", err)
|
||||
}
|
||||
licenseData = cfg.Spec.Data
|
||||
} else {
|
||||
licenseData, err = readLicenseFromHost(ctx, info.DockerRootDir)
|
||||
}
|
||||
licenseData = cfg.Spec.Data
|
||||
}
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
|
@ -115,6 +120,10 @@ func (c *client) LoadLocalLicense(ctx context.Context, clnt WrappedDockerClient)
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return checkResponseToSubscription(checkResponse, parsedLicense.KeyID), nil
|
||||
}
|
||||
|
||||
func checkResponseToSubscription(checkResponse *model.CheckResponse, keyID string) *model.Subscription {
|
||||
|
||||
// TODO - this translation still needs some work
|
||||
// Primary missing piece is how to distinguish from basic, vs std/advanced
|
||||
|
@ -144,7 +153,7 @@ func (c *client) LoadLocalLicense(ctx context.Context, clnt WrappedDockerClient)
|
|||
// Translate the legacy structure into the new Subscription fields
|
||||
return &model.Subscription{
|
||||
// Name
|
||||
ID: parsedLicense.KeyID, // This is not actually the same, but is unique
|
||||
ID: keyID, // This is not actually the same, but is unique
|
||||
// DockerID
|
||||
ProductID: productID,
|
||||
ProductRatePlan: ratePlan,
|
||||
|
@ -159,7 +168,11 @@ func (c *client) LoadLocalLicense(ctx context.Context, clnt WrappedDockerClient)
|
|||
Value: checkResponse.MaxEngines,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) SummarizeLicense(checkResponse *model.CheckResponse, keyID string) *model.Subscription {
|
||||
return checkResponseToSubscription(checkResponse, keyID)
|
||||
}
|
||||
|
||||
// getLatestNamedConfig looks for versioned instances of configs with the
|
||||
|
|
Loading…
Reference in New Issue