Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 88 additions & 9 deletions cmd/licenseStatus.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import (
"strings"

"github.com/spf13/cobra"
"github.com/spf13/viper"
)

type LicenseStatus struct {
type LicenseStatusV3 struct {
ExpiryDate string `json:"expiryDate"`
MaximumEngines int `json:"maximumEngines"`
SerialNumber string `json:"serialNumber"`
Expand All @@ -20,11 +21,23 @@ type LicenseStatus struct {
MaximumAuthors int `json:"maximumAuthors"`
}

type LicenseStatusV4 struct {
Licensed bool `json:"licensed"`
Expiration string `json:"expiration"`
MaximumEngines int `json:"maximumEngines"`
Expired bool `json:"expired"`
SerialNumber string `json:"serialNumber"`
MaximumAuthors int `json:"maximumAuthors"`
}

type licenseStatusFlags struct {
outputType string
noHeaders bool
apiVersion apiVersionFlag
}

var licenseStatusV4BuildThreshold = 23319

func newLicenseStatusCmd() *cobra.Command {
f := licenseStatusFlags{}
cmd := &cobra.Command{
Expand All @@ -36,6 +49,9 @@ func newLicenseStatusCmd() *cobra.Command {
}
cmd.Flags().StringVarP(&f.outputType, "output", "o", "table", "Specify the output type. Should be one of table, json, or custom-columns")
cmd.Flags().BoolVar(&f.noHeaders, "no-headers", false, "Don't print column headers")
cmd.Flags().Var(&f.apiVersion, "api-version", "The api version to use when contacting FME Server. Must be one of v3 or v4")
cmd.Flags().MarkHidden("api-version")
cmd.RegisterFlagCompletionFunc("api-version", apiVersionFlagCompletion)
return cmd
}

Expand All @@ -45,11 +61,30 @@ func licenseStatusRun(f *licenseStatusFlags) func(cmd *cobra.Command, args []str
if jsonOutput {
f.outputType = "json"
}

// get build to decide if we should use v3 or v4
// FME Server 2023.0+ and later can use v4. Otherwise fall back to v3
if f.apiVersion == "" {
fmeflowBuild := viper.GetInt("build")
if fmeflowBuild < licenseStatusV4BuildThreshold {
f.apiVersion = apiVersionFlagV3
} else {
f.apiVersion = apiVersionFlagV4
}
}

var endpoint string
if f.apiVersion == "v4" {
endpoint = "/fmeapiv4/license/status"
} else {
endpoint = "/fmerest/v3/licensing/license/status"
}

// set up http
client := &http.Client{}

// call the status endpoint to see if it is finished
request, err := buildFmeFlowRequest("/fmerest/v3/licensing/license/status", "GET", nil)
request, err := buildFmeFlowRequest(endpoint, "GET", nil)
if err != nil {
return err
}
Expand All @@ -65,10 +100,12 @@ func licenseStatusRun(f *licenseStatusFlags) func(cmd *cobra.Command, args []str
return err
}

var result LicenseStatus
if err := json.Unmarshal(responseData, &result); err != nil {
return err
} else {
if f.apiVersion == "v4" {
var result LicenseStatusV4
if err := json.Unmarshal(responseData, &result); err != nil {
return err
}

if f.outputType == "table" {
// output all values returned by the JSON in a table
t := createTableWithDefaultColumns(result)
Expand All @@ -93,8 +130,6 @@ func licenseStatusRun(f *licenseStatusFlags) func(cmd *cobra.Command, args []str
return errors.New("custom-columns format specified but no custom columns given")
}

// we have to marshal the Items array, then create an array of marshalled items
// to pass to the creation of the table.
marshalledItems := [][]byte{}
mJson, err := json.Marshal(result)
if err != nil {
Expand All @@ -112,13 +147,57 @@ func licenseStatusRun(f *licenseStatusFlags) func(cmd *cobra.Command, args []str
}
fmt.Fprintln(cmd.OutOrStdout(), t.Render())
} else {
return errors.New("invalid output format specified")
}
} else {
var result LicenseStatusV3
if err := json.Unmarshal(responseData, &result); err != nil {
return err
}

if f.outputType == "table" {
// output all values returned by the JSON in a table
t := createTableWithDefaultColumns(result)

if f.noHeaders {
t.ResetHeaders()
}
fmt.Fprintln(cmd.OutOrStdout(), t.Render())
} else if f.outputType == "json" {
prettyJSON, err := prettyPrintJSON(responseData)
if err != nil {
return err
}
fmt.Fprintln(cmd.OutOrStdout(), prettyJSON)
}
} else if strings.HasPrefix(f.outputType, "custom-columns") {
// parse the columns and json queries
columnsString := ""
if strings.HasPrefix(f.outputType, "custom-columns=") {
columnsString = f.outputType[len("custom-columns="):]
}
if len(columnsString) == 0 {
return errors.New("custom-columns format specified but no custom columns given")
}

marshalledItems := [][]byte{}
mJson, err := json.Marshal(result)
if err != nil {
return err
}
marshalledItems = append(marshalledItems, mJson)

columnsInput := strings.Split(columnsString, ",")
t, err := createTableFromCustomColumns(marshalledItems, columnsInput)
if err != nil {
return err
}
if f.noHeaders {
t.ResetHeaders()
}
fmt.Fprintln(cmd.OutOrStdout(), t.Render())
} else {
return errors.New("invalid output format specified")
}
}
return nil

Expand Down
99 changes: 85 additions & 14 deletions cmd/licenseStatus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ func TestLicenseStatus(t *testing.T) {
"maximumAuthors": 10
}`

// standard responses for v4 (different field names)
responseV4 := `{
"licensed": true,
"expiration": "PERMANENT",
"maximumEngines": 10,
"expired": false,
"serialNumber": "AAAA-AAAA-AAAA",
"maximumAuthors": 10
}`

cases := []testCase{
{
name: "unknown flag",
Expand All @@ -24,52 +34,113 @@ func TestLicenseStatus(t *testing.T) {
wantErrOutputRegex: "unknown flag: --badflag",
},
{
name: "500 bad status code",
name: "500 bad status code v3",
statusCode: http.StatusInternalServerError,
wantErrText: "500 Internal Server Error",
args: []string{"license", "status"},
args: []string{"license", "status", "--api-version", "v3"},
},
{
name: "404 bad status code",
name: "404 bad status code v3",
statusCode: http.StatusNotFound,
wantErrText: "404 Not Found",
args: []string{"license", "status"},
args: []string{"license", "status", "--api-version", "v3"},
},
{
name: "get license status table output",
name: "get license status table output v3",
statusCode: http.StatusOK,
args: []string{"license", "status"},
args: []string{"license", "status", "--api-version", "v3"},
wantOutputRegex: "^[\\s]*EXPIRY DATE[\\s]*MAXIMUM ENGINES[\\s]*SERIAL NUMBER[\\s]*IS LICENSE EXPIRED[\\s]*IS LICENSED[\\s]*MAXIMUM AUTHORS[\\s]*PERMANENT[\\s]*10[\\s]*AAAA-AAAA-AAAA[\\s]*false[\\s]*true[\\s]*10[\\s]*$",
body: responseV3,
},
{
name: "get license status no headers",
name: "get license status no headers v3",
statusCode: http.StatusOK,
body: responseV3,
args: []string{"license", "status", "--no-headers"},
args: []string{"license", "status", "--no-headers", "--api-version", "v3"},
wantOutputRegex: "^[\\s]*PERMANENT[\\s]*10[\\s]*AAAA-AAAA-AAAA[\\s]*false[\\s]*true[\\s]*10[\\s]*$",
},
{
name: "get license status json",
name: "get license status json v3",
statusCode: http.StatusOK,
args: []string{"license", "status", "--json"},
args: []string{"license", "status", "--json", "--api-version", "v3"},
body: responseV3,
wantOutputJson: responseV3,
},
{
name: "get license status json",
name: "get license status json output flag v3",
statusCode: http.StatusOK,
args: []string{"license", "status", "--output=json"},
args: []string{"license", "status", "--output=json", "--api-version", "v3"},
body: responseV3,
wantOutputJson: responseV3,
},
{
name: "get license status custom columns",
name: "get license status custom columns v3",
statusCode: http.StatusOK,
body: responseV3,
args: []string{"license", "status", "--output", "custom-columns=SERIAL:.serialNumber,LICENSED:.isLicensed"},
args: []string{"license", "status", "--output", "custom-columns=SERIAL:.serialNumber,LICENSED:.isLicensed", "--api-version", "v3"},
wantOutputRegex: "^[\\s]*SERIAL[\\s]*LICENSED[\\s]*AAAA-AAAA-AAAA[\\s]*true[\\s]*$",
},
{
name: "500 bad status code v4",
statusCode: http.StatusInternalServerError,
wantErrText: "500 Internal Server Error",
args: []string{"license", "status", "--api-version", "v4"},
},
{
name: "404 bad status code v4",
statusCode: http.StatusNotFound,
wantErrText: "404 Not Found",
args: []string{"license", "status", "--api-version", "v4"},
},
{
name: "get license status table output v4",
statusCode: http.StatusOK,
args: []string{"license", "status", "--api-version", "v4"},
wantOutputRegex: "^[\\s]*LICENSED[\\s]*EXPIRATION[\\s]*MAXIMUM ENGINES[\\s]*EXPIRED[\\s]*SERIAL NUMBER[\\s]*MAXIMUM AUTHORS[\\s]*true[\\s]*PERMANENT[\\s]*10[\\s]*false[\\s]*AAAA-AAAA-AAAA[\\s]*10[\\s]*$",
body: responseV4,
},
{
name: "get license status no headers v4",
statusCode: http.StatusOK,
body: responseV4,
args: []string{"license", "status", "--no-headers", "--api-version", "v4"},
wantOutputRegex: "^[\\s]*true[\\s]*PERMANENT[\\s]*10[\\s]*false[\\s]*AAAA-AAAA-AAAA[\\s]*10[\\s]*$",
},
{
name: "get license status json v4",
statusCode: http.StatusOK,
args: []string{"license", "status", "--json", "--api-version", "v4"},
body: responseV4,
wantOutputJson: responseV4,
},
{
name: "get license status json output flag v4",
statusCode: http.StatusOK,
args: []string{"license", "status", "--output=json", "--api-version", "v4"},
body: responseV4,
wantOutputJson: responseV4,
},
{
name: "get license status custom columns v4",
statusCode: http.StatusOK,
body: responseV4,
args: []string{"license", "status", "--output", "custom-columns=SERIAL:.serialNumber,LICENSED:.licensed", "--api-version", "v4"},
wantOutputRegex: "^[\\s]*SERIAL[\\s]*LICENSED[\\s]*AAAA-AAAA-AAAA[\\s]*true[\\s]*$",
},
{
name: "get license status table output (no explicit version)",
statusCode: http.StatusOK,
args: []string{"license", "status"},
wantOutputRegex: "^[\\s]*LICENSED[\\s]*EXPIRATION[\\s]*MAXIMUM ENGINES[\\s]*EXPIRED[\\s]*SERIAL NUMBER[\\s]*MAXIMUM AUTHORS[\\s]*true[\\s]*PERMANENT[\\s]*10[\\s]*false[\\s]*AAAA-AAAA-AAAA[\\s]*10[\\s]*$",
body: responseV4,
},
{
name: "get license status json (no explicit version)",
statusCode: http.StatusOK,
args: []string{"license", "status", "--json"},
body: responseV4,
wantOutputJson: responseV4,
},
}

runTests(cases, t)
Expand Down
41 changes: 37 additions & 4 deletions cmd/machinekey.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,62 @@ import (
"net/http"

"github.com/spf13/cobra"
"github.com/spf13/viper"
)

type MachineKey struct {
MachineKey string `json:"machineKey"`
}

type machineKeyFlags struct {
apiVersion apiVersionFlag
}

var machineKeyV4BuildThreshold = 23319

func newMachineKeyCmd() *cobra.Command {
return &cobra.Command{
f := machineKeyFlags{}
cmd := &cobra.Command{
Use: "machinekey",
Short: "Retrieves machine key of the machine running FME Flow.",
Long: `Retrieves machine key of the machine running FME Flow.`,
Args: NoArgs,
RunE: machineKeyRun(),
RunE: machineKeyRun(&f),
}

cmd.Flags().Var(&f.apiVersion, "api-version", "The api version to use when contacting FME Server. Must be one of v3 or v4")
cmd.Flags().MarkHidden("api-version")
cmd.RegisterFlagCompletionFunc("api-version", apiVersionFlagCompletion)

return cmd
}

func machineKeyRun() func(cmd *cobra.Command, args []string) error {
func machineKeyRun(f *machineKeyFlags) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
// set up http
client := &http.Client{}

// get build to decide if we should use v3 or v4
// FME Server 2023.0+ and later can use v4. Otherwise fall back to v3
if f.apiVersion == "" {
fmeflowBuild := viper.GetInt("build")
if fmeflowBuild < machineKeyV4BuildThreshold {
f.apiVersion = apiVersionFlagV3
} else {
f.apiVersion = apiVersionFlagV4
}
}

// v3 and v4 work exactly the same, so we can just change the endpoint
var endpoint string
if f.apiVersion == "v4" {
endpoint = "/fmeapiv4/license/machinekey"
} else {
endpoint = "/fmerest/v3/licensing/machinekey"
}

// call the status endpoint to see if it is finished
request, err := buildFmeFlowRequest("/fmerest/v3/licensing/machinekey", "GET", nil)
request, err := buildFmeFlowRequest(endpoint, "GET", nil)
if err != nil {
return err
}
Expand Down
Loading