commit
2b8fc650c9
|
@ -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,65 @@ 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
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ func (s *HTTPServer) registerEnterpriseHandlers() {
|
|||
s.mux.HandleFunc("/v1/quota-usages", s.wrap(s.entOnly))
|
||||
s.mux.HandleFunc("/v1/quota/", s.wrap(s.entOnly))
|
||||
s.mux.HandleFunc("/v1/quota", s.wrap(s.entOnly))
|
||||
|
||||
s.mux.HandleFunc("/v1/operator/license", s.wrap(s.entOnly))
|
||||
}
|
||||
|
||||
func (s *HTTPServer) entOnly(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
|
|
|
@ -81,6 +81,9 @@ type TestAgent struct {
|
|||
// ports that are reserved through freeport that must be returned at
|
||||
// the end of a test, done when Shutdown() is called.
|
||||
ports []int
|
||||
|
||||
// Enterprise specifies if the agent is enterprise or not
|
||||
Enterprise bool
|
||||
}
|
||||
|
||||
// NewTestAgent returns a started agent with the given name and
|
||||
|
@ -91,6 +94,7 @@ func NewTestAgent(t testing.T, name string, configCallback func(*Config)) *TestA
|
|||
T: t,
|
||||
Name: name,
|
||||
ConfigCallback: configCallback,
|
||||
Enterprise: EnterpriseTestAgent,
|
||||
}
|
||||
|
||||
a.Start()
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
// +build !ent
|
||||
|
||||
package agent
|
||||
|
||||
const (
|
||||
// EnterpriseTestAgent is used to configure a TestAgent's Enterprise flag
|
||||
EnterpriseTestAgent = false
|
||||
)
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/nomad/api"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
var _ cli.Command = &LicenseCommand{}
|
||||
|
||||
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 <path>
|
||||
|
||||
Install a new license from stdin:
|
||||
$ nomad license put -
|
||||
|
||||
Retrieve the current license:
|
||||
|
||||
$ nomad license get
|
||||
|
||||
`
|
||||
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 {
|
||||
var validity string
|
||||
if resp.Valid {
|
||||
validity = "valid"
|
||||
outputLicenseInfo(ui, resp.License, false, validity)
|
||||
return 0
|
||||
} else if resp.License != nil {
|
||||
now := time.Now()
|
||||
if resp.License.ExpirationTime.Before(now) {
|
||||
validity = "expired!"
|
||||
outputLicenseInfo(ui, resp.License, true, validity)
|
||||
} else {
|
||||
validity = "invalid!"
|
||||
for _, warn := range resp.Warnings {
|
||||
ui.Output(fmt.Sprintf(" %s", warn))
|
||||
}
|
||||
outputLicenseInfo(ui, resp.License, false, validity)
|
||||
}
|
||||
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, validity string) {
|
||||
expStr := ""
|
||||
if expired {
|
||||
expStr = fmt.Sprintf("Expired At|%s", lic.ExpirationTime.String())
|
||||
} else {
|
||||
expStr = fmt.Sprintf("Expires At|%s", lic.ExpirationTime.String())
|
||||
}
|
||||
|
||||
output := []string{
|
||||
fmt.Sprintf("License Status|%s", validity),
|
||||
fmt.Sprintf("License ID|%s", lic.LicenseID),
|
||||
fmt.Sprintf("Customer ID|%s", lic.CustomerID),
|
||||
expStr,
|
||||
fmt.Sprintf("License ID|%s", lic.LicenseID),
|
||||
fmt.Sprintf("Customer ID|%s", lic.CustomerID),
|
||||
fmt.Sprintf("Terminates At|%s", lic.TerminationTime.String()),
|
||||
fmt.Sprintf("Datacenter|%s", lic.InstallationID),
|
||||
}
|
||||
ui.Output(formatKV(output))
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type LicenseGetCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
func (c *LicenseGetCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: nomad license get [options]
|
||||
|
||||
Gets a new license in Servers and Clients
|
||||
General Options:
|
||||
|
||||
` + generalOptionsUsage()
|
||||
|
||||
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 {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
client, err := c.Meta.Client()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
resp, _, err := client.Operator().LicenseGet(nil)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error getting license: %v", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
return OutputLicenseReply(c.Ui, resp)
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var _ cli.Command = &LicenseGetCommand{}
|
||||
|
||||
func TestCommand_LicenseGet_OSSErr(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv, _, url := testServer(t, false, nil)
|
||||
defer srv.Shutdown()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
cmd := &LicenseGetCommand{Meta: Meta{Ui: ui}}
|
||||
|
||||
code := cmd.Run([]string{"-address=" + url})
|
||||
require.Equal(t, 1, code)
|
||||
|
||||
if srv.Enterprise {
|
||||
require.Contains(t, ui.OutputWriter.String(), "License Status")
|
||||
} else {
|
||||
require.Contains(t, ui.ErrorWriter.String(), "Nomad Enterprise only endpoint")
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"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 <path>
|
||||
|
||||
Install a new license from stdin:
|
||||
|
||||
$ nomad license put -
|
||||
|
||||
`
|
||||
return strings.TrimSpace(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(file string, testStdin io.Reader) (string, error) {
|
||||
// Handle empty quoted shell parameters
|
||||
if len(file) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if file == "-" {
|
||||
if len(file) > 1 {
|
||||
return file, nil
|
||||
}
|
||||
return loadFromStdin(testStdin)
|
||||
}
|
||||
|
||||
return loadFromFile(file)
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var _ cli.Command = &LicensePutCommand{}
|
||||
|
||||
func TestCommand_LicensePut_Err(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
srv, _, url := testServer(t, false, nil)
|
||||
defer srv.Shutdown()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
cmd := &LicensePutCommand{Meta: Meta{Ui: ui}, testStdin: strings.NewReader("testlicenseblob")}
|
||||
|
||||
if code := cmd.Run([]string{"-address=" + url, "-"}); code != 1 {
|
||||
require.Equal(t, code, 1)
|
||||
}
|
||||
|
||||
if srv.Enterprise {
|
||||
require.Contains(t, ui.ErrorWriter.String(), "error validating license")
|
||||
} else {
|
||||
require.Contains(t, ui.ErrorWriter.String(), "Nomad Enterprise only endpoint")
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue