diff --git a/cmd/migrationTasks.go b/cmd/migrationTasks.go index afe7f20..6ee088c 100644 --- a/cmd/migrationTasks.go +++ b/cmd/migrationTasks.go @@ -13,17 +13,43 @@ import ( "github.com/jedib0t/go-pretty/v6/table" "github.com/spf13/cobra" + "github.com/spf13/viper" ) // generated using https://mholt.github.io/json-to-go/ -type migrationTasks struct { +type migrationTasksV4 struct { Offset int `json:"offset"` Limit int `json:"limit"` TotalCount int `json:"totalCount"` - Items []migrationTask + Items []migrationTaskV4 } -type migrationTask struct { +type migrationTasksV3 struct { + Offset int `json:"offset"` + Limit int `json:"limit"` + TotalCount int `json:"totalCount"` + Items []migrationTaskV3 +} + +type migrationTaskV4 struct { + ID int `json:"id"` + Type string `json:"type"` + Username string `json:"username"` + UserID string `json:"userID"` + StartDate time.Time `json:"startDate"` + FinishedDate time.Time `json:"finishedDate"` + Status string `json:"status"` + SuccessTopic string `json:"successTopic"` + FailureTopic string `json:"failureTopic"` + ResourceName string `json:"resourceName"` + PackagePath string `json:"packagePath"` + PackageName string `json:"packageName"` + ImportMode string `json:"importMode"` + PauseNotifications bool `json:"pauseNotifications"` + Result string `json:"result"` +} + +type migrationTaskV3 struct { DisableProjectItems bool `json:"disableProjectItems"` Result string `json:"result"` ImportMode string `json:"importMode"` @@ -51,8 +77,11 @@ type migrationTasksFlags struct { migrationTaskFile string outputType string noHeaders bool + apiVersion apiVersionFlag } +var migrationTasksV4BuildThreshold = 25208 + func newMigrationTasksCmd() *cobra.Command { f := migrationTasksFlags{} cmd := &cobra.Command{ @@ -74,7 +103,13 @@ func newMigrationTasksCmd() *cobra.Command { # Output the migration log for a given id to a local file fmeflow migration tasks --id 1 --log --file my-backup-log.txt - + + # Output the migration log for a given id parsed as JSON to the console + fmeflow migration tasks --id 1 --log --json + + # Output the migration log for a given id parsed as JSON to a local file + fmeflow migration tasks --id 1 --log --json --file my-backup-log.txt + # Output just the start and end time of the a given id fmeflow migration tasks --id 1 --output="custom-columns=Start Time:.startDate,End Time:.finishedDate"`, Args: NoArgs, @@ -91,6 +126,7 @@ func newMigrationTasksCmd() *cobra.Command { cmd.Flags().StringVar(&f.migrationTaskFile, "file", "", "File to save the log to.") 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 } @@ -106,15 +142,130 @@ func migrationTasksRun(f *migrationTasksFlags) func(cmd *cobra.Command, args []s // set up http client := &http.Client{} - var outputTasks []migrationTask + if f.apiVersion == "" { + if viper.GetInt("build") < migrationTasksV4BuildThreshold { + f.apiVersion = apiVersionFlagV3 + } else { + f.apiVersion = apiVersionFlagV4 + } + } + + if f.apiVersion == apiVersionFlagV4 { + var outputTasks []migrationTaskV4 - if !f.migrationTaskLog { // output one or more tasks - var responseData []byte - if f.migrationTaskId == -1 { - request, err := buildFmeFlowRequest("/fmerest/v3/migration/tasks", "GET", nil) + if !f.migrationTaskLog { // output one or more tasks + var responseData []byte + if f.migrationTaskId == -1 { + request, err := buildFmeFlowRequest("/fmeapiv4/migrations/tasks", "GET", nil) + if err != nil { + return err + } + response, err := client.Do(&request) + if err != nil { + return err + } else if response.StatusCode != 200 { + return errors.New(response.Status) + } + responseData, err = io.ReadAll(response.Body) + if err != nil { + return err + } + + var result migrationTasksV4 + if err := json.Unmarshal(responseData, &result); err != nil { + return err + } else { + outputTasks = result.Items + } + } else { + endpoint := "/fmeapiv4/migrations/tasks/" + strconv.Itoa(f.migrationTaskId) + request, err := buildFmeFlowRequest(endpoint, "GET", nil) + if err != nil { + return err + } + response, err := client.Do(&request) + if err != nil { + return err + } else if response.StatusCode != 200 { + return errors.New(response.Status) + } + + responseData, err = io.ReadAll(response.Body) + if err != nil { + return err + } + + var result migrationTaskV4 + if err := json.Unmarshal(responseData, &result); err != nil { + return err + } else { + outputTasks = []migrationTaskV4{result} + } + } + + if f.outputType == "table" { + t := table.NewWriter() + t.SetStyle(defaultStyle) + + t.AppendHeader(table.Row{"ID", "Type", "Username", "Start Time", "End Time", "Status"}) + + for _, element := range outputTasks { + t.AppendRow(table.Row{element.ID, element.Type, element.Username, element.StartDate, element.FinishedDate, element.Status}) + } + if f.noHeaders { + t.ResetHeaders() + } + fmt.Fprintln(cmd.OutOrStdout(), t.Render()) + // output the raw json but formatted + } 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{} + for _, element := range outputTasks { + mJson, err := json.Marshal(element) + 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") + } + } else if f.migrationTaskId != -1 && f.migrationTaskLog && f.outputType != "json" { + endpoint := "/fmeapiv4/migrations/tasks/" + strconv.Itoa(f.migrationTaskId) + "/log" + request, err := buildFmeFlowRequest(endpoint, "GET", nil) if err != nil { return err } + + request.Header.Add("Accept", "application/octet-stream") response, err := client.Do(&request) if err != nil { return err @@ -122,140 +273,220 @@ func migrationTasksRun(f *migrationTasksFlags) func(cmd *cobra.Command, args []s return errors.New(response.Status) } - responseData, err = io.ReadAll(response.Body) + responseData, err := io.ReadAll(response.Body) if err != nil { return err } - var result migrationTasks - if err := json.Unmarshal(responseData, &result); err != nil { - return err + if f.migrationTaskFile == "" { + fmt.Fprintln(cmd.OutOrStdout(), string(responseData)) } else { - outputTasks = result.Items + // Create the output file + out, err := os.Create(f.migrationTaskFile) + if err != nil { + return err + } + defer out.Close() + + // use Copy so that it doesn't store the entire file in memory + _, err = io.Copy(out, strings.NewReader(string(responseData))) + if err != nil { + return err + } + + fmt.Fprintln(cmd.OutOrStdout(), "Log file downloaded to "+f.migrationTaskFile) } - } else { - endpoint := "/fmerest/v3/migration/tasks/id/" + strconv.Itoa(f.migrationTaskId) + + } else if f.migrationTaskId != -1 && f.migrationTaskLog && f.outputType == "json" { + endpoint := "/fmeapiv4/migrations/tasks/" + strconv.Itoa(f.migrationTaskId) + "/log/parsed" request, err := buildFmeFlowRequest(endpoint, "GET", nil) if err != nil { return err } + request.Header.Add("Accept", "application/json") response, err := client.Do(&request) if err != nil { return err } else if response.StatusCode != 200 { return errors.New(response.Status) } - - responseData, err = io.ReadAll(response.Body) + responseData, err := io.ReadAll(response.Body) if err != nil { return err } - var result migrationTask - if err := json.Unmarshal(responseData, &result); err != nil { - return err + if f.migrationTaskFile == "" { + fmt.Fprintln(cmd.OutOrStdout(), string(responseData)) } else { - outputTasks = []migrationTask{result} + // Create the output file + out, err := os.Create(f.migrationTaskFile) + if err != nil { + return err + } + defer out.Close() + + // use Copy so that it doesn't store the entire file in memory + _, err = io.Copy(out, strings.NewReader(string(responseData))) + if err != nil { + return err + } + + fmt.Fprintln(cmd.OutOrStdout(), "Log file downloaded to "+f.migrationTaskFile) } + } - if f.outputType == "table" { - t := table.NewWriter() - t.SetStyle(defaultStyle) + } else if f.apiVersion == apiVersionFlagV3 { + var outputTasks []migrationTaskV3 - t.AppendHeader(table.Row{"ID", "Type", "Username", "Start Time", "End Time", "Status"}) + if !f.migrationTaskLog { // output one or more tasks + var responseData []byte + if f.migrationTaskId == -1 { + request, err := buildFmeFlowRequest("/fmerest/v3/migration/tasks", "GET", nil) + if err != nil { + return err + } + response, err := client.Do(&request) + if err != nil { + return err + } else if response.StatusCode != 200 { + return errors.New(response.Status) + } - for _, element := range outputTasks { - t.AppendRow(table.Row{element.ID, element.Type, element.UserName, element.StartDate, element.FinishedDate, element.Status}) - } - if f.noHeaders { - t.ResetHeaders() - } - fmt.Fprintln(cmd.OutOrStdout(), t.Render()) - // output the raw json but formatted - } 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="):] - } + responseData, err = io.ReadAll(response.Body) + if err != nil { + return err + } + + var result migrationTasksV3 + if err := json.Unmarshal(responseData, &result); err != nil { + return err + } else { + outputTasks = result.Items + } + } else { + endpoint := "/fmerest/v3/migration/tasks/id/" + strconv.Itoa(f.migrationTaskId) + request, err := buildFmeFlowRequest(endpoint, "GET", nil) + if err != nil { + return err + } + response, err := client.Do(&request) + if err != nil { + return err + } else if response.StatusCode != 200 { + return errors.New(response.Status) + } + + responseData, err = io.ReadAll(response.Body) + if err != nil { + return err + } - if len(columnsString) == 0 { - return errors.New("custom-columns format specified but no custom columns given") + var result migrationTaskV3 + if err := json.Unmarshal(responseData, &result); err != nil { + return err + } else { + outputTasks = []migrationTaskV3{result} + } } - // we have to marshal the Items array, then create an array of marshalled items - // to pass to the creation of the table. - marshalledItems := [][]byte{} - for _, element := range outputTasks { - mJson, err := json.Marshal(element) + if f.outputType == "table" { + t := table.NewWriter() + t.SetStyle(defaultStyle) + + t.AppendHeader(table.Row{"ID", "Type", "Username", "Start Time", "End Time", "Status"}) + + for _, element := range outputTasks { + t.AppendRow(table.Row{element.ID, element.Type, element.UserName, element.StartDate, element.FinishedDate, element.Status}) + } + if f.noHeaders { + t.ResetHeaders() + } + fmt.Fprintln(cmd.OutOrStdout(), t.Render()) + // output the raw json but formatted + } 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 = append(marshalledItems, mJson) + // we have to marshal the Items array, then create an array of marshalled items + // to pass to the creation of the table. + marshalledItems := [][]byte{} + for _, element := range outputTasks { + mJson, err := json.Marshal(element) + 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") } - columnsInput := strings.Split(columnsString, ",") - t, err := createTableFromCustomColumns(marshalledItems, columnsInput) + } else if f.migrationTaskId != -1 && f.migrationTaskLog { + endpoint := "/fmerest/v3/migration/tasks/id/" + strconv.Itoa(f.migrationTaskId) + "/log" + request, err := buildFmeFlowRequest(endpoint, "GET", nil) if err != nil { return err } - if f.noHeaders { - t.ResetHeaders() - } - fmt.Fprintln(cmd.OutOrStdout(), t.Render()) - } else { - return errors.New("invalid output format specified") - } - - } else if f.migrationTaskId != -1 && f.migrationTaskLog { - endpoint := "/fmerest/v3/migration/tasks/id/" + strconv.Itoa(f.migrationTaskId) + "/log" - request, err := buildFmeFlowRequest(endpoint, "GET", nil) - if err != nil { - return err - } - - request.Header.Add("Accept", "application/octet-stream") - response, err := client.Do(&request) - if err != nil { - return err - } else if response.StatusCode != 200 { - return errors.New(response.Status) - } - - responseData, err := io.ReadAll(response.Body) - if err != nil { - return err - } - if f.migrationTaskFile == "" { - fmt.Fprintln(cmd.OutOrStdout(), string(responseData)) - } else { - // Create the output file - out, err := os.Create(f.migrationTaskFile) + request.Header.Add("Accept", "application/octet-stream") + response, err := client.Do(&request) if err != nil { return err + } else if response.StatusCode != 200 { + return errors.New(response.Status) } - defer out.Close() - // use Copy so that it doesn't store the entire file in memory - _, err = io.Copy(out, strings.NewReader(string(responseData))) + responseData, err := io.ReadAll(response.Body) if err != nil { return err } - fmt.Fprintln(cmd.OutOrStdout(), "Log file downloaded to "+f.migrationTaskFile) - } + if f.migrationTaskFile == "" { + fmt.Fprintln(cmd.OutOrStdout(), string(responseData)) + } else { + // Create the output file + out, err := os.Create(f.migrationTaskFile) + if err != nil { + return err + } + defer out.Close() - } + // use Copy so that it doesn't store the entire file in memory + _, err = io.Copy(out, strings.NewReader(string(responseData))) + if err != nil { + return err + } + + fmt.Fprintln(cmd.OutOrStdout(), "Log file downloaded to "+f.migrationTaskFile) + } + } + } return nil } } diff --git a/cmd/migrationTasks_test.go b/cmd/migrationTasks_v3_test.go similarity index 87% rename from cmd/migrationTasks_test.go rename to cmd/migrationTasks_v3_test.go index 6d05396..d2bffb3 100644 --- a/cmd/migrationTasks_test.go +++ b/cmd/migrationTasks_v3_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func TestMigrationTasks(t *testing.T) { +func TestMigrationTasksV3(t *testing.T) { // standard responses for v3 responseV3 := `{ "offset": -1, @@ -63,18 +63,21 @@ func TestMigrationTasks(t *testing.T) { statusCode: http.StatusOK, args: []string{"migration", "tasks", "--badflag"}, wantErrOutputRegex: "unknown flag: --badflag", + fmeflowBuild: 23166, }, { - name: "500 bad status code", - statusCode: http.StatusInternalServerError, - wantErrText: "500 Internal Server Error", - args: []string{"migration", "tasks"}, + name: "500 bad status code", + statusCode: http.StatusInternalServerError, + wantErrText: "500 Internal Server Error", + args: []string{"migration", "tasks"}, + fmeflowBuild: 23166, }, { - name: "404 bad status code", - statusCode: http.StatusNotFound, - wantErrText: "404 Not Found", - args: []string{"migration", "tasks"}, + name: "404 bad status code", + statusCode: http.StatusNotFound, + wantErrText: "404 Not Found", + args: []string{"migration", "tasks"}, + fmeflowBuild: 23166, }, { name: "get migration tasks table output", @@ -82,6 +85,7 @@ func TestMigrationTasks(t *testing.T) { args: []string{"migration", "tasks"}, wantOutputRegex: "^[\\s]*ID[\\s]*TYPE[\\s]*USERNAME[\\s]*START TIME[\\s]*END TIME[\\s]*STATUS[\\s]*2[\\s]*EXPORT[\\s]*admin[\\s]*2022-11-09 22:40:51 \\+0000 UTC[\\s]*2022-11-09 22:40:52 \\+0000 UTC[\\s]*SUCCESS[\\s]*1[\\s]*EXPORT[\\s]*admin[\\s]*2022-11-09 21:59:22 \\+0000 UTC[\\s]*2022-11-09 21:59:24 \\+0000 UTC[\\s]*SUCCESS[\\s]*$", body: responseV3, + fmeflowBuild: 23166, }, { name: "get migration tasks no headers", @@ -89,6 +93,7 @@ func TestMigrationTasks(t *testing.T) { body: responseV3, args: []string{"migration", "tasks", "--no-headers"}, wantOutputRegex: "^[\\s]*2[\\s]*EXPORT[\\s]*admin[\\s]*2022-11-09 22:40:51 \\+0000 UTC[\\s]*2022-11-09 22:40:52 \\+0000 UTC[\\s]*SUCCESS[\\s]*1[\\s]*EXPORT[\\s]*admin[\\s]*2022-11-09 21:59:22 \\+0000 UTC[\\s]*2022-11-09 21:59:24 \\+0000 UTC[\\s]*SUCCESS[\\s]*$", + fmeflowBuild: 23166, }, { name: "get migration by id", @@ -96,6 +101,7 @@ func TestMigrationTasks(t *testing.T) { body: responseV3One, args: []string{"migration", "tasks", "--id", "1"}, wantOutputRegex: "^[\\s]*ID[\\s]*TYPE[\\s]*USERNAME[\\s]*START TIME[\\s]*END TIME[\\s]*STATUS[\\s]*1[\\s]*EXPORT[\\s]*admin[\\s]*2022-11-09 21:59:22 \\+0000 UTC[\\s]*2022-11-09 21:59:24 \\+0000 UTC[\\s]*SUCCESS[\\s]*$", + fmeflowBuild: 23166, }, { name: "get migration log", @@ -103,6 +109,7 @@ func TestMigrationTasks(t *testing.T) { body: migrationLogResponse, args: []string{"migration", "tasks", "--id", "1", "--log"}, wantOutputRegex: migrationLogResponse, + fmeflowBuild: 23166, }, { name: "get migration tasks json", @@ -110,6 +117,7 @@ func TestMigrationTasks(t *testing.T) { args: []string{"migration", "tasks", "--json"}, body: responseV3, wantOutputJson: responseV3, + fmeflowBuild: 23166, }, { name: "get migration by id custom columns", @@ -117,6 +125,7 @@ func TestMigrationTasks(t *testing.T) { body: responseV3One, args: []string{"migration", "tasks", "--id", "1", "--output", "custom-columns=PACKAGE:.packageName,CONTENT:.contentType"}, wantOutputRegex: "^[\\s]*PACKAGE[\\s]*CONTENT[\\s]*ServerConfigPackage.fsconfig[\\s]*SYSTEM[\\s]*$", + fmeflowBuild: 23166, }, { name: "get migrations custom columns", @@ -124,6 +133,7 @@ func TestMigrationTasks(t *testing.T) { body: responseV3, args: []string{"migration", "tasks", "--output", "custom-columns=PACKAGE:.packageName,CONTENT:.contentType"}, wantOutputRegex: "^[\\s]*PACKAGE[\\s]*CONTENT[\\s]*ServerConfigPackage.fsconfig[\\s]*SYSTEM[\\s]*ServerConfigPackage.fsconfig[\\s]*SYSTEM[\\s]*$", + fmeflowBuild: 23166, }, } diff --git a/cmd/migrationTasks_v4_test.go b/cmd/migrationTasks_v4_test.go new file mode 100644 index 0000000..8a23f87 --- /dev/null +++ b/cmd/migrationTasks_v4_test.go @@ -0,0 +1,145 @@ +package cmd + +import ( + "net/http" + "testing" +) + +func TestMigrationTasksV4(t *testing.T) { + responseV4 := `{ + "offset": -1, + "limit": -1, + "totalCount": 2, + "items": [ + { + "result": "Successful export", + "id": 2, + "packageName": "ServerConfigPackage.fsconfig", + "type": "EXPORT", + "username": "admin", + "startDate": "2022-11-09T22:40:51Z", + "finishedDate": "2022-11-09T22:40:52Z", + "status": "SUCCESS" + }, + { + "result": "Successful export", + "id": 1, + "packageName": "ServerConfigPackage.fsconfig", + "type": "EXPORT", + "username": "admin", + "startDate": "2022-11-09T21:59:22Z", + "finishedDate": "2022-11-09T21:59:24Z", + "status": "SUCCESS" + } + ] + }` + + migrationLogResponse := `Opening log file using character encoding UTF-8 + Wed-09-Nov-2022 09:59:22.279 PM INFORM Thread-4 409511 : Exporting migration package. + Wed-09-Nov-2022 09:59:22.324 PM INFORM Thread-4 409574 : Exporting scheduled task Category: Utilities Name: Backup_Configuration. + Wed-09-Nov-2022 09:59:22.326 PM INFORM Thread-4 409574 : Exporting scheduled task Category: Dashboards Name: DashboardStatisticsGathering.` + + migrationLogParsedResponse := `{"items":[{"number":1,"time":"2025-11-19T20:24:33.836Z","status":"INFORM","message":"Getting migration package from shared resource \"FME_SHAREDRESOURCE_SYSTEM\" with path \"/temp/tomcat/5c2c13eac945440fb4c71ed1d1c51587/localhost_2025-11-19-T104055_b25606.fsconfig\"...","line":"Wed-19-Nov-2025 08:24:33.836 PM INFORM Thread-8 409520 : Getting migration package from shared resource \"FME_SHAREDRESOURCE_SYSTEM\" with path \"/temp/tomcat/5c2c13eac945440fb4c71ed1d1c51587/localhost_2025-11-19-T104055_b25606.fsconfig\"..."},{"number":2,"time":"2025-11-19T20:24:33.844Z","status":"INFORM","message":"Unzipping migration package.","line":"Wed-19-Nov-2025 08:24:33.844 PM INFORM Thread-8 409503 : Unzipping migration package."},{"number":3,"time":"2025-11-19T20:24:34.172Z","status":"INFORM","message":"Upgrading migration package schema version.","line":"Wed-19-Nov-2025 08:24:34.172 PM INFORM Thread-8 409504 : Upgrading migration package schema version."}],"totalCount":188,"limit":100,"offset":0}` + + responseV3One := `{ + "result": "Successful export", + "id": 1, + "packageName": "ServerConfigPackage.fsconfig", + "type": "EXPORT", + "username": "admin", + "startDate": "2022-11-09T21:59:22Z", + "finishedDate": "2022-11-09T21:59:24Z", + "status": "SUCCESS" + }` + + cases := []testCase{ + { + name: "unknown flag", + statusCode: http.StatusOK, + args: []string{"migration", "tasks", "--badflag"}, + wantErrOutputRegex: "unknown flag: --badflag", + fmeflowBuild: 26000, + }, + { + name: "500 bad status code", + statusCode: http.StatusInternalServerError, + wantErrText: "500 Internal Server Error", + args: []string{"migration", "tasks"}, + fmeflowBuild: 26000, + }, + { + name: "404 bad status code", + statusCode: http.StatusNotFound, + wantErrText: "404 Not Found", + args: []string{"migration", "tasks"}, + fmeflowBuild: 26000, + }, + { + name: "get migration tasks table output", + statusCode: http.StatusOK, + args: []string{"migration", "tasks"}, + wantOutputRegex: "^[\\s]*ID[\\s]*TYPE[\\s]*USERNAME[\\s]*START TIME[\\s]*END TIME[\\s]*STATUS[\\s]*2[\\s]*EXPORT[\\s]*admin[\\s]*2022-11-09 22:40:51 \\+0000 UTC[\\s]*2022-11-09 22:40:52 \\+0000 UTC[\\s]*SUCCESS[\\s]*1[\\s]*EXPORT[\\s]*admin[\\s]*2022-11-09 21:59:22 \\+0000 UTC[\\s]*2022-11-09 21:59:24 \\+0000 UTC[\\s]*SUCCESS[\\s]*$", + body: responseV4, + fmeflowBuild: 26000, + }, + { + name: "get migration tasks no headers", + statusCode: http.StatusOK, + body: responseV4, + args: []string{"migration", "tasks", "--no-headers"}, + wantOutputRegex: "^[\\s]*2[\\s]*EXPORT[\\s]*admin[\\s]*2022-11-09 22:40:51 \\+0000 UTC[\\s]*2022-11-09 22:40:52 \\+0000 UTC[\\s]*SUCCESS[\\s]*1[\\s]*EXPORT[\\s]*admin[\\s]*2022-11-09 21:59:22 \\+0000 UTC[\\s]*2022-11-09 21:59:24 \\+0000 UTC[\\s]*SUCCESS[\\s]*$", + fmeflowBuild: 26000, + }, + { + name: "get migration by id", + statusCode: http.StatusOK, + body: responseV3One, + args: []string{"migration", "tasks", "--id", "1"}, + wantOutputRegex: "^[\\s]*ID[\\s]*TYPE[\\s]*USERNAME[\\s]*START TIME[\\s]*END TIME[\\s]*STATUS[\\s]*1[\\s]*EXPORT[\\s]*admin[\\s]*2022-11-09 21:59:22 \\+0000 UTC[\\s]*2022-11-09 21:59:24 \\+0000 UTC[\\s]*SUCCESS[\\s]*$", + fmeflowBuild: 26000, + }, + { + name: "get migration log", + statusCode: http.StatusOK, + body: migrationLogResponse, + args: []string{"migration", "tasks", "--id", "1", "--log"}, + wantOutputRegex: migrationLogResponse, + fmeflowBuild: 26000, + }, + { + name: "get migration tasks json", + statusCode: http.StatusOK, + args: []string{"migration", "tasks", "--json"}, + body: responseV4, + wantOutputJson: responseV4, + fmeflowBuild: 26000, + }, + { + name: "get migration by id custom columns", + statusCode: http.StatusOK, + body: responseV3One, + args: []string{"migration", "tasks", "--id", "1", "--output", "custom-columns=PACKAGE:.packageName,STATUS:.status"}, + wantOutputRegex: "^[\\s]*PACKAGE[\\s]*STATUS[\\s]*ServerConfigPackage.fsconfig[\\s]*SUCCESS[\\s]*$", + fmeflowBuild: 26000, + }, + { + name: "get migrations custom columns", + statusCode: http.StatusOK, + body: responseV4, + args: []string{"migration", "tasks", "--output", "custom-columns=PACKAGE:.packageName,STATUS:.status"}, + wantOutputRegex: "^[\\s]*PACKAGE[\\s]*STATUS[\\s]*ServerConfigPackage.fsconfig[\\s]*SUCCESS[\\s]*ServerConfigPackage.fsconfig[\\s]*SUCCESS[\\s]*$", + fmeflowBuild: 26000, + }, + { + name: "get migration log parsed json", + statusCode: http.StatusOK, + body: migrationLogParsedResponse, + args: []string{"migration", "tasks", "--id", "1", "--log", "--json"}, + wantOutputJson: migrationLogParsedResponse, + fmeflowBuild: 26000, + }, + } + + runTests(cases, t) + +} diff --git a/docs/fmeflow_migration_tasks.md b/docs/fmeflow_migration_tasks.md index ae6eb95..dbf9971 100644 --- a/docs/fmeflow_migration_tasks.md +++ b/docs/fmeflow_migration_tasks.md @@ -28,7 +28,13 @@ fmeflow migration tasks [flags] # Output the migration log for a given id to a local file fmeflow migration tasks --id 1 --log --file my-backup-log.txt - + + # Output the migration log for a given id parsed as JSON to the console + fmeflow migration tasks --id 1 --log --json + + # Output the migration log for a given id parsed as JSON to a local file + fmeflow migration tasks --id 1 --log --json --file my-backup-log.txt + # Output just the start and end time of the a given id fmeflow migration tasks --id 1 --output="custom-columns=Start Time:.startDate,End Time:.finishedDate" ``` @@ -36,12 +42,13 @@ fmeflow migration tasks [flags] ### Options ``` - --file string File to save the log to. - -h, --help help for tasks - --id int Retrieves the record for a migration task according to the given ID. (default -1) - --log Downloads the log file of a migration task. - --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 + --file string File to save the log to. + -h, --help help for tasks + --id int Retrieves the record for a migration task according to the given ID. (default -1) + --log Downloads the log file of a migration task. + --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