license cli commands

cli changes, formatting
This commit is contained in:
Drew Bailey
2020-04-21 14:55:31 -04:00
parent c10ac6394f
commit 8b222d79d5
6 changed files with 398 additions and 1 deletions

View File

@@ -1,6 +1,9 @@
package api
import "strconv"
import (
"strconv"
"time"
)
// Operator can be used to perform low-level operator tasks for Nomad.
type Operator struct {
@@ -176,3 +179,85 @@ func (op *Operator) SchedulerCASConfiguration(conf *SchedulerConfiguration, q *W
return &out, wm, nil
}
type License struct {
// The unique identifier of the license
LicenseID string `json:"license_id"`
// The customer ID associated with the license
CustomerID string `json:"customer_id"`
// If set, an identifier that should be used to lock the license to a
// particular site, cluster, etc.
InstallationID string `json:"installation_id"`
// The time at which the license was issued
IssueTime time.Time `json:"issue_time"`
// The time at which the license starts being valid
StartTime time.Time `json:"start_time"`
// The time after which the license expires
ExpirationTime time.Time `json:"expiration_time"`
// The time at which the license ceases to function and can
// no longer be used in any capacity
TerminationTime time.Time `json:"termination_time"`
// The product the license is valid for
Product string `json:"product"`
// License Specific Flags
Flags map[string]interface{} `json:"flags"`
// Modules is a list of the licensed enterprise modules
Modules []string `json:"modules"`
// List of features enabled by the license
Features []string `json:"features"`
}
type LicenseReply struct {
Valid bool
License *License
Warnings []string
QueryMeta
}
func (op *Operator) LicensePut(license string, q *WriteOptions) (*LicenseReply, *WriteMeta, error) {
var resp LicenseReply
wm, err := op.c.write("/v1/operator/license", license, &resp, q)
if err != nil {
return nil, nil, err
}
return &resp, wm, nil
}
func (op *Operator) LicenseGet(q *QueryOptions) (*LicenseReply, *QueryMeta, error) {
var reply LicenseReply
qm, err := op.c.query("/v1/operator/license", &reply, q)
if err != nil {
return nil, nil, err
}
return &reply, qm, nil
}
func (op *Operator) LicenseGetSigned(q *QueryOptions) (string, *QueryMeta, error) {
var reply string
qm, err := op.c.query("/v1/operator/license?signed=true", &reply, q)
if err != nil {
return "", nil, err
}
return reply, qm, nil
}
// LicenseReset will reset the license to the builtin one if it is still valid.
// If the builtin license is invalid, the current license stays active
func (op *Operator) LicenseReset(q *WriteOptions) (*LicenseReply, *WriteMeta, error) {
var reply LicenseReply
wm, err := op.c.delete("/v1/operator/license", &reply, q)
if err != nil {
return nil, nil, err
}
return &reply, wm, nil
}

View File

@@ -361,6 +361,21 @@ func Commands(metaPtr *Meta, agentUi cli.Ui) map[string]cli.CommandFactory {
Meta: meta,
}, nil
},
"license": func() (cli.Command, error) {
return &LicenseCommand{
Meta: meta,
}, nil
},
"license get": func() (cli.Command, error) {
return &LicenseGetCommand{
Meta: meta,
}, nil
},
"license put": func() (cli.Command, error) {
return &LicensePutCommand{
Meta: meta,
}, nil
},
"logs": func() (cli.Command, error) {
return &AllocLogsCommand{
Meta: meta,

99
command/license.go Normal file
View File

@@ -0,0 +1,99 @@
package command
import (
"fmt"
"strings"
"time"
"github.com/hashicorp/nomad/api"
"github.com/mitchellh/cli"
)
type LicenseCommand struct {
Meta
}
func (l *LicenseCommand) Help() string {
helpText := `
Usage: nomad license <subcommand> [options] [args]
This command has subcommands for managing the Nomad Enterprise license.
For more detailed examples see:
https://www.nomadproject.io/docs/commands/license/
Install a new license from a file:
$ nomad license put @nomad.license
Install a new license from stdin:
$ nomad license put -
Install a new license from a string:
$ nomad license put "<license blob>"
Retrieve the current license:
$ nomad license get
Reset the current license:
$ nomad license reset
`
return strings.TrimSpace(helpText)
}
func (l *LicenseCommand) Synopsis() string {
return "Interact with Nomad Enterprise License"
}
func (l *LicenseCommand) Name() string { return "license" }
func (l *LicenseCommand) Run(args []string) int {
return cli.RunResultHelp
}
func OutputLicenseReply(ui cli.Ui, resp *api.LicenseReply) int {
if resp.Valid {
ui.Output("License is valid")
outputLicenseInfo(ui, resp.License, false)
return 0
} else if resp.License != nil {
now := time.Now()
if resp.License.ExpirationTime.Before(now) {
ui.Output("License has expired!")
outputLicenseInfo(ui, resp.License, true)
} else {
ui.Output("License is invalid!")
for _, warn := range resp.Warnings {
ui.Output(fmt.Sprintf(" %s", warn))
}
outputLicenseInfo(ui, resp.License, false)
}
return 1
} else {
// TODO - remove the expired message here in the future
// once the go-licensing library is updated post 1.1
ui.Output("Nomad is unlicensed or the license has expired")
return 0
}
}
func outputLicenseInfo(ui cli.Ui, lic *api.License, expired bool) {
ui.Output(fmt.Sprintf("License ID: %s", lic.LicenseID))
ui.Output(fmt.Sprintf("Customer ID: %s", lic.CustomerID))
if expired {
ui.Output(fmt.Sprintf("Expired At: %s", lic.ExpirationTime.String()))
} else {
ui.Output(fmt.Sprintf("Expires At: %s", lic.ExpirationTime.String()))
}
ui.Output(fmt.Sprintf("Terminates At: %s", lic.TerminationTime.String()))
ui.Output(fmt.Sprintf("Datacenter: %s", lic.InstallationID))
if len(lic.Modules) > 0 {
ui.Output("Modules:")
for _, mod := range lic.Modules {
ui.Output(fmt.Sprintf("\t%v", mod))
}
}
ui.Output("Licensed Features:")
for _, f := range lic.Features {
ui.Output(fmt.Sprintf("\t%s", f))
}
}

63
command/license_get.go Normal file
View File

@@ -0,0 +1,63 @@
package command
import (
"fmt"
)
type LicenseGetCommand struct {
Meta
}
func (c *LicenseGetCommand) Help() string {
helpText := `
Usage: nomad license put [options]
Gets a new license in Servers and Clients
General Options:
` + generalOptionsUsage() + `
Get Options:
-signed
Determines if the returned license should be a signed blob instead of a
parsed license.
`
return helpText
}
func (c *LicenseGetCommand) Synopsis() string {
return "Install a new Nomad Enterprise License"
}
func (c *LicenseGetCommand) Name() string { return "license get" }
func (c *LicenseGetCommand) Run(args []string) int {
var signed bool
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
flags.Usage = func() { c.Ui.Output(c.Help()) }
flags.BoolVar(&signed, "signed", false, "Gets the signed license blob instead of a parsed license")
client, err := c.Meta.Client()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
}
if signed {
resp, _, err := client.Operator().LicenseGetSigned(nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error getting signed license: %v", err))
}
c.Ui.Output(resp)
return 0
}
resp, _, err := client.Operator().LicenseGet(nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error putting license: %v", err))
}
return OutputLicenseReply(c.Ui, resp)
}

122
command/license_put.go Normal file
View File

@@ -0,0 +1,122 @@
package command
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"github.com/pkg/errors"
)
type LicensePutCommand struct {
Meta
testStdin io.Reader
}
func (c *LicensePutCommand) Help() string {
helpText := `
Usage: nomad license put [options]
Puts a new license in Servers and Clients
General Options:
` + generalOptionsUsage() + `
Install a new license from a file:
$ nomad license put @nomad.license
Install a new license from stdin:
$ nomad license put -
Install a new license from a string:
$ nomad license put "<license blob>"
`
return helpText
}
func (c *LicensePutCommand) Synopsis() string {
return "Install a new Nomad Enterprise License"
}
func (c *LicensePutCommand) Name() string { return "license put" }
func (c *LicensePutCommand) Run(args []string) int {
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
flags.Usage = func() { c.Ui.Output(c.Help()) }
if err := flags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing flags: %s", err))
return 1
}
args = flags.Args()
data, err := c.dataFromArgs(args)
if err != nil {
c.Ui.Error(errors.Wrap(err, "Error parsing arguments").Error())
return 1
}
client, err := c.Meta.Client()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
return 1
}
resp, _, err := client.Operator().LicensePut(data, nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error putting license: %v", err))
return 1
}
return OutputLicenseReply(c.Ui, resp)
}
func (c *LicensePutCommand) dataFromArgs(args []string) (string, error) {
switch len(args) {
case 0:
return "", fmt.Errorf("Missing LICENSE argument")
case 1:
return LoadDataSource(args[0], c.testStdin)
default:
return "", fmt.Errorf("Too many arguments, exptected 1, got %d", len(args))
}
}
func loadFromFile(path string) (string, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return "", fmt.Errorf("Failed to read file: %v", err)
}
return string(data), nil
}
func loadFromStdin(testStdin io.Reader) (string, error) {
var stdin io.Reader = os.Stdin
if testStdin != nil {
stdin = testStdin
}
var b bytes.Buffer
if _, err := io.Copy(&b, stdin); err != nil {
return "", fmt.Errorf("Failed to read stdin: %v", err)
}
return b.String(), nil
}
func LoadDataSource(data string, testStdin io.Reader) (string, error) {
// Handle empty quoted shell parameters
if len(data) == 0 {
return "", nil
}
switch data[0] {
case '@':
return loadFromFile(data[1:])
case '-':
if len(data) > 1 {
return data, nil
}
return loadFromStdin(testStdin)
default:
return data, nil
}
}

View File

@@ -0,0 +1,13 @@
package command
import (
"testing"
"github.com/mitchellh/cli"
)
var _ cli.Command = &LicensePutCommand{}
func TestCommand_LicensePut(t *testing.T) {
// TODO create test once http endpoints are configured
}