diff --git a/cmd/info.go b/cmd/info.go index 2b7a74d..e114948 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -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"` @@ -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{ @@ -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 } @@ -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 } @@ -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 } } diff --git a/cmd/info_test.go b/cmd/info_v3_test.go similarity index 78% rename from cmd/info_test.go rename to cmd/info_v3_test.go index ab1d96b..31511ed 100644 --- a/cmd/info_test.go +++ b/cmd/info_v3_test.go @@ -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, @@ -29,18 +29,21 @@ 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", @@ -48,6 +51,7 @@ func TestInfo(t *testing.T) { 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", @@ -55,6 +59,7 @@ func TestInfo(t *testing.T) { 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", @@ -62,6 +67,7 @@ func TestInfo(t *testing.T) { args: []string{"info", "--json"}, body: responseV3, wantOutputJson: responseV3, + fmeflowBuild: 23166, }, { name: "get info json via output type", @@ -69,6 +75,7 @@ func TestInfo(t *testing.T) { args: []string{"info", "--output=json"}, body: responseV3, wantOutputJson: responseV3, + fmeflowBuild: 23166, }, { name: "get info custom columns", @@ -76,6 +83,7 @@ func TestInfo(t *testing.T) { 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, }, } diff --git a/cmd/info_v4_test.go b/cmd/info_v4_test.go new file mode 100644 index 0000000..49007f1 --- /dev/null +++ b/cmd/info_v4_test.go @@ -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) +} diff --git a/docs/fmeflow_info.md b/docs/fmeflow_info.md index 357c125..5fdbf65 100644 --- a/docs/fmeflow_info.md +++ b/docs/fmeflow_info.md @@ -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