2018-03-19 18:56:51 -04:00
|
|
|
package licenseutils
|
|
|
|
|
|
|
|
import (
|
2018-09-10 17:31:45 -04:00
|
|
|
"bytes"
|
2018-03-19 18:56:51 -04:00
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
"github.com/docker/licensing"
|
|
|
|
"github.com/docker/licensing/model"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
// HubUser wraps a licensing client and holds key information
|
|
|
|
// for a user to avoid multiple lookups
|
|
|
|
type HubUser struct {
|
2018-10-02 14:10:22 -04:00
|
|
|
Client licensing.Client
|
2018-03-19 18:56:51 -04:00
|
|
|
token string
|
|
|
|
User model.User
|
|
|
|
Orgs []model.Org
|
|
|
|
}
|
|
|
|
|
|
|
|
//GetOrgByID finds the org by the ID in the users list of orgs
|
|
|
|
func (u HubUser) GetOrgByID(orgID string) (model.Org, error) {
|
|
|
|
for _, org := range u.Orgs {
|
|
|
|
if org.ID == orgID {
|
|
|
|
return org, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return model.Org{}, fmt.Errorf("org %s not found", orgID)
|
|
|
|
}
|
|
|
|
|
2018-09-10 17:31:45 -04:00
|
|
|
func getClient() (licensing.Client, error) {
|
2018-03-19 18:56:51 -04:00
|
|
|
baseURI, err := url.Parse(licensingDefaultBaseURI)
|
|
|
|
if err != nil {
|
2018-09-10 17:31:45 -04:00
|
|
|
return nil, err
|
2018-03-19 18:56:51 -04:00
|
|
|
}
|
|
|
|
|
2018-09-10 17:31:45 -04:00
|
|
|
return licensing.New(&licensing.Config{
|
2018-03-19 18:56:51 -04:00
|
|
|
BaseURI: *baseURI,
|
|
|
|
HTTPClient: &http.Client{},
|
2018-08-27 18:20:30 -04:00
|
|
|
PublicKeys: licensingPublicKeys,
|
2018-03-19 18:56:51 -04:00
|
|
|
})
|
2018-09-10 17:31:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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()
|
2018-03-19 18:56:51 -04:00
|
|
|
if err != nil {
|
|
|
|
return HubUser{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// For licensing we know they must have a valid login session
|
|
|
|
if authConfig.Username == "" {
|
|
|
|
return HubUser{}, fmt.Errorf("you must be logged in to access licenses. Please use 'docker login' then try again")
|
|
|
|
}
|
|
|
|
token, err := lclient.LoginViaAuth(ctx, authConfig.Username, authConfig.Password)
|
|
|
|
if err != nil {
|
|
|
|
return HubUser{}, err
|
|
|
|
}
|
|
|
|
user, err := lclient.GetHubUserByName(ctx, authConfig.Username)
|
|
|
|
if err != nil {
|
|
|
|
return HubUser{}, err
|
|
|
|
}
|
|
|
|
orgs, err := lclient.GetHubUserOrgs(ctx, token)
|
|
|
|
if err != nil {
|
|
|
|
return HubUser{}, err
|
|
|
|
}
|
|
|
|
return HubUser{
|
2018-10-02 14:10:22 -04:00
|
|
|
Client: lclient,
|
2018-03-19 18:56:51 -04:00
|
|
|
token: token,
|
|
|
|
User: *user,
|
|
|
|
Orgs: orgs,
|
|
|
|
}, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetAvailableLicenses finds all available licenses for a given account and their orgs
|
|
|
|
func (u HubUser) GetAvailableLicenses(ctx context.Context) ([]LicenseDisplay, error) {
|
2018-10-02 14:10:22 -04:00
|
|
|
subs, err := u.Client.ListSubscriptions(ctx, u.token, u.User.ID)
|
2018-03-19 18:56:51 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, org := range u.Orgs {
|
2018-10-02 14:10:22 -04:00
|
|
|
orgSub, err := u.Client.ListSubscriptions(ctx, u.token, org.ID)
|
2018-03-19 18:56:51 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
subs = append(subs, orgSub...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert the SubscriptionDetails to a more user-friendly type to render in the CLI
|
|
|
|
|
|
|
|
res := []LicenseDisplay{}
|
|
|
|
|
|
|
|
// Filter out expired licenses
|
|
|
|
i := 0
|
|
|
|
for _, s := range subs {
|
2018-09-24 19:51:05 -04:00
|
|
|
if s.State == "active" && s.Expires != nil {
|
2018-03-19 18:56:51 -04:00
|
|
|
owner := ""
|
|
|
|
if s.DockerID == u.User.ID {
|
|
|
|
owner = u.User.Username
|
|
|
|
} else {
|
|
|
|
ownerOrg, err := u.GetOrgByID(s.DockerID)
|
|
|
|
if err == nil {
|
|
|
|
owner = ownerOrg.Orgname
|
|
|
|
} else {
|
|
|
|
owner = "unknown"
|
|
|
|
logrus.Debugf("Unable to lookup org ID %s: %s", s.DockerID, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
comps := []string{}
|
|
|
|
for _, pc := range s.PricingComponents {
|
|
|
|
comps = append(comps, fmt.Sprintf("%s:%d", pc.Name, pc.Value))
|
|
|
|
}
|
|
|
|
res = append(res, LicenseDisplay{
|
|
|
|
Subscription: *s,
|
|
|
|
Num: i,
|
|
|
|
Owner: owner,
|
|
|
|
ComponentsString: strings.Join(comps, ","),
|
|
|
|
})
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GenerateTrialLicense will generate a new trial license for the specified user or org
|
|
|
|
func (u HubUser) GenerateTrialLicense(ctx context.Context, targetID string) (*model.IssuedLicense, error) {
|
2018-10-18 17:45:27 -04:00
|
|
|
subID, err := u.Client.GenerateNewTrialSubscription(ctx, u.token, targetID)
|
2018-03-19 18:56:51 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-10-02 14:10:22 -04:00
|
|
|
return u.Client.DownloadLicenseFromHub(ctx, u.token, subID)
|
2018-03-19 18:56:51 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetIssuedLicense will download a license by ID
|
|
|
|
func (u HubUser) GetIssuedLicense(ctx context.Context, ID string) (*model.IssuedLicense, error) {
|
2018-10-02 14:10:22 -04:00
|
|
|
return u.Client.DownloadLicenseFromHub(ctx, u.token, ID)
|
2018-03-19 18:56:51 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// LoadLocalIssuedLicense will load a local license file
|
|
|
|
func LoadLocalIssuedLicense(ctx context.Context, filename string) (*model.IssuedLicense, error) {
|
2018-09-10 17:31:45 -04:00
|
|
|
lclient, err := getClient()
|
2018-03-19 18:56:51 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-09-10 17:31:45 -04:00
|
|
|
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
|
|
|
|
}
|
2018-03-19 18:56:51 -04:00
|
|
|
|
2018-09-10 17:31:45 -04:00
|
|
|
cr, err := lclient.VerifyLicense(ctx, license)
|
2018-03-19 18:56:51 -04:00
|
|
|
if err != nil {
|
2018-09-10 17:31:45 -04:00
|
|
|
return "", err
|
2018-03-19 18:56:51 -04:00
|
|
|
}
|
2018-09-10 17:31:45 -04:00
|
|
|
return lclient.SummarizeLicense(cr, license.KeyID).String(), nil
|
2018-03-19 18:56:51 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2018-09-10 17:31:45 -04:00
|
|
|
// The file may contain a leading BOM, which will choke the
|
|
|
|
// json deserializer.
|
|
|
|
data = bytes.TrimPrefix(data, []byte("\xef\xbb\xbf"))
|
2018-03-19 18:56:51 -04:00
|
|
|
|
|
|
|
err = json.Unmarshal(data, &license)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "malformed license file")
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = lclient.VerifyLicense(ctx, license)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &license, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ApplyLicense will store a license on the local system
|
|
|
|
func ApplyLicense(ctx context.Context, dclient licensing.WrappedDockerClient, license *model.IssuedLicense) error {
|
|
|
|
info, err := dclient.Info(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return licensing.StoreLicense(ctx, dclient, license, info.DockerRootDir)
|
|
|
|
}
|