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
148 changes: 93 additions & 55 deletions cmd/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,19 @@ import (
"strings"

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

type FMEFlowInfo struct {
type FMEFlowInfoV4 struct {
BuildNumber int `json:"buildNumber"`
BuildString string `json:"buildString"`
ReleaseYear int `json:"releaseYear"`
MajorVersion int `json:"majorVersion"`
MinorVersion int `json:"minorVersion"`
HotfixVersion int `json:"hotfixVersion"`
}

type FMEFlowInfoV3 struct {
CurrentTime string `json:"currentTime"`
LicenseManagement bool `json:"licenseManagement"`
Build string `json:"build"`
Expand All @@ -22,8 +32,11 @@ type FMEFlowInfo struct {
type infoFlags struct {
outputType string
noHeaders bool
apiVersion apiVersionFlag
}

var infoV4BuildThreshold = 25208

func newInfoCmd() *cobra.Command {
f := infoFlags{}
cmd := &cobra.Command{
Expand All @@ -37,14 +50,15 @@ func newInfoCmd() *cobra.Command {
# Output FME Server information in json
fmeflow info --json

# Output just the build string with no column headers
fmeflow info --output=custom-columns="BUILD:.build" --no-headers
# Output just the build number string with no column headers
fmeflow info --output=custom-columns="BUILD NUMBER:.buildNumber" --no-headers
`,
Args: NoArgs,
RunE: infoRun(&f),
}
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")
return cmd
}

Expand All @@ -55,11 +69,25 @@ func infoRun(f *infoFlags) func(cmd *cobra.Command, args []string) error {
f.outputType = "json"
}

if f.apiVersion == "" {
if viper.GetInt("build") < infoV4BuildThreshold {
f.apiVersion = apiVersionFlagV3
} else {
f.apiVersion = apiVersionFlagV4
}
}

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

// call the status endpoint to see if it is finished
request, err := buildFmeFlowRequest("/fmerest/v3/info", "GET", nil)
request := http.Request{}
err := error(nil)

if f.apiVersion == "v4" {
request, err = buildFmeFlowRequest("/fmeinfo/version", "GET", nil)
} else if f.apiVersion == "v3" {
// call the status endpoint to see if it is finished
request, err = buildFmeFlowRequest("/fmerest/v3/info", "GET", nil)
}
if err != nil {
return err
}
Expand All @@ -75,60 +103,70 @@ func infoRun(f *infoFlags) func(cmd *cobra.Command, args []string) error {
return err
}

var result FMEFlowInfo
if err := json.Unmarshal(responseData, &result); err != nil {
return err
var result any
if f.apiVersion == "v4" {
var v4Result FMEFlowInfoV4
if err := json.Unmarshal(responseData, &v4Result); err != nil {
return err
}
result = v4Result
} else {
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")
}

// 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 {
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())
var v3Result FMEFlowInfoV3
if err := json.Unmarshal(responseData, &v3Result); err != nil {
return err
}
result = v3Result
}

} else {
return errors.New("invalid output format specified")
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")
}

// 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 {
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
}
}
28 changes: 18 additions & 10 deletions cmd/info_test.go → cmd/info_v3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"github.com/stretchr/testify/require"
)

func TestInfo(t *testing.T) {
// standard responses for v3 and v4
func TestInfoV3(t *testing.T) {
// standard responses for v3
responseV3 := `{
"currentTime": "Mon-14-Nov-2022 07:20:24 PM",
"licenseManagement": true,
Expand All @@ -29,53 +29,61 @@ func TestInfo(t *testing.T) {
statusCode: http.StatusOK,
args: []string{"info", "--badflag"},
wantErrOutputRegex: "unknown flag: --badflag",
fmeflowBuild: 23166,
},
{
name: "500 bad status code",
statusCode: http.StatusInternalServerError,
wantErrText: "500 Internal Server Error",
args: []string{"info"},
name: "500 bad status code",
statusCode: http.StatusInternalServerError,
wantErrText: "500 Internal Server Error",
args: []string{"info"},
fmeflowBuild: 23166,
},
{
name: "404 bad status code",
statusCode: http.StatusNotFound,
wantErrText: "404 Not Found",
args: []string{"info"},
name: "404 bad status code",
statusCode: http.StatusNotFound,
wantErrText: "404 Not Found",
args: []string{"info"},
fmeflowBuild: 23166,
},
{
name: "get info table output",
statusCode: http.StatusOK,
body: responseV3,
args: []string{"info"},
wantOutputRegex: "[\\s]*CURRENT TIME[\\s]*LICENSE MANAGEMENT[\\s]*BUILD[\\s]*TIME ZONE[\\s]*VERSION[\\s]*Mon-14-Nov-2022 07:20:24 PM[\\s]*true[\\s]*FME Server 2023.0 - Build 23166 - linux-x64[\\s]*\\+0000[\\s]*FME Server[\\s]*",
fmeflowBuild: 23166,
},
{
name: "get info no headers",
statusCode: http.StatusOK,
body: responseV3,
args: []string{"info", "--no-headers"},
wantOutputRegex: "[\\s]*Mon-14-Nov-2022 07:20:24 PM[\\s]*true[\\s]*FME Server 2023.0 - Build 23166 - linux-x64[\\s]*\\+0000[\\s]*FME Server[\\s]*",
fmeflowBuild: 23166,
},
{
name: "get info json",
statusCode: http.StatusOK,
args: []string{"info", "--json"},
body: responseV3,
wantOutputJson: responseV3,
fmeflowBuild: 23166,
},
{
name: "get info json via output type",
statusCode: http.StatusOK,
args: []string{"info", "--output=json"},
body: responseV3,
wantOutputJson: responseV3,
fmeflowBuild: 23166,
},
{
name: "get info custom columns",
statusCode: http.StatusOK,
body: responseV3,
args: []string{"info", "--output=custom-columns=TIME:.currentTime,BUILD:.build"},
wantOutputRegex: "[\\s]*TIME[\\s]*BUILD[\\s]*Mon-14-Nov-2022 07:20:24 PM[\\s]*FME Server 2023.0 - Build 23166 - linux-x64[\\s]*",
fmeflowBuild: 23166,
},
}

Expand Down
82 changes: 82 additions & 0 deletions cmd/info_v4_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package cmd

import (
"net/http"
"testing"
)

func TestInfoV4(t *testing.T) {
responseV4 := `{
"buildNumber":25606,
"buildString":"FME Flow 2025.1 - Build 25606 - linux-x64",
"releaseYear":2025,
"majorVersion":1,
"minorVersion":0,
"hotfixVersion":0
}`

cases := []testCase{
{
name: "unknown flag",
statusCode: http.StatusOK,
args: []string{"info", "--badflag"},
wantErrOutputRegex: "unknown flag: --badflag",
fmeflowBuild: 25606,
},
{
name: "500 bad status code",
statusCode: http.StatusInternalServerError,
wantErrText: "500 Internal Server Error",
args: []string{"info"},
fmeflowBuild: 25606,
},
{
name: "404 bad status code",
statusCode: http.StatusNotFound,
wantErrText: "404 Not Found",
args: []string{"info"},
fmeflowBuild: 25606,
},
{
name: "get info table output",
statusCode: http.StatusOK,
body: responseV4,
args: []string{"info"},
wantOutputRegex: "[\\s]*BUILD NUMBER[\\s]*BUILD STRING[\\s]*RELEASE YEAR[\\s]*MAJOR VERSION[\\s]*MINOR VERSION[\\s]*HOTFIX VERSION[\\s]*25606[\\s]*FME Flow 2025.1 - Build 25606 - linux-x64[\\s]*2025[\\s]*1[\\s]*0[\\s]*0[\\s]*",
fmeflowBuild: 25606,
},
{
name: "get info no headers",
statusCode: http.StatusOK,
body: responseV4,
args: []string{"info", "--no-headers"},
wantOutputRegex: "[\\s]*25606[\\s]*FME Flow 2025.1 - Build 25606 - linux-x64[\\s]*2025[\\s]*1[\\s]*0[\\s]*0[\\s]*",
fmeflowBuild: 25606,
},
{
name: "get info json",
statusCode: http.StatusOK,
args: []string{"info", "--json"},
body: responseV4,
wantOutputJson: responseV4,
fmeflowBuild: 25606,
},
{
name: "get info json via output type",
statusCode: http.StatusOK,
args: []string{"info", "--output=json"},
body: responseV4,
wantOutputJson: responseV4,
fmeflowBuild: 25606,
},
{
name: "get info custom columns",
statusCode: http.StatusOK,
body: responseV4,
args: []string{"info", "--output=custom-columns=BUILD NUMBER:.buildNumber,BUILD STRING:.buildString"},
wantOutputRegex: "[\\s]*BUILD NUMBER[\\s]*BUILD STRING[\\s]*25606[\\s]*FME Flow 2025.1 - Build 25606 - linux-x64[\\s]*",
fmeflowBuild: 25606,
},
}
runTests(cases, t)
}
11 changes: 6 additions & 5 deletions docs/fmeflow_info.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,18 @@ fmeflow info [flags]
# Output FME Server information in json
fmeflow info --json

# Output just the build string with no column headers
fmeflow info --output=custom-columns="BUILD:.build" --no-headers
# Output just the build number string with no column headers
fmeflow info --output=custom-columns="BUILD NUMBER:.buildNumber" --no-headers

```

### Options

```
-h, --help help for info
--no-headers Don't print column headers
-o, --output string Specify the output type. Should be one of table, json, or custom-columns (default "table")
--api-version string The api version to use when contacting FME Server. Must be one of v3 or v4
-h, --help help for info
--no-headers Don't print column headers
-o, --output string Specify the output type. Should be one of table, json, or custom-columns (default "table")
```

### Options inherited from parent commands
Expand Down