From 2ae2cd098ab515292d5f98ae2f1864f22d21be00 Mon Sep 17 00:00:00 2001 From: Aashir Siddiqui Date: Fri, 13 Sep 2024 15:48:19 +0100 Subject: [PATCH 01/30] CLI now supports tokens filtered by loginId Signed-off-by: Aashir Siddiqui --- docs/generated/galasactl_auth_tokens_get.md | 3 +- pkg/auth/authTokensGet.go | 56 +++++++++++++++++++++ pkg/cmd/authTokens.go | 19 +++++++ pkg/cmd/authTokensGet.go | 9 +++- 4 files changed, 85 insertions(+), 2 deletions(-) diff --git a/docs/generated/galasactl_auth_tokens_get.md b/docs/generated/galasactl_auth_tokens_get.md index 20bfe461..84c0c2b8 100644 --- a/docs/generated/galasactl_auth_tokens_get.md +++ b/docs/generated/galasactl_auth_tokens_get.md @@ -13,7 +13,8 @@ galasactl auth tokens get [flags] ### Options ``` - -h, --help Displays the options for the 'auth tokens get' command. + -h, --help Displays the options for the 'auth tokens get' command. + -i, --id string An optional flag that is required to return the access tokens of the currently logged in user.The input must be a string ``` ### Options inherited from parent commands diff --git a/pkg/auth/authTokensGet.go b/pkg/auth/authTokensGet.go index bf23cbdd..58606b63 100644 --- a/pkg/auth/authTokensGet.go +++ b/pkg/auth/authTokensGet.go @@ -8,6 +8,7 @@ package auth import ( "context" "log" + "strings" galasaErrors "github.com/galasa-dev/cli/pkg/errors" "github.com/galasa-dev/cli/pkg/galasaapi" @@ -37,6 +38,48 @@ func GetTokens( return err } +func GetTokensByLoginId( + apiClient *galasaapi.APIClient, + console spi.Console, + loginId string, +) error { + + authTokens, err := getAuthTokensByLoginIdFromRestApi(apiClient, loginId) + + if err == nil { + summaryFormatter := tokensformatter.NewTokenSummaryFormatter() + + var outputText string + outputText, err = summaryFormatter.FormatTokens(authTokens) + + if err == nil { + console.WriteString(outputText) + } + } + + return err +} + +func getAuthTokensByLoginIdFromRestApi(apiClient *galasaapi.APIClient, loginId string) ([]galasaapi.AuthToken, error) { + var context context.Context = nil + var authTokens []galasaapi.AuthToken + + validateLoginIdFlag(loginId) + + tokens, resp, err := apiClient.AuthenticationAPIApi.GetTokens(context).LoginId(loginId).Execute() + + if err != nil { + log.Println("getAuthTokensFromRestApi - Failed to retrieve list of tokens from API server") + err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_RETRIEVING_TOKEN_LIST_FROM_API_SERVER, err.Error()) + } else { + defer resp.Body.Close() + authTokens = tokens.GetTokens() + log.Printf("getAuthTokensFromRestApi - %v tokens collected", len(authTokens)) + } + + return authTokens, err +} + func getAuthTokensFromRestApi(apiClient *galasaapi.APIClient) ([]galasaapi.AuthToken, error) { var context context.Context = nil var authTokens []galasaapi.AuthToken @@ -54,3 +97,16 @@ func getAuthTokensFromRestApi(apiClient *galasaapi.APIClient) ([]galasaapi.AuthT return authTokens, err } + +func validateLoginIdFlag(loginId string) (string, error) { + + var err error + + loginId = strings.TrimSpace(loginId) + + if loginId == "" { + err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_MISSING_USER_LOGIN_ID_FLAG) + } + + return loginId, err +} diff --git a/pkg/cmd/authTokens.go b/pkg/cmd/authTokens.go index 3363ff8a..0174b9b7 100644 --- a/pkg/cmd/authTokens.go +++ b/pkg/cmd/authTokens.go @@ -16,6 +16,7 @@ import ( type AuthTokensCmdValues struct { bootstrap string + name string } type AuthTokensCommand struct { @@ -76,3 +77,21 @@ func (cmd *AuthTokensCommand) createAuthTokensCobraCmd( return authTokensCmd, err } + +func addLoginIdFlagToAuthTokensGet(cmd *cobra.Command, isMandatory bool, authTokensGetCmdValues *AuthTokensCmdValues) { + + flagName := "id" + var description string + if isMandatory { + description = "A mandatory flag that is required to return the access tokens of the currently logged in user." + } else { + description = "An optional flag that is required to return the access tokens of the currently logged in user." + } + description += "The input must be a string" + + cmd.PersistentFlags().StringVarP(&authTokensGetCmdValues.name, flagName, "i", "", description) + + if isMandatory { + cmd.MarkPersistentFlagRequired(flagName) + } +} diff --git a/pkg/cmd/authTokensGet.go b/pkg/cmd/authTokensGet.go index 49b583b7..8f83c6b4 100644 --- a/pkg/cmd/authTokensGet.go +++ b/pkg/cmd/authTokensGet.go @@ -74,6 +74,7 @@ func (cmd *AuthTokensGetCommand) createCobraCmd( var err error + authTokensGetCommandValues := authTokensCommand.Values().(*AuthTokensCmdValues) authGetTokensCobraCmd := &cobra.Command{ Use: "get", Short: "Get a list of authentication tokens", @@ -84,6 +85,7 @@ func (cmd *AuthTokensGetCommand) createCobraCmd( }, } + addLoginIdFlagToAuthTokensGet(authGetTokensCobraCmd, false, authTokensGetCommandValues) authTokensCommand.CobraCommand().AddCommand(authGetTokensCobraCmd) return authGetTokensCobraCmd, err @@ -134,7 +136,12 @@ func (cmd *AuthTokensGetCommand) executeAuthTokensGet( if err == nil { // Call to process the command in a unit-testable way. - err = auth.GetTokens(apiClient, console) + // Checking if the loginId param was passed + if cmd.cobraCommand.Flags().Changed("id") { + err = auth.GetTokensByLoginId(apiClient, console, authTokenCmdValues.name) + } else { + err = auth.GetTokens(apiClient, console) + } } } } From 676dd212e244afc6953721fdbf7735dbe1f64467 Mon Sep 17 00:00:00 2001 From: Aashir Siddiqui Date: Wed, 18 Sep 2024 09:34:34 +0100 Subject: [PATCH 02/30] Implemented the changes requested Signed-off-by: Aashir Siddiqui --- docs/generated/galasactl_auth_tokens_get.md | 4 +- pkg/auth/authTokensGet.go | 69 +++++++--------- pkg/auth/authTokensGet_test.go | 92 ++++++++++++++++++++- pkg/cmd/authTokens.go | 20 +---- pkg/cmd/authTokensGet.go | 18 ++-- pkg/errors/errorMessage.go | 1 + 6 files changed, 131 insertions(+), 73 deletions(-) diff --git a/docs/generated/galasactl_auth_tokens_get.md b/docs/generated/galasactl_auth_tokens_get.md index 84c0c2b8..61803bc7 100644 --- a/docs/generated/galasactl_auth_tokens_get.md +++ b/docs/generated/galasactl_auth_tokens_get.md @@ -13,8 +13,8 @@ galasactl auth tokens get [flags] ### Options ``` - -h, --help Displays the options for the 'auth tokens get' command. - -i, --id string An optional flag that is required to return the access tokens of the currently logged in user.The input must be a string + -h, --help Displays the options for the 'auth tokens get' command. + --user string An optional flag that is used to retrieve the access tokens of the currently logged in user. The input must be a string. ``` ### Options inherited from parent commands diff --git a/pkg/auth/authTokensGet.go b/pkg/auth/authTokensGet.go index 58606b63..bae63fe2 100644 --- a/pkg/auth/authTokensGet.go +++ b/pkg/auth/authTokensGet.go @@ -20,53 +20,39 @@ import ( func GetTokens( apiClient *galasaapi.APIClient, console spi.Console, + loginId string, ) error { - authTokens, err := getAuthTokensFromRestApi(apiClient) + authTokens, err := getAuthTokensFromRestApi(apiClient, loginId) if err == nil { - summaryFormatter := tokensformatter.NewTokenSummaryFormatter() - - var outputText string - outputText, err = summaryFormatter.FormatTokens(authTokens) - - if err == nil { - console.WriteString(outputText) - } + err = formatFetchedTokensAndWriteToConsole(authTokens, console) } return err } -func GetTokensByLoginId( - apiClient *galasaapi.APIClient, - console spi.Console, - loginId string, -) error { +func getAuthTokensFromRestApi(apiClient *galasaapi.APIClient, loginId string) ([]galasaapi.AuthToken, error) { + var context context.Context = nil + var authTokens []galasaapi.AuthToken + var err error - authTokens, err := getAuthTokensByLoginIdFromRestApi(apiClient, loginId) + apiCall := apiClient.AuthenticationAPIApi.GetTokens(context) - if err == nil { - summaryFormatter := tokensformatter.NewTokenSummaryFormatter() + if loginId != "" { - var outputText string - outputText, err = summaryFormatter.FormatTokens(authTokens) + loginId, err = validateLoginIdFlag(loginId) if err == nil { - console.WriteString(outputText) + apiCall = apiCall.LoginId(loginId) } } - return err -} - -func getAuthTokensByLoginIdFromRestApi(apiClient *galasaapi.APIClient, loginId string) ([]galasaapi.AuthToken, error) { - var context context.Context = nil - var authTokens []galasaapi.AuthToken - - validateLoginIdFlag(loginId) + if err != nil { + return authTokens, err + } - tokens, resp, err := apiClient.AuthenticationAPIApi.GetTokens(context).LoginId(loginId).Execute() + tokens, resp, err := apiCall.Execute() if err != nil { log.Println("getAuthTokensFromRestApi - Failed to retrieve list of tokens from API server") @@ -80,22 +66,18 @@ func getAuthTokensByLoginIdFromRestApi(apiClient *galasaapi.APIClient, loginId s return authTokens, err } -func getAuthTokensFromRestApi(apiClient *galasaapi.APIClient) ([]galasaapi.AuthToken, error) { - var context context.Context = nil - var authTokens []galasaapi.AuthToken +func formatFetchedTokensAndWriteToConsole(authTokens []galasaapi.AuthToken, console spi.Console) error { - tokens, resp, err := apiClient.AuthenticationAPIApi.GetTokens(context).Execute() + summaryFormatter := tokensformatter.NewTokenSummaryFormatter() - if err != nil { - log.Println("getAuthTokensFromRestApi - Failed to retrieve list of tokens from API server") - err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_RETRIEVING_TOKEN_LIST_FROM_API_SERVER, err.Error()) - } else { - defer resp.Body.Close() - authTokens = tokens.GetTokens() - log.Printf("getAuthTokensFromRestApi - %v tokens collected", len(authTokens)) + outputText, err := summaryFormatter.FormatTokens(authTokens) + + if err == nil { + console.WriteString(outputText) } - return authTokens, err + return err + } func validateLoginIdFlag(loginId string) (string, error) { @@ -103,10 +85,15 @@ func validateLoginIdFlag(loginId string) (string, error) { var err error loginId = strings.TrimSpace(loginId) + splits := strings.Split(loginId, " ") if loginId == "" { err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_MISSING_USER_LOGIN_ID_FLAG) } + if len(splits) > 1 { + err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_INVALID_LOGIN_ID, loginId) + } + return loginId, err } diff --git a/pkg/auth/authTokensGet_test.go b/pkg/auth/authTokensGet_test.go index f9813f56..67ac4205 100644 --- a/pkg/auth/authTokensGet_test.go +++ b/pkg/auth/authTokensGet_test.go @@ -61,6 +61,33 @@ func mockAuthTokensServlet(t *testing.T, writer http.ResponseWriter, request *ht body = `{ "tokens":[] }` + } else if state == "missingLoginIdFlag" { + statusCode = 400 + body = `{"error_code": 1155,"error_message": "GAL1155E: The id provided by the --id field cannot be an empty string."}` + } else if state == "invalidLoginIdFlag" { + statusCode = 400 + body = `{"error_code": 1157,"error_message": "GAL1157E: '%s' is not supported as a valid value. Valid value should not contain spaces. A value of 'admin' is valid but 'galasa admin' is not."}` + } else if state == "populatedByLoginId" { + body = `{ + "tokens":[ + { + "token_id":"098234980123-1283182389", + "creation_time":"2023-12-03T18:25:43.511Z", + "owner": { + "login_id":"mcobbett" + }, + "description":"So I can access ecosystem1 from my laptop." + }, + { + "token_id":"8218971d287s1-dhj32er2323", + "creation_time":"2024-03-03T09:36:50.511Z", + "owner": { + "login_id":"mcobbett" + }, + "description":"Automated build of example repo can change CPS properties" + } + ] + }` } else { statusCode = 500 body = `{"error_code": 5000,"error_message": "GAL5000E: Error occured when trying to access the endpoint. Report the problem to your Galasa Ecosystem owner."}` @@ -86,7 +113,7 @@ Total:3 ` //When - err := GetTokens(apiClient, console) + err := GetTokens(apiClient, console, "") //Then assert.Nil(t, err) @@ -104,7 +131,7 @@ func TestNoTokensPathReturnsOk(t *testing.T) { expectedOutput := "Total:0\n" //When - err := GetTokens(apiClient, console) + err := GetTokens(apiClient, console, "") //Then assert.Nil(t, err) @@ -121,10 +148,69 @@ func TestInvalidPathReturnsError(t *testing.T) { console := utils.NewMockConsole() //When - err := GetTokens(apiClient, console) + err := GetTokens(apiClient, console, "") //Then assert.NotNil(t, err) assert.Contains(t, err.Error(), "GAL1146E") assert.Contains(t, err.Error(), "Could not get list of tokens from API server") } + +func TestMissingLoginIdFlagReturnsBadRequest(t *testing.T) { + //Given... + serverState := "missingLoginId" + server := NewAuthTokensServletMock(t, serverState) + apiClient := api.InitialiseAPI(server.URL) + defer server.Close() + + console := utils.NewMockConsole() + expectedOutput := `GAL1155E: The id provided by the --id field cannot be an empty string.` + + //When + err := GetTokens(apiClient, console, " ") + + //Then + assert.NotNil(t, err) + assert.Equal(t, expectedOutput, err.Error()) +} + +func TestLoginIdWithSpacesReturnsBadRequest(t *testing.T) { + //Given... + serverState := "invalidLoginIdFlag" + server := NewAuthTokensServletMock(t, serverState) + apiClient := api.InitialiseAPI(server.URL) + defer server.Close() + + console := utils.NewMockConsole() + expectedOutput := `GAL1157E: 'galasa admin' is not supported as a valid value. Valid value should not contain spaces. A value of 'admin' is valid but 'galasa admin' is not.` + + //When + err := GetTokens(apiClient, console, "galasa admin") + + //Then + assert.NotNil(t, err) + assert.Equal(t, expectedOutput, err.Error()) +} + +func TestGetTokensByLoginIdReturnsOK(t *testing.T) { + //Given... + serverState := "populatedByLoginId" + server := NewAuthTokensServletMock(t, serverState) + apiClient := api.InitialiseAPI(server.URL) + defer server.Close() + + console := utils.NewMockConsole() + expectedOutput := `tokenid created(YYYY-MM-DD) user description +098234980123-1283182389 2023-12-03 mcobbett So I can access ecosystem1 from my laptop. +8218971d287s1-dhj32er2323 2024-03-03 mcobbett Automated build of example repo can change CPS properties + +Total:2 +` + + //When + err := GetTokens(apiClient, console, "mcobbett") + + //Then + assert.Nil(t, err) + assert.Equal(t, expectedOutput, console.ReadText()) +} diff --git a/pkg/cmd/authTokens.go b/pkg/cmd/authTokens.go index 0174b9b7..e8d03c57 100644 --- a/pkg/cmd/authTokens.go +++ b/pkg/cmd/authTokens.go @@ -16,7 +16,7 @@ import ( type AuthTokensCmdValues struct { bootstrap string - name string + loginId string } type AuthTokensCommand struct { @@ -77,21 +77,3 @@ func (cmd *AuthTokensCommand) createAuthTokensCobraCmd( return authTokensCmd, err } - -func addLoginIdFlagToAuthTokensGet(cmd *cobra.Command, isMandatory bool, authTokensGetCmdValues *AuthTokensCmdValues) { - - flagName := "id" - var description string - if isMandatory { - description = "A mandatory flag that is required to return the access tokens of the currently logged in user." - } else { - description = "An optional flag that is required to return the access tokens of the currently logged in user." - } - description += "The input must be a string" - - cmd.PersistentFlags().StringVarP(&authTokensGetCmdValues.name, flagName, "i", "", description) - - if isMandatory { - cmd.MarkPersistentFlagRequired(flagName) - } -} diff --git a/pkg/cmd/authTokensGet.go b/pkg/cmd/authTokensGet.go index 8f83c6b4..3541dc7e 100644 --- a/pkg/cmd/authTokensGet.go +++ b/pkg/cmd/authTokensGet.go @@ -85,7 +85,7 @@ func (cmd *AuthTokensGetCommand) createCobraCmd( }, } - addLoginIdFlagToAuthTokensGet(authGetTokensCobraCmd, false, authTokensGetCommandValues) + addLoginIdFlagToAuthTokensGet(authGetTokensCobraCmd, authTokensGetCommandValues) authTokensCommand.CobraCommand().AddCommand(authGetTokensCobraCmd) return authGetTokensCobraCmd, err @@ -135,13 +135,7 @@ func (cmd *AuthTokensGetCommand) executeAuthTokensGet( apiClient, err = authenticator.GetAuthenticatedAPIClient() if err == nil { - // Call to process the command in a unit-testable way. - // Checking if the loginId param was passed - if cmd.cobraCommand.Flags().Changed("id") { - err = auth.GetTokensByLoginId(apiClient, console, authTokenCmdValues.name) - } else { - err = auth.GetTokens(apiClient, console) - } + err = auth.GetTokens(apiClient, console, authTokenCmdValues.loginId) } } } @@ -149,3 +143,11 @@ func (cmd *AuthTokensGetCommand) executeAuthTokensGet( return err } + +func addLoginIdFlagToAuthTokensGet(cmd *cobra.Command, authTokensGetCmdValues *AuthTokensCmdValues) { + + flagName := "user" + var description string = "An optional flag that is used to retrieve the access tokens of the currently logged in user. The input must be a string." + + cmd.Flags().StringVar(&authTokensGetCmdValues.loginId, flagName, "", description) +} diff --git a/pkg/errors/errorMessage.go b/pkg/errors/errorMessage.go index 517fb77c..3d551704 100644 --- a/pkg/errors/errorMessage.go +++ b/pkg/errors/errorMessage.go @@ -249,6 +249,7 @@ var ( GALASA_ERROR_INVALID_TOKEN_ID_FORMAT = NewMessageType("GAL1154E: The provided token ID, '%s', does not match formatting requirements. The token ID can contain any character in the 'a'-'z', 'A'-'Z', '0'-'9', '-' (dash), or '_' (underscore) ranges only.", 1154, STACK_TRACE_NOT_WANTED) GALASA_ERROR_MISSING_USER_LOGIN_ID_FLAG = NewMessageType("GAL1155E: The id provided by the --id field cannot be an empty string.", 1155, STACK_TRACE_NOT_WANTED) GALASA_ERROR_LOGIN_ID_NOT_SUPPORTED = NewMessageType("GAL1156E: '%s' is not supported as a valid value. Valid values are 'me'.", 1156, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_INVALID_LOGIN_ID = NewMessageType("GAL1157E: '%s' is not supported as a valid value. Valid value should not contain spaces. A value of 'admin' is valid but 'galasa admin' is not.", 1157, STACK_TRACE_NOT_WANTED) // Warnings... GALASA_WARNING_MAVEN_NO_GALASA_OBR_REPO = NewMessageType("GAL2000W: Warning: Maven configuration file settings.xml should contain a reference to a Galasa repository so that the galasa OBR can be resolved. The official release repository is '%s', and 'pre-release' repository is '%s'", 2000, STACK_TRACE_WANTED) From edd875383a6d40736a229ba6a89a8464bece7bb4 Mon Sep 17 00:00:00 2001 From: Aashir Siddiqui Date: Wed, 18 Sep 2024 09:38:49 +0100 Subject: [PATCH 03/30] Added error msg --- pkg/errors/errorMessage.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/errors/errorMessage.go b/pkg/errors/errorMessage.go index 1557763a..4a313290 100644 --- a/pkg/errors/errorMessage.go +++ b/pkg/errors/errorMessage.go @@ -249,6 +249,7 @@ var ( GALASA_ERROR_INVALID_TOKEN_ID_FORMAT = NewMessageType("GAL1154E: The provided token ID, '%s', does not match formatting requirements. The token ID can contain any character in the 'a'-'z', 'A'-'Z', '0'-'9', '-' (dash), or '_' (underscore) ranges only.", 1154, STACK_TRACE_NOT_WANTED) GALASA_ERROR_MISSING_USER_LOGIN_ID_FLAG = NewMessageType("GAL1155E: The id provided by the --id field cannot be an empty string.", 1155, STACK_TRACE_NOT_WANTED) GALASA_ERROR_LOGIN_ID_NOT_SUPPORTED = NewMessageType("GAL1156E: '%s' is not supported as a valid value. Valid values are 'me'.", 1156, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_INVALID_LOGIN_ID = NewMessageType("GAL1157E: '%s' is not supported as a valid value. Valid value should not contain spaces. A value of 'admin' is valid but 'galasa admin' is not.", 1157, STACK_TRACE_NOT_WANTED) GALASA_ERROR_DELETE_RUN_FAILED = NewMessageType("GAL1157E: An attempt to delete a run named '%s' failed. Cause is %s", 1157, STACK_TRACE_NOT_WANTED) GALASA_ERROR_SERVER_DELETE_RUNS_FAILED = NewMessageType("GAL1158E: An attempt to delete a run named '%s' failed. Sending the delete request to the Galasa service failed. Cause is %v", 1158, STACK_TRACE_NOT_WANTED) From 89bbd8546665c9af136b87d553268e73aa0ce128 Mon Sep 17 00:00:00 2001 From: Aashir Siddiqui Date: Fri, 13 Sep 2024 15:48:19 +0100 Subject: [PATCH 04/30] CLI now supports tokens filtered by loginId Signed-off-by: Aashir Siddiqui --- docs/generated/galasactl_auth_tokens_get.md | 3 +- pkg/auth/authTokensGet.go | 56 +++++++++++++++++++++ pkg/cmd/authTokens.go | 19 +++++++ pkg/cmd/authTokensGet.go | 9 +++- 4 files changed, 85 insertions(+), 2 deletions(-) diff --git a/docs/generated/galasactl_auth_tokens_get.md b/docs/generated/galasactl_auth_tokens_get.md index 20bfe461..84c0c2b8 100644 --- a/docs/generated/galasactl_auth_tokens_get.md +++ b/docs/generated/galasactl_auth_tokens_get.md @@ -13,7 +13,8 @@ galasactl auth tokens get [flags] ### Options ``` - -h, --help Displays the options for the 'auth tokens get' command. + -h, --help Displays the options for the 'auth tokens get' command. + -i, --id string An optional flag that is required to return the access tokens of the currently logged in user.The input must be a string ``` ### Options inherited from parent commands diff --git a/pkg/auth/authTokensGet.go b/pkg/auth/authTokensGet.go index bf23cbdd..58606b63 100644 --- a/pkg/auth/authTokensGet.go +++ b/pkg/auth/authTokensGet.go @@ -8,6 +8,7 @@ package auth import ( "context" "log" + "strings" galasaErrors "github.com/galasa-dev/cli/pkg/errors" "github.com/galasa-dev/cli/pkg/galasaapi" @@ -37,6 +38,48 @@ func GetTokens( return err } +func GetTokensByLoginId( + apiClient *galasaapi.APIClient, + console spi.Console, + loginId string, +) error { + + authTokens, err := getAuthTokensByLoginIdFromRestApi(apiClient, loginId) + + if err == nil { + summaryFormatter := tokensformatter.NewTokenSummaryFormatter() + + var outputText string + outputText, err = summaryFormatter.FormatTokens(authTokens) + + if err == nil { + console.WriteString(outputText) + } + } + + return err +} + +func getAuthTokensByLoginIdFromRestApi(apiClient *galasaapi.APIClient, loginId string) ([]galasaapi.AuthToken, error) { + var context context.Context = nil + var authTokens []galasaapi.AuthToken + + validateLoginIdFlag(loginId) + + tokens, resp, err := apiClient.AuthenticationAPIApi.GetTokens(context).LoginId(loginId).Execute() + + if err != nil { + log.Println("getAuthTokensFromRestApi - Failed to retrieve list of tokens from API server") + err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_RETRIEVING_TOKEN_LIST_FROM_API_SERVER, err.Error()) + } else { + defer resp.Body.Close() + authTokens = tokens.GetTokens() + log.Printf("getAuthTokensFromRestApi - %v tokens collected", len(authTokens)) + } + + return authTokens, err +} + func getAuthTokensFromRestApi(apiClient *galasaapi.APIClient) ([]galasaapi.AuthToken, error) { var context context.Context = nil var authTokens []galasaapi.AuthToken @@ -54,3 +97,16 @@ func getAuthTokensFromRestApi(apiClient *galasaapi.APIClient) ([]galasaapi.AuthT return authTokens, err } + +func validateLoginIdFlag(loginId string) (string, error) { + + var err error + + loginId = strings.TrimSpace(loginId) + + if loginId == "" { + err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_MISSING_USER_LOGIN_ID_FLAG) + } + + return loginId, err +} diff --git a/pkg/cmd/authTokens.go b/pkg/cmd/authTokens.go index 3363ff8a..0174b9b7 100644 --- a/pkg/cmd/authTokens.go +++ b/pkg/cmd/authTokens.go @@ -16,6 +16,7 @@ import ( type AuthTokensCmdValues struct { bootstrap string + name string } type AuthTokensCommand struct { @@ -76,3 +77,21 @@ func (cmd *AuthTokensCommand) createAuthTokensCobraCmd( return authTokensCmd, err } + +func addLoginIdFlagToAuthTokensGet(cmd *cobra.Command, isMandatory bool, authTokensGetCmdValues *AuthTokensCmdValues) { + + flagName := "id" + var description string + if isMandatory { + description = "A mandatory flag that is required to return the access tokens of the currently logged in user." + } else { + description = "An optional flag that is required to return the access tokens of the currently logged in user." + } + description += "The input must be a string" + + cmd.PersistentFlags().StringVarP(&authTokensGetCmdValues.name, flagName, "i", "", description) + + if isMandatory { + cmd.MarkPersistentFlagRequired(flagName) + } +} diff --git a/pkg/cmd/authTokensGet.go b/pkg/cmd/authTokensGet.go index 49b583b7..8f83c6b4 100644 --- a/pkg/cmd/authTokensGet.go +++ b/pkg/cmd/authTokensGet.go @@ -74,6 +74,7 @@ func (cmd *AuthTokensGetCommand) createCobraCmd( var err error + authTokensGetCommandValues := authTokensCommand.Values().(*AuthTokensCmdValues) authGetTokensCobraCmd := &cobra.Command{ Use: "get", Short: "Get a list of authentication tokens", @@ -84,6 +85,7 @@ func (cmd *AuthTokensGetCommand) createCobraCmd( }, } + addLoginIdFlagToAuthTokensGet(authGetTokensCobraCmd, false, authTokensGetCommandValues) authTokensCommand.CobraCommand().AddCommand(authGetTokensCobraCmd) return authGetTokensCobraCmd, err @@ -134,7 +136,12 @@ func (cmd *AuthTokensGetCommand) executeAuthTokensGet( if err == nil { // Call to process the command in a unit-testable way. - err = auth.GetTokens(apiClient, console) + // Checking if the loginId param was passed + if cmd.cobraCommand.Flags().Changed("id") { + err = auth.GetTokensByLoginId(apiClient, console, authTokenCmdValues.name) + } else { + err = auth.GetTokens(apiClient, console) + } } } } From 967d8c87b427387bc4dad1dc24bc8b31d3c279b7 Mon Sep 17 00:00:00 2001 From: Aashir Siddiqui Date: Wed, 18 Sep 2024 09:34:34 +0100 Subject: [PATCH 05/30] Implemented the changes requested Signed-off-by: Aashir Siddiqui --- docs/generated/galasactl_auth_tokens_get.md | 4 +- pkg/auth/authTokensGet.go | 69 +++++++--------- pkg/auth/authTokensGet_test.go | 92 ++++++++++++++++++++- pkg/cmd/authTokens.go | 20 +---- pkg/cmd/authTokensGet.go | 18 ++-- pkg/errors/errorMessage.go | 1 + 6 files changed, 131 insertions(+), 73 deletions(-) diff --git a/docs/generated/galasactl_auth_tokens_get.md b/docs/generated/galasactl_auth_tokens_get.md index 84c0c2b8..61803bc7 100644 --- a/docs/generated/galasactl_auth_tokens_get.md +++ b/docs/generated/galasactl_auth_tokens_get.md @@ -13,8 +13,8 @@ galasactl auth tokens get [flags] ### Options ``` - -h, --help Displays the options for the 'auth tokens get' command. - -i, --id string An optional flag that is required to return the access tokens of the currently logged in user.The input must be a string + -h, --help Displays the options for the 'auth tokens get' command. + --user string An optional flag that is used to retrieve the access tokens of the currently logged in user. The input must be a string. ``` ### Options inherited from parent commands diff --git a/pkg/auth/authTokensGet.go b/pkg/auth/authTokensGet.go index 58606b63..bae63fe2 100644 --- a/pkg/auth/authTokensGet.go +++ b/pkg/auth/authTokensGet.go @@ -20,53 +20,39 @@ import ( func GetTokens( apiClient *galasaapi.APIClient, console spi.Console, + loginId string, ) error { - authTokens, err := getAuthTokensFromRestApi(apiClient) + authTokens, err := getAuthTokensFromRestApi(apiClient, loginId) if err == nil { - summaryFormatter := tokensformatter.NewTokenSummaryFormatter() - - var outputText string - outputText, err = summaryFormatter.FormatTokens(authTokens) - - if err == nil { - console.WriteString(outputText) - } + err = formatFetchedTokensAndWriteToConsole(authTokens, console) } return err } -func GetTokensByLoginId( - apiClient *galasaapi.APIClient, - console spi.Console, - loginId string, -) error { +func getAuthTokensFromRestApi(apiClient *galasaapi.APIClient, loginId string) ([]galasaapi.AuthToken, error) { + var context context.Context = nil + var authTokens []galasaapi.AuthToken + var err error - authTokens, err := getAuthTokensByLoginIdFromRestApi(apiClient, loginId) + apiCall := apiClient.AuthenticationAPIApi.GetTokens(context) - if err == nil { - summaryFormatter := tokensformatter.NewTokenSummaryFormatter() + if loginId != "" { - var outputText string - outputText, err = summaryFormatter.FormatTokens(authTokens) + loginId, err = validateLoginIdFlag(loginId) if err == nil { - console.WriteString(outputText) + apiCall = apiCall.LoginId(loginId) } } - return err -} - -func getAuthTokensByLoginIdFromRestApi(apiClient *galasaapi.APIClient, loginId string) ([]galasaapi.AuthToken, error) { - var context context.Context = nil - var authTokens []galasaapi.AuthToken - - validateLoginIdFlag(loginId) + if err != nil { + return authTokens, err + } - tokens, resp, err := apiClient.AuthenticationAPIApi.GetTokens(context).LoginId(loginId).Execute() + tokens, resp, err := apiCall.Execute() if err != nil { log.Println("getAuthTokensFromRestApi - Failed to retrieve list of tokens from API server") @@ -80,22 +66,18 @@ func getAuthTokensByLoginIdFromRestApi(apiClient *galasaapi.APIClient, loginId s return authTokens, err } -func getAuthTokensFromRestApi(apiClient *galasaapi.APIClient) ([]galasaapi.AuthToken, error) { - var context context.Context = nil - var authTokens []galasaapi.AuthToken +func formatFetchedTokensAndWriteToConsole(authTokens []galasaapi.AuthToken, console spi.Console) error { - tokens, resp, err := apiClient.AuthenticationAPIApi.GetTokens(context).Execute() + summaryFormatter := tokensformatter.NewTokenSummaryFormatter() - if err != nil { - log.Println("getAuthTokensFromRestApi - Failed to retrieve list of tokens from API server") - err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_RETRIEVING_TOKEN_LIST_FROM_API_SERVER, err.Error()) - } else { - defer resp.Body.Close() - authTokens = tokens.GetTokens() - log.Printf("getAuthTokensFromRestApi - %v tokens collected", len(authTokens)) + outputText, err := summaryFormatter.FormatTokens(authTokens) + + if err == nil { + console.WriteString(outputText) } - return authTokens, err + return err + } func validateLoginIdFlag(loginId string) (string, error) { @@ -103,10 +85,15 @@ func validateLoginIdFlag(loginId string) (string, error) { var err error loginId = strings.TrimSpace(loginId) + splits := strings.Split(loginId, " ") if loginId == "" { err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_MISSING_USER_LOGIN_ID_FLAG) } + if len(splits) > 1 { + err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_INVALID_LOGIN_ID, loginId) + } + return loginId, err } diff --git a/pkg/auth/authTokensGet_test.go b/pkg/auth/authTokensGet_test.go index f9813f56..67ac4205 100644 --- a/pkg/auth/authTokensGet_test.go +++ b/pkg/auth/authTokensGet_test.go @@ -61,6 +61,33 @@ func mockAuthTokensServlet(t *testing.T, writer http.ResponseWriter, request *ht body = `{ "tokens":[] }` + } else if state == "missingLoginIdFlag" { + statusCode = 400 + body = `{"error_code": 1155,"error_message": "GAL1155E: The id provided by the --id field cannot be an empty string."}` + } else if state == "invalidLoginIdFlag" { + statusCode = 400 + body = `{"error_code": 1157,"error_message": "GAL1157E: '%s' is not supported as a valid value. Valid value should not contain spaces. A value of 'admin' is valid but 'galasa admin' is not."}` + } else if state == "populatedByLoginId" { + body = `{ + "tokens":[ + { + "token_id":"098234980123-1283182389", + "creation_time":"2023-12-03T18:25:43.511Z", + "owner": { + "login_id":"mcobbett" + }, + "description":"So I can access ecosystem1 from my laptop." + }, + { + "token_id":"8218971d287s1-dhj32er2323", + "creation_time":"2024-03-03T09:36:50.511Z", + "owner": { + "login_id":"mcobbett" + }, + "description":"Automated build of example repo can change CPS properties" + } + ] + }` } else { statusCode = 500 body = `{"error_code": 5000,"error_message": "GAL5000E: Error occured when trying to access the endpoint. Report the problem to your Galasa Ecosystem owner."}` @@ -86,7 +113,7 @@ Total:3 ` //When - err := GetTokens(apiClient, console) + err := GetTokens(apiClient, console, "") //Then assert.Nil(t, err) @@ -104,7 +131,7 @@ func TestNoTokensPathReturnsOk(t *testing.T) { expectedOutput := "Total:0\n" //When - err := GetTokens(apiClient, console) + err := GetTokens(apiClient, console, "") //Then assert.Nil(t, err) @@ -121,10 +148,69 @@ func TestInvalidPathReturnsError(t *testing.T) { console := utils.NewMockConsole() //When - err := GetTokens(apiClient, console) + err := GetTokens(apiClient, console, "") //Then assert.NotNil(t, err) assert.Contains(t, err.Error(), "GAL1146E") assert.Contains(t, err.Error(), "Could not get list of tokens from API server") } + +func TestMissingLoginIdFlagReturnsBadRequest(t *testing.T) { + //Given... + serverState := "missingLoginId" + server := NewAuthTokensServletMock(t, serverState) + apiClient := api.InitialiseAPI(server.URL) + defer server.Close() + + console := utils.NewMockConsole() + expectedOutput := `GAL1155E: The id provided by the --id field cannot be an empty string.` + + //When + err := GetTokens(apiClient, console, " ") + + //Then + assert.NotNil(t, err) + assert.Equal(t, expectedOutput, err.Error()) +} + +func TestLoginIdWithSpacesReturnsBadRequest(t *testing.T) { + //Given... + serverState := "invalidLoginIdFlag" + server := NewAuthTokensServletMock(t, serverState) + apiClient := api.InitialiseAPI(server.URL) + defer server.Close() + + console := utils.NewMockConsole() + expectedOutput := `GAL1157E: 'galasa admin' is not supported as a valid value. Valid value should not contain spaces. A value of 'admin' is valid but 'galasa admin' is not.` + + //When + err := GetTokens(apiClient, console, "galasa admin") + + //Then + assert.NotNil(t, err) + assert.Equal(t, expectedOutput, err.Error()) +} + +func TestGetTokensByLoginIdReturnsOK(t *testing.T) { + //Given... + serverState := "populatedByLoginId" + server := NewAuthTokensServletMock(t, serverState) + apiClient := api.InitialiseAPI(server.URL) + defer server.Close() + + console := utils.NewMockConsole() + expectedOutput := `tokenid created(YYYY-MM-DD) user description +098234980123-1283182389 2023-12-03 mcobbett So I can access ecosystem1 from my laptop. +8218971d287s1-dhj32er2323 2024-03-03 mcobbett Automated build of example repo can change CPS properties + +Total:2 +` + + //When + err := GetTokens(apiClient, console, "mcobbett") + + //Then + assert.Nil(t, err) + assert.Equal(t, expectedOutput, console.ReadText()) +} diff --git a/pkg/cmd/authTokens.go b/pkg/cmd/authTokens.go index 0174b9b7..e8d03c57 100644 --- a/pkg/cmd/authTokens.go +++ b/pkg/cmd/authTokens.go @@ -16,7 +16,7 @@ import ( type AuthTokensCmdValues struct { bootstrap string - name string + loginId string } type AuthTokensCommand struct { @@ -77,21 +77,3 @@ func (cmd *AuthTokensCommand) createAuthTokensCobraCmd( return authTokensCmd, err } - -func addLoginIdFlagToAuthTokensGet(cmd *cobra.Command, isMandatory bool, authTokensGetCmdValues *AuthTokensCmdValues) { - - flagName := "id" - var description string - if isMandatory { - description = "A mandatory flag that is required to return the access tokens of the currently logged in user." - } else { - description = "An optional flag that is required to return the access tokens of the currently logged in user." - } - description += "The input must be a string" - - cmd.PersistentFlags().StringVarP(&authTokensGetCmdValues.name, flagName, "i", "", description) - - if isMandatory { - cmd.MarkPersistentFlagRequired(flagName) - } -} diff --git a/pkg/cmd/authTokensGet.go b/pkg/cmd/authTokensGet.go index 8f83c6b4..3541dc7e 100644 --- a/pkg/cmd/authTokensGet.go +++ b/pkg/cmd/authTokensGet.go @@ -85,7 +85,7 @@ func (cmd *AuthTokensGetCommand) createCobraCmd( }, } - addLoginIdFlagToAuthTokensGet(authGetTokensCobraCmd, false, authTokensGetCommandValues) + addLoginIdFlagToAuthTokensGet(authGetTokensCobraCmd, authTokensGetCommandValues) authTokensCommand.CobraCommand().AddCommand(authGetTokensCobraCmd) return authGetTokensCobraCmd, err @@ -135,13 +135,7 @@ func (cmd *AuthTokensGetCommand) executeAuthTokensGet( apiClient, err = authenticator.GetAuthenticatedAPIClient() if err == nil { - // Call to process the command in a unit-testable way. - // Checking if the loginId param was passed - if cmd.cobraCommand.Flags().Changed("id") { - err = auth.GetTokensByLoginId(apiClient, console, authTokenCmdValues.name) - } else { - err = auth.GetTokens(apiClient, console) - } + err = auth.GetTokens(apiClient, console, authTokenCmdValues.loginId) } } } @@ -149,3 +143,11 @@ func (cmd *AuthTokensGetCommand) executeAuthTokensGet( return err } + +func addLoginIdFlagToAuthTokensGet(cmd *cobra.Command, authTokensGetCmdValues *AuthTokensCmdValues) { + + flagName := "user" + var description string = "An optional flag that is used to retrieve the access tokens of the currently logged in user. The input must be a string." + + cmd.Flags().StringVar(&authTokensGetCmdValues.loginId, flagName, "", description) +} diff --git a/pkg/errors/errorMessage.go b/pkg/errors/errorMessage.go index 517fb77c..3d551704 100644 --- a/pkg/errors/errorMessage.go +++ b/pkg/errors/errorMessage.go @@ -249,6 +249,7 @@ var ( GALASA_ERROR_INVALID_TOKEN_ID_FORMAT = NewMessageType("GAL1154E: The provided token ID, '%s', does not match formatting requirements. The token ID can contain any character in the 'a'-'z', 'A'-'Z', '0'-'9', '-' (dash), or '_' (underscore) ranges only.", 1154, STACK_TRACE_NOT_WANTED) GALASA_ERROR_MISSING_USER_LOGIN_ID_FLAG = NewMessageType("GAL1155E: The id provided by the --id field cannot be an empty string.", 1155, STACK_TRACE_NOT_WANTED) GALASA_ERROR_LOGIN_ID_NOT_SUPPORTED = NewMessageType("GAL1156E: '%s' is not supported as a valid value. Valid values are 'me'.", 1156, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_INVALID_LOGIN_ID = NewMessageType("GAL1157E: '%s' is not supported as a valid value. Valid value should not contain spaces. A value of 'admin' is valid but 'galasa admin' is not.", 1157, STACK_TRACE_NOT_WANTED) // Warnings... GALASA_WARNING_MAVEN_NO_GALASA_OBR_REPO = NewMessageType("GAL2000W: Warning: Maven configuration file settings.xml should contain a reference to a Galasa repository so that the galasa OBR can be resolved. The official release repository is '%s', and 'pre-release' repository is '%s'", 2000, STACK_TRACE_WANTED) From 364a9091802109ff8b207b4ee990beeafd7beeae Mon Sep 17 00:00:00 2001 From: Eamonn Mansour <47121388+eamansour@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:28:37 +0100 Subject: [PATCH 06/30] Update runs get to use cursor-based pagination (#273) * Use cursor-based pagination when getting runs from the API server Signed-off-by: Eamonn Mansour <47121388+eamansour@users.noreply.github.com> * Remove includeCursor query parameter Signed-off-by: Eamonn Mansour <47121388+eamansour@users.noreply.github.com> * Swap cursor condition order Signed-off-by: Eamonn Mansour <47121388+eamansour@users.noreply.github.com> * Turn on includeCursor parameter Signed-off-by: Eamonn Mansour <47121388+eamansour@users.noreply.github.com> * Remove log statement Signed-off-by: Eamonn Mansour <47121388+eamansour@users.noreply.github.com> * Use 'from:desc' as the default sort for runs get Signed-off-by: Eamonn Mansour <47121388+eamansour@users.noreply.github.com> --------- Signed-off-by: Eamonn Mansour <47121388+eamansour@users.noreply.github.com> Signed-off-by: Aashir Siddiqui --- pkg/runs/runsDownload_test.go | 6 - pkg/runs/runsGet.go | 12 +- pkg/runs/runsGet_test.go | 275 +++++++++++++++++++--------------- 3 files changed, 163 insertions(+), 130 deletions(-) diff --git a/pkg/runs/runsDownload_test.go b/pkg/runs/runsDownload_test.go index fd5123e4..06d48490 100644 --- a/pkg/runs/runsDownload_test.go +++ b/pkg/runs/runsDownload_test.go @@ -12,7 +12,6 @@ import ( "net/http" "net/http/httptest" "os" - "strconv" "strings" "testing" "time" @@ -237,10 +236,7 @@ func WriteMockRasRunsResponse( writer.Header().Set("Content-Type", "application/json") values := req.URL.Query() - pageRequestedStr := values.Get("page") runNameQueryParameter := values.Get("runname") - pageRequested, _ := strconv.Atoi(pageRequestedStr) - assert.Equal(t, pageRequested, 1) assert.Equal(t, runNameQueryParameter, runName) @@ -254,9 +250,7 @@ func WriteMockRasRunsResponse( writer.Write([]byte(fmt.Sprintf(` { - "pageNumber": 1, "pageSize": 1, - "numPages": 1, "amountOfRuns": %d, "runs":[ %s ] }`, len(runResultStrings), combinedRunResultStrings))) diff --git a/pkg/runs/runsGet.go b/pkg/runs/runsGet.go index 48fe614c..d6fcc745 100644 --- a/pkg/runs/runsGet.go +++ b/pkg/runs/runsGet.go @@ -222,6 +222,7 @@ func GetRunsFromRestApi( var pageNumberWanted int32 = 1 gotAllResults := false var restApiVersion string + var pageCursor string restApiVersion, err = embedded.GetGalasactlRestApiVersion() @@ -232,7 +233,7 @@ func GetRunsFromRestApi( var runData *galasaapi.RunResults var httpResponse *http.Response log.Printf("Requesting page '%d' ", pageNumberWanted) - apicall := apiClient.ResultArchiveStoreAPIApi.GetRasSearchRuns(context).ClientApiVersion(restApiVersion) + apicall := apiClient.ResultArchiveStoreAPIApi.GetRasSearchRuns(context).ClientApiVersion(restApiVersion).IncludeCursor("true") if fromAgeMins != 0 { apicall = apicall.From(fromTime) } @@ -251,8 +252,10 @@ func GetRunsFromRestApi( if shouldGetActive { apicall = apicall.Status(activeStatusNames) } - apicall = apicall.Page(pageNumberWanted) - apicall = apicall.Sort("to:desc") + if pageCursor != "" { + apicall = apicall.Cursor(pageCursor) + } + apicall = apicall.Sort("from:desc") runData, httpResponse, err = apicall.Execute() if err != nil { @@ -271,9 +274,10 @@ func GetRunsFromRestApi( results = append(results, runsOnThisPage...) // Have we processed the last page ? - if pageNumberWanted == runData.GetNumPages() { + if !runData.HasNextCursor() || len(runsOnThisPage) < int(runData.GetPageSize()) { gotAllResults = true } else { + pageCursor = runData.GetNextCursor() pageNumberWanted++ } } diff --git a/pkg/runs/runsGet_test.go b/pkg/runs/runsGet_test.go index 16b2728b..bc7b8638 100644 --- a/pkg/runs/runsGet_test.go +++ b/pkg/runs/runsGet_test.go @@ -10,7 +10,6 @@ import ( "net/http" "net/http/httptest" "net/url" - "strconv" "strings" "testing" @@ -84,9 +83,16 @@ const ( "contentType": "application/json" }] }` + + EMPTY_RUNS_RESPONSE = ` + { + "pageSize": 1, + "amountOfRuns": 0, + "runs":[] + }` ) -func NewRunsGetServletMock(t *testing.T, status int, runName string, runResultStrings ...string) *httptest.Server { +func NewRunsGetServletMock(t *testing.T, status int, nextPageCursors []string, pages map[string][]string, pageSize int, runName string, runResultStrings ...string) *httptest.Server { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { clientVersion := r.Header.Get("ClientApiVersion") @@ -96,7 +102,13 @@ func NewRunsGetServletMock(t *testing.T, status int, runName string, runResultSt } else if strings.Contains(r.URL.Path, "/ras/resultnames") { ConfigureServerForResultNamesEndpoint(t, w, r, status) } else { - ConfigureServerForRasRunsEndpoint(t, w, r, runName, status, runResultStrings...) + nextCursor := "" + if len(nextPageCursors) > 0 { + // Advance the expected page cursors by one + nextCursor = nextPageCursors[0] + nextPageCursors = nextPageCursors[1:] + } + ConfigureServerForRasRunsEndpoint(t, w, r, pages, nextCursor, runName, pageSize, status) } })) return server @@ -127,7 +139,16 @@ func ConfigureServerForDetailsEndpoint(t *testing.T, w http.ResponseWriter, r *h `, combinedRunResultStrings))) } -func ConfigureServerForRasRunsEndpoint(t *testing.T, w http.ResponseWriter, r *http.Request, runName string, status int, runResultStrings ...string) { +func ConfigureServerForRasRunsEndpoint( + t *testing.T, + w http.ResponseWriter, + r *http.Request, + pages map[string][]string, + nextPageCursor string, + runName string, + pageSize int, + status int, +) { if r.URL.Path != "/ras/runs" { t.Errorf("Expected to request '/ras/runs', got: %s", r.URL.Path) } @@ -138,14 +159,24 @@ func ConfigureServerForRasRunsEndpoint(t *testing.T, w http.ResponseWriter, r *h w.WriteHeader(status) values := r.URL.Query() - pageRequestedStr := values.Get("page") runNameQueryParameter := values.Get("runname") - pageRequested, _ := strconv.Atoi(pageRequestedStr) - assert.Equal(t, pageRequested, 1) + + var pageRunsJson []string + var keyExists bool + cursorQueryParameter := values.Get("cursor") + + // Keys of the pages map correspond to page cursors, including + // an empty string key for the first request to /ras/runs + pageRunsJson, keyExists = pages[cursorQueryParameter] + assert.True(t, keyExists) + + // Subsequent requests shouldn't be made to the same page, + // so delete the page since we've visited it + delete(pages, cursorQueryParameter) assert.Equal(t, runNameQueryParameter, runName) combinedRunResultStrings := "" - for index, runResult := range runResultStrings { + for index, runResult := range pageRunsJson { if index > 0 { combinedRunResultStrings += "," } @@ -154,12 +185,11 @@ func ConfigureServerForRasRunsEndpoint(t *testing.T, w http.ResponseWriter, r *h w.Write([]byte(fmt.Sprintf(` { - "pageNumber": 1, - "pageSize": 1, - "numPages": 1, + "nextCursor": "%s", + "pageSize": %d, "amountOfRuns": %d, "runs":[ %s ] - }`, len(runResultStrings), combinedRunResultStrings))) + }`, nextPageCursor, pageSize, len(pageRunsJson), combinedRunResultStrings))) } func ConfigureServerForResultNamesEndpoint(t *testing.T, w http.ResponseWriter, r *http.Request, status int) { @@ -208,12 +238,17 @@ func TestOutputFormatGarbageStringValidationGivesError(t *testing.T) { func TestRunsGetOfRunNameWhichExistsProducesExpectedSummary(t *testing.T) { // Given ... + pages := make(map[string][]string, 0) + pages[""] = []string{ RUN_U456 } + nextPageCursors := []string{ "" } + runName := "U456" age := "2d:24h" requestor := "" result := "" + pageSize := 100 - server := NewRunsGetServletMock(t, http.StatusOK, runName, RUN_U456) + server := NewRunsGetServletMock(t, http.StatusOK, nextPageCursors, pages, pageSize, runName) shouldGetActive := false defer server.Close() @@ -247,11 +282,15 @@ func TestRunsGetOfRunNameWhichDoesNotExistProducesError(t *testing.T) { // Given ... age := "2d:24h" runName := "garbage" + pages := make(map[string][]string, 0) + pages[""] = []string{ RUN_U456 } + nextPageCursors := []string{ "" } requestor := "" result := "" shouldGetActive := false + pageSize := 100 - server := NewRunsGetServletMock(t, http.StatusOK, runName) + server := NewRunsGetServletMock(t, http.StatusOK, nextPageCursors, pages, pageSize, runName) defer server.Close() mockConsole := utils.NewMockConsole() @@ -276,13 +315,17 @@ func TestRunsGetOfRunNameWhichDoesNotExistProducesError(t *testing.T) { func TestRunsGetWhereRunNameExistsTwiceProducesTwoRunResultLines(t *testing.T) { // Given ... + pages := make(map[string][]string, 0) + pages[""] = []string{ RUN_U456, RUN_U456_v2 } + nextPageCursors := []string{ "" } age := "" runName := "U456" requestor := "" result := "" shouldGetActive := false + pageSize := 100 - server := NewRunsGetServletMock(t, http.StatusOK, runName, RUN_U456, RUN_U456_v2) + server := NewRunsGetServletMock(t, http.StatusOK, nextPageCursors, pages, pageSize, runName) defer server.Close() mockConsole := utils.NewMockConsole() @@ -350,13 +393,17 @@ func TestOutputFormatDetailsValidatesOk(t *testing.T) { func TestRunsGetOfRunNameWhichExistsProducesExpectedDetails(t *testing.T) { // Given ... + pages := make(map[string][]string, 0) + pages[""] = []string{ RUN_U456 } + nextPageCursors := []string{ "" } age := "" runName := "U456" requestor := "" result := "" shouldGetActive := false + pageSize := 100 - server := NewRunsGetServletMock(t, http.StatusOK, runName, RUN_U456) + server := NewRunsGetServletMock(t, http.StatusOK, nextPageCursors, pages, pageSize, runName, RUN_U456) defer server.Close() outputFormat := "details" @@ -410,13 +457,17 @@ func TestGetFormatterNamesStringMultipleFormattersFormatsOk(t *testing.T) { func TestAPIInternalErrorIsHandledOk(t *testing.T) { // Given ... + pages := make(map[string][]string, 0) + pages[""] = []string{ RUN_U456 } + nextPageCursors := []string{ "" } age := "" runName := "U456" requestor := "" result := "" shouldGetActive := false + pageSize := 100 - server := NewRunsGetServletMock(t, http.StatusInternalServerError, runName, RUN_U456) + server := NewRunsGetServletMock(t, http.StatusInternalServerError, nextPageCursors, pages, pageSize, runName) defer server.Close() outputFormat := "details" @@ -439,13 +490,17 @@ func TestAPIInternalErrorIsHandledOk(t *testing.T) { func TestRunsGetOfRunNameWhichExistsProducesExpectedRaw(t *testing.T) { // Given ... + pages := make(map[string][]string, 0) + pages[""] = []string{ RUN_U456 } + nextPageCursors := []string{ "" } age := "" runName := "U456" requestor := "" result := "" shouldGetActive := false + pageSize := 100 - server := NewRunsGetServletMock(t, http.StatusOK, runName, RUN_U456) + server := NewRunsGetServletMock(t, http.StatusOK, nextPageCursors, pages, pageSize, runName) defer server.Close() outputFormat := "raw" @@ -579,14 +634,7 @@ func TestRunsGetURLQueryWithFromAndToDate(t *testing.T) { assert.NotEqualValues(t, query.Get("to"), "") w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) - w.Write([]byte(` - { - "pageNumber": 1, - "pageSize": 1, - "numPages": 1, - "amountOfRuns": 0, - "runs":[] - }`)) + w.Write([]byte(EMPTY_RUNS_RESPONSE)) })) defer server.Close() @@ -619,14 +667,7 @@ func TestRunsGetURLQueryJustFromAge(t *testing.T) { assert.EqualValues(t, query.Get("to"), "") w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) - w.Write([]byte(` - { - "pageNumber": 1, - "pageSize": 1, - "numPages": 1, - "amountOfRuns": 0, - "runs":[] - }`)) + w.Write([]byte(EMPTY_RUNS_RESPONSE)) })) defer server.Close() @@ -659,14 +700,7 @@ func TestRunsGetURLQueryWithNoRunNameAndNoFromAgeReturnsError(t *testing.T) { assert.EqualValues(t, query.Get("runname"), "") w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) - w.Write([]byte(` - { - "pageNumber": 1, - "pageSize": 1, - "numPages": 1, - "amountOfRuns": 0, - "runs":[] - }`)) + w.Write([]byte(EMPTY_RUNS_RESPONSE)) })) defer server.Close() @@ -700,14 +734,7 @@ func TestRunsGetURLQueryWithOlderToAgeThanFromAgeReturnsError(t *testing.T) { assert.EqualValues(t, query.Get("runname"), "U456") w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) - w.Write([]byte(` - { - "pageNumber": 1, - "pageSize": 1, - "numPages": 1, - "amountOfRuns": 0, - "runs":[] - }`)) + w.Write([]byte(EMPTY_RUNS_RESPONSE)) })) defer server.Close() @@ -741,14 +768,7 @@ func TestRunsGetURLQueryWithBadlyFormedFromAndToParameterReturnsError(t *testing assert.EqualValues(t, query.Get("runname"), "U456") w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) - w.Write([]byte(` - { - "pageNumber": 1, - "pageSize": 1, - "numPages": 1, - "amountOfRuns": 0, - "runs":[] - }`)) + w.Write([]byte(EMPTY_RUNS_RESPONSE)) })) defer server.Close() @@ -937,14 +957,7 @@ func TestRunsGetURLQueryWithRequestorNotSuppliedReturnsOK(t *testing.T) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) - w.Write([]byte(` - { - "pageNumber": 1, - "pageSize": 1, - "numPages": 1, - "amountOfRuns": 0, - "runs":[] - }`)) + w.Write([]byte(EMPTY_RUNS_RESPONSE)) })) defer server.Close() @@ -979,14 +992,7 @@ func TestRunsGetURLQueryWithRequestorSuppliedReturnsOK(t *testing.T) { assert.EqualValues(t, query.Get("requestor"), requestor) w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) - w.Write([]byte(` - { - "pageNumber": 1, - "pageSize": 1, - "numPages": 1, - "amountOfRuns": 0, - "runs":[] - }`)) + w.Write([]byte(EMPTY_RUNS_RESPONSE)) })) defer server.Close() @@ -1021,14 +1027,7 @@ func TestRunsGetURLQueryWithNumericRequestorSuppliedReturnsOK(t *testing.T) { assert.Contains(t, r.URL.RawQuery, "requestor="+url.QueryEscape(requestor)) w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) - w.Write([]byte(` - { - "pageNumber": 1, - "pageSize": 1, - "numPages": 1, - "amountOfRuns": 0, - "runs":[] - }`)) + w.Write([]byte(EMPTY_RUNS_RESPONSE)) })) defer server.Close() @@ -1063,14 +1062,7 @@ func TestRunsGetURLQueryWithDashInRequestorSuppliedReturnsOK(t *testing.T) { assert.Contains(t, r.URL.RawQuery, "requestor="+url.QueryEscape(requestor)) w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) - w.Write([]byte(` - { - "pageNumber": 1, - "pageSize": 1, - "numPages": 1, - "amountOfRuns": 0, - "runs":[] - }`)) + w.Write([]byte(EMPTY_RUNS_RESPONSE)) })) defer server.Close() @@ -1105,14 +1097,7 @@ func TestRunsGetURLQueryWithAmpersandRequestorSuppliedReturnsOK(t *testing.T) { assert.Contains(t, r.URL.RawQuery, "requestor="+url.QueryEscape(requestor)) w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) - w.Write([]byte(` - { - "pageNumber": 1, - "pageSize": 1, - "numPages": 1, - "amountOfRuns": 0, - "runs":[] - }`)) + w.Write([]byte(EMPTY_RUNS_RESPONSE)) })) defer server.Close() @@ -1148,14 +1133,7 @@ func TestRunsGetURLQueryWithSpecialCharactersRequestorSuppliedReturnsOK(t *testi w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) - w.Write([]byte(` - { - "pageNumber": 1, - "pageSize": 1, - "numPages": 1, - "amountOfRuns": 0, - "runs":[] - }`)) + w.Write([]byte(EMPTY_RUNS_RESPONSE)) })) defer server.Close() @@ -1175,13 +1153,17 @@ func TestRunsGetURLQueryWithSpecialCharactersRequestorSuppliedReturnsOK(t *testi func TestRunsGetURLQueryWithResultSuppliedReturnsOK(t *testing.T) { // Given ... + pages := make(map[string][]string, 0) + pages[""] = []string{ RUN_U456 } + nextPageCursors := []string{ "" } age := "" runName := "U456" requestor := "" result := "Passed" shouldGetActive := false + pageSize := 100 - server := NewRunsGetServletMock(t, http.StatusOK, runName, RUN_U456) + server := NewRunsGetServletMock(t, http.StatusOK, nextPageCursors, pages, pageSize, runName) defer server.Close() outputFormat := "summary" @@ -1202,13 +1184,17 @@ func TestRunsGetURLQueryWithResultSuppliedReturnsOK(t *testing.T) { func TestRunsGetURLQueryWithMultipleResultSuppliedReturnsOK(t *testing.T) { // Given ... + pages := make(map[string][]string, 0) + pages[""] = []string{ RUN_U456 } + nextPageCursors := []string{ "" } age := "" runName := "U456" requestor := "" result := "Passed,envfail" shouldGetActive := false + pageSize := 100 - server := NewRunsGetServletMock(t, http.StatusOK, runName, RUN_U456) + server := NewRunsGetServletMock(t, http.StatusOK, nextPageCursors, pages, pageSize, runName) defer server.Close() outputFormat := "summary" @@ -1230,13 +1216,17 @@ func TestRunsGetURLQueryWithMultipleResultSuppliedReturnsOK(t *testing.T) { func TestRunsGetURLQueryWithResultNotSuppliedReturnsOK(t *testing.T) { // Given ... + pages := make(map[string][]string, 0) + pages[""] = []string{ RUN_U456 } + nextPageCursors := []string{ "" } age := "" runName := "U456" requestor := "" result := "" shouldGetActive := false + pageSize := 100 - server := NewRunsGetServletMock(t, http.StatusOK, runName, RUN_U456) + server := NewRunsGetServletMock(t, http.StatusOK, nextPageCursors, pages, pageSize, runName) defer server.Close() outputFormat := "summary" @@ -1255,13 +1245,17 @@ func TestRunsGetURLQueryWithResultNotSuppliedReturnsOK(t *testing.T) { func TestRunsGetURLQueryWithInvalidResultSuppliedReturnsError(t *testing.T) { // Given ... + pages := make(map[string][]string, 0) + pages[""] = []string{ RUN_U456 } + nextPageCursors := []string{ "" } age := "" runName := "U456" requestor := "" result := "garbage" shouldGetActive := false + pageSize := 100 - server := NewRunsGetServletMock(t, http.StatusOK, runName, RUN_U456) + server := NewRunsGetServletMock(t, http.StatusOK, nextPageCursors, pages, pageSize, runName) defer server.Close() outputFormat := "summary" @@ -1282,13 +1276,17 @@ func TestRunsGetURLQueryWithInvalidResultSuppliedReturnsError(t *testing.T) { func TestActiveAndResultAreMutuallyExclusiveShouldReturnError(t *testing.T) { // Given ... + pages := make(map[string][]string, 0) + pages[""] = []string{ RUN_U456 } + nextPageCursors := []string{ "" } age := "" runName := "U456" requestor := "" result := "Passed" shouldGetActive := true + pageSize := 100 - server := NewRunsGetServletMock(t, http.StatusOK, runName, RUN_U456) + server := NewRunsGetServletMock(t, http.StatusOK, nextPageCursors, pages, pageSize, runName) defer server.Close() outputFormat := "summary" @@ -1308,13 +1306,17 @@ func TestActiveAndResultAreMutuallyExclusiveShouldReturnError(t *testing.T) { func TestActiveParameterReturnsOk(t *testing.T) { // Given ... + pages := make(map[string][]string, 0) + pages[""] = []string{ RUN_U456 } + nextPageCursors := []string{ "" } age := "" runName := "U456" requestor := "" result := "" shouldGetActive := true + pageSize := 100 - server := NewRunsGetServletMock(t, http.StatusOK, runName, RUN_U456) + server := NewRunsGetServletMock(t, http.StatusOK, nextPageCursors, pages, pageSize, runName) defer server.Close() outputFormat := "summary" @@ -1351,14 +1353,7 @@ func TestRunsGetActiveRunsBuildsQueryCorrectly(t *testing.T) { assert.NotContains(t, r.URL.RawQuery, "status="+url.QueryEscape("finished")) w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) - w.Write([]byte(` - { - "pageNumber": 1, - "pageSize": 1, - "numPages": 1, - "amountOfRuns": 0, - "runs":[] - }`)) + w.Write([]byte(EMPTY_RUNS_RESPONSE)) })) defer server.Close() @@ -1375,3 +1370,43 @@ func TestRunsGetActiveRunsBuildsQueryCorrectly(t *testing.T) { // Then ... assert.Nil(t, err) } + + +func TestRunsGetWithNextCursorGetsNextPageOfRuns(t *testing.T) { + + // Given ... + pages := make(map[string][]string, 0) + pages[""] = []string{ RUN_U456 } + pages["page2"] = []string{ RUN_U456 } + pages["page3"] = []string{} + nextPageCursors := []string{ "page2", "page3" } + + age := "" + runName := "U456" + requestor := "" + result := "" + shouldGetActive := false + pageSize := 1 + + server := NewRunsGetServletMock(t, http.StatusOK, nextPageCursors, pages, pageSize, runName) + defer server.Close() + + outputFormat := "raw" + mockConsole := utils.NewMockConsole() + + apiServerUrl := server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockTimeService := utils.NewMockTimeService() + + // When... + err := GetRuns(runName, age, requestor, result, shouldGetActive, outputFormat, mockTimeService, mockConsole, apiServerUrl, apiClient) + + // Then... + assert.Nil(t, err) + runsReturned := mockConsole.ReadText() + assert.Contains(t, runsReturned, runName) + + run := "U456|Finished|Passed|2023-05-10T06:00:13.043037Z|2023-05-10T06:00:36.159003Z|2023-05-10T06:02:53.823338Z|137664|myTestPackage.MyTestName|unitTesting|myBundleId|" + apiServerUrl + "/ras/runs/xxx876xxx/runlog\n" + expectedResults := run + run + assert.Equal(t, runsReturned, expectedResults) +} From 27454b1ae15fad1cb3622677adb73fe26bd74cdc Mon Sep 17 00:00:00 2001 From: Mike Cobbett <77053+techcobweb@users.noreply.github.com> Date: Tue, 27 Aug 2024 17:02:00 +0100 Subject: [PATCH 07/30] JVM launching -D parameters should be passed before the -jar parameter to have any effect (#275) Signed-off-by: Mike Cobbett <77053+techcobweb@users.noreply.github.com> Signed-off-by: Aashir Siddiqui --- .secrets.baseline | 10 ++++ pkg/launcher/jvmLauncher.go | 17 ++++--- pkg/launcher/jvmLauncher_test.go | 83 ++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 7 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 8c3e5d0a..40a5223c 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -105,6 +105,16 @@ "type": "Secret Keyword", "verified_result": null } + ], + "pkg/launcher/jvmLauncher_test.go": [ + { + "hashed_secret": "c042bdfc4bc5516ec716afe9e85c173b614ff9f5", + "is_secret": false, + "is_verified": false, + "line_number": 824, + "type": "Hex High Entropy String", + "verified_result": null + } ] }, "version": "0.13.1+ibm.62.dss", diff --git a/pkg/launcher/jvmLauncher.go b/pkg/launcher/jvmLauncher.go index 02b29fd4..ded7a746 100644 --- a/pkg/launcher/jvmLauncher.go +++ b/pkg/launcher/jvmLauncher.go @@ -667,14 +667,21 @@ func getCommandSyntax( args = appendArgsBootstrapJvmLaunchOptions(args, bootstrapProperties) - args = append(args, "-jar") - args = append(args, bootJarPath) - + // Note: Any -D properties are options for the JVM, so must appear before the -jar parameter. + // Parameters after the -jar parameter get passed into the 'main' of the launched java program. args = append(args, "-Dfile.encoding=UTF-8") nativeGalasaHomeFolderPath := galasaHome.GetNativeFolderPath() args = append(args, `-DGALASA_HOME="`+nativeGalasaHomeFolderPath+`"`) + // If there is a jwt, pass it through. + if jwt != "" { + args = append(args, "-DGALASA_JWT="+jwt) + } + + args = append(args, "-jar") + args = append(args, bootJarPath) + // --localmaven file://${M2_PATH}/repository/ // Note: URLs always have forward-slashes localMaven, err = defaultLocalMavenIfNotSet(localMaven, fileSystem) @@ -725,10 +732,6 @@ func getCommandSyntax( args = append(args, "--trace") } - // If there is a jwt, pass it through. - if jwt != "" { - args = append(args, "-DGALASA_JWT="+jwt) - } } return cmd, args, err diff --git a/pkg/launcher/jvmLauncher_test.go b/pkg/launcher/jvmLauncher_test.go index d896492d..191c522b 100644 --- a/pkg/launcher/jvmLauncher_test.go +++ b/pkg/launcher/jvmLauncher_test.go @@ -8,6 +8,7 @@ package launcher import ( "log" "strconv" + "strings" "testing" "github.com/galasa-dev/cli/pkg/api" @@ -767,6 +768,88 @@ func TestCommandIncludesGALASA_HOMESystemProperty(t *testing.T) { assert.Contains(t, args, `-DGALASA_HOME="/User/Home/testuser/.galasa"`) } +func TestCommandAllDashDSystemPropertiesPassedAppearBeforeTheDashJar(t *testing.T) { + + bootstrapProps, _, galasaHome, fs, + javaHome, + testObrs, + testLocation, + remoteMaven, + localMaven, + galasaVersionToRun, + overridesFilePath, + _ := getDefaultCommandSyntaxTestParameters() + + isTraceEnabled := true + isDebugEnabled := false + var debugPort uint32 = 0 + debugMode := "" + + cmd, args, err := getCommandSyntax( + bootstrapProps, + galasaHome, + fs, javaHome, + testObrs, + testLocation, + remoteMaven, + localMaven, + galasaVersionToRun, + overridesFilePath, + "", // No Gherkin URL supplied + isTraceEnabled, + isDebugEnabled, + debugPort, + debugMode, + BLANK_JWT, + ) + + assert.NotNil(t, cmd) + assert.NotNil(t, args) + assert.Nil(t, err) + + // Combine all arguments into a single string. + allArgs := strings.Join(args, " ") + + allDashDIndexes := getAllIndexesOfSubstring(allArgs, "-D") + allDashJarIndexes := getAllIndexesOfSubstring(allArgs, "-jar") + + assert.Equal(t, 1, len(allDashJarIndexes), "-jar option is found in command launch parameters an unexpected number of times") + dashJarIndex := allDashJarIndexes[0] + for _, dashDIndex := range allDashDIndexes { + assert.Less(t, dashDIndex, dashJarIndex, "A -Dxxx parameter is found after the -jar parameter, so will do nothing. -D parameters should appear before the -jar parameter") + } +} + +func TestFindingIndexesOfSubstring(t *testing.T) { + originalString := "012345678901234567890" + indexes := getAllIndexesOfSubstring(originalString, "01") + if assert.Equal(t, 2, len(indexes)) { + assert.Equal(t, 0, indexes[0]) + assert.Equal(t, 10, indexes[1]) + } +} + +func getAllIndexesOfSubstring(originalString string, subString string) []int { + var result []int = make([]int, 0) + + searchString := originalString + charactersProcessed := 0 + isDone := false + for !isDone { + + index := strings.Index(searchString, subString) + if index == -1 { + isDone = true + } else { + result = append(result, index+charactersProcessed) + searchString = searchString[index+1:] + charactersProcessed += index + 1 + } + } + + return result +} + func TestCommandIncludesFlagsFromBootstrapProperties(t *testing.T) { bootstrapProps, _, galasaHome, fs, javaHome, From 7e0165a18ca3886431235208266324f1c247cee0 Mon Sep 17 00:00:00 2001 From: Mike Cobbett <77053+techcobweb@users.noreply.github.com> Date: Tue, 27 Aug 2024 17:54:16 +0100 Subject: [PATCH 08/30] CLI should handle long and multi-line property values better. (#274) * handle properties which have newlines or are very long better. Cropping text in the summary view. * explained away the interesting transform of apiversion to apiVersion in comments --------- Signed-off-by: Mike Cobbett <77053+techcobweb@users.noreply.github.com> Signed-off-by: Aashir Siddiqui --- pkg/propertiesformatter/propertyFormatter.go | 15 ++++++ .../propertyFormatter_test.go | 46 +++++++++++++++++++ pkg/propertiesformatter/rawFormatter.go | 6 +-- pkg/propertiesformatter/summaryFormatter.go | 2 +- pkg/propertiesformatter/yamlFormatter.go | 8 +++- 5 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 pkg/propertiesformatter/propertyFormatter_test.go diff --git a/pkg/propertiesformatter/propertyFormatter.go b/pkg/propertiesformatter/propertyFormatter.go index cf97a270..61608c7a 100644 --- a/pkg/propertiesformatter/propertyFormatter.go +++ b/pkg/propertiesformatter/propertyFormatter.go @@ -37,6 +37,8 @@ const ( //namespaces display HEADER_NAMESPACE = "namespace" HEADER_NAMESPACE_TYPE = "type" + + PROPERTY_VALUE_MAX_VISIBLE_LENGTH = 60 ) type PropertyFormatter interface { @@ -59,6 +61,19 @@ func calculateMaxLengthOfEachColumn(table [][]string) []int { return columnLengths } +func substituteNewLines(originalPropValue string) string { + newLinesReplacedValue := strings.Replace(originalPropValue, "\n", "\\n", -1) + return newLinesReplacedValue +} + +func cropExtraLongValue(originalPropValue string) string { + croppedValue := originalPropValue + if len(originalPropValue) > PROPERTY_VALUE_MAX_VISIBLE_LENGTH { + croppedValue = originalPropValue[:PROPERTY_VALUE_MAX_VISIBLE_LENGTH] + `...(cropped)` + } + return croppedValue +} + func writeFormattedTableToStringBuilder(table [][]string, buff *strings.Builder, columnLengths []int) { for _, row := range table { for column, val := range row { diff --git a/pkg/propertiesformatter/propertyFormatter_test.go b/pkg/propertiesformatter/propertyFormatter_test.go new file mode 100644 index 00000000..ae88bcf4 --- /dev/null +++ b/pkg/propertiesformatter/propertyFormatter_test.go @@ -0,0 +1,46 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package propertiesformatter + +import ( + "log" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLongPropertyGetsCropped(t *testing.T) { + // For... + original := "012345678901234567890123456789012345678901234567890123456789-this-is-over-long" + expectedPropertyValue := "012345678901234567890123456789012345678901234567890123456789...(cropped)" + + croppedValue := cropExtraLongValue(original) + // Then... + assert.Equal(t, croppedValue, expectedPropertyValue) +} + +func TestShortPropertyGetsUnaffectedByCropping(t *testing.T) { + // For... + original := "this-is-short-enough-not-to-be-cropped" + expectedPropertyValue := original + + croppedValue := cropExtraLongValue(original) + // Then... + assert.Equal(t, croppedValue, expectedPropertyValue) +} + +func TestShortPropertyWithNewLinesGetsReplacedBySlashN(t *testing.T) { + // For... + original := "this-is-a-value\nspread-over-multiple\nlines" + expectedPropertyValue := "this-is-a-value\nspread-over-multiple\nlines" + + croppedValue := cropExtraLongValue(original) + log.Print("original:" + original) + log.Print("cropped:" + croppedValue) + // Then... + assert.Equal(t, croppedValue, expectedPropertyValue) +} diff --git a/pkg/propertiesformatter/rawFormatter.go b/pkg/propertiesformatter/rawFormatter.go index fa1c251d..b15a85ae 100644 --- a/pkg/propertiesformatter/rawFormatter.go +++ b/pkg/propertiesformatter/rawFormatter.go @@ -29,7 +29,7 @@ func (*PropertyRawFormatter) GetName() string { } func (*PropertyRawFormatter) FormatProperties(cpsProperties []galasaapi.GalasaProperty) (string, error) { - var result string = "" + result := "" buff := strings.Builder{} var err error @@ -40,7 +40,7 @@ func (*PropertyRawFormatter) FormatProperties(cpsProperties []galasaapi.GalasaPr buff.WriteString(namespace + "|" + name + "|" + - value + "\n") + substituteNewLines(value) + "\n") } result = buff.String() @@ -48,7 +48,7 @@ func (*PropertyRawFormatter) FormatProperties(cpsProperties []galasaapi.GalasaPr } func (*PropertyRawFormatter) FormatNamespaces(namespaces []galasaapi.Namespace) (string, error) { - var result string = "" + result := "" buff := strings.Builder{} var err error diff --git a/pkg/propertiesformatter/summaryFormatter.go b/pkg/propertiesformatter/summaryFormatter.go index 79df5bce..f33a4564 100644 --- a/pkg/propertiesformatter/summaryFormatter.go +++ b/pkg/propertiesformatter/summaryFormatter.go @@ -48,7 +48,7 @@ func (*PropertySummaryFormatter) FormatProperties(cpsProperties []galasaapi.Gala value := *property.Data.Value line = append(line, namespace) - line = append(line, name, value) + line = append(line, name, cropExtraLongValue(substituteNewLines(value))) table = append(table, line) } diff --git a/pkg/propertiesformatter/yamlFormatter.go b/pkg/propertiesformatter/yamlFormatter.go index ec7e5818..9fc8c04c 100644 --- a/pkg/propertiesformatter/yamlFormatter.go +++ b/pkg/propertiesformatter/yamlFormatter.go @@ -31,7 +31,6 @@ func (*PropertyYamlFormatter) GetName() string { } func (*PropertyYamlFormatter) FormatProperties(cpsProperties []galasaapi.GalasaProperty) (string, error) { - var result string = "" var err error buff := strings.Builder{} //totalProperties := len(cpsProperties) @@ -47,6 +46,11 @@ func (*PropertyYamlFormatter) FormatProperties(cpsProperties []galasaapi.GalasaP yamlRepresentationBytes, err = yaml.Marshal(property) if err == nil { yamlStr := string(yamlRepresentationBytes) + + // The generated bean serialises in json as 'apiVersion' which is correct. In yaml it serialises as 'apiversion' (incorrect) + // So this is a hack to correct that failure. + // Note: This will corrupt any value string which also has 'apiversion' inside it ! + // TODO: The fix is to change the bean and add a 'yaml' annotation so it gets rendered correctly. Golang has yaml annotations, but does the generator support them ? yamlStr = strings.ReplaceAll(yamlStr, "apiversion", "apiVersion") propertyString += yamlStr } @@ -54,6 +58,6 @@ func (*PropertyYamlFormatter) FormatProperties(cpsProperties []galasaapi.GalasaP buff.WriteString(propertyString) } - result = buff.String() + result := buff.String() return result, err } From e80f2867fbf7012ef37e74a1351f8a146bc5cf31 Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Fri, 30 Aug 2024 14:58:25 +0100 Subject: [PATCH 09/30] Add copyright and remove argo app param Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui --- .github/workflows/build.yml | 19 +++++++++---------- .github/workflows/pr-build.yml | 13 +++++++------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f47f0c27..90c5dc3c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,3 +1,8 @@ +# +# Copyright contributors to the Galasa project +# +# SPDX-License-Identifier: EPL-2.0 +# name: Main build on: @@ -9,8 +14,6 @@ env: REGISTRY: ghcr.io NAMESPACE: galasa-dev BRANCH: ${{ github.ref_name }} - ARGO_APP_BRANCH: gh # TODO: remove this parameter and just use env.BRANCH once we update development.galasa.dev/main with these workflows. - jobs: log-github-ref: @@ -39,7 +42,8 @@ jobs: run : | set -o pipefail gradle -b build.gradle installJarsIntoTemplates --info \ - -PsourceMaven=https://development.galasa.dev/${{ env.ARGO_APP_BRANCH }}/maven-repo/maven \ + --no-daemon --console plain \ + -PsourceMaven=https://development.galasa.dev/${{ env.BRANCH }}/maven-repo/maven \ -PcentralMaven=https://repo.maven.apache.org/maven2/ \ -PtargetMaven=${{ github.workspace }}/repo 2>&1 | tee build.log @@ -95,11 +99,6 @@ jobs: run : | ./test-galasactl-local.sh --buildTool maven - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 - with: - gradle-version: 8.9 - - name: Run local test script with Gradle run : | ./test-galasactl-local.sh --buildTool gradle @@ -177,13 +176,13 @@ jobs: env: ARGOCD_AUTH_TOKEN: ${{ secrets.ARGOCD_TOKEN }} run: | - docker run --env ARGOCD_AUTH_TOKEN=${{ env.ARGOCD_AUTH_TOKEN }} --rm -v ${{ github.workspace }}:/var/workspace ghcr.io/galasa-dev/argocdcli:main app actions run ${{ env.ARGO_APP_BRANCH }}-cli restart --kind Deployment --resource-name cli-${{ env.ARGO_APP_BRANCH }} --server argocd.galasa.dev + docker run --env ARGOCD_AUTH_TOKEN=${{ env.ARGOCD_AUTH_TOKEN }} --rm -v ${{ github.workspace }}:/var/workspace ghcr.io/galasa-dev/argocdcli:main app actions run ${{ env.BRANCH }}-cli restart --kind Deployment --resource-name cli-${{ env.BRANCH }} --server argocd.galasa.dev - name: Wait for application health in ArgoCD env: ARGOCD_AUTH_TOKEN: ${{ secrets.ARGOCD_TOKEN }} run: | - docker run --env ARGOCD_AUTH_TOKEN=${{ env.ARGOCD_AUTH_TOKEN }} --rm -v ${{ github.workspace }}:/var/workspace ghcr.io/galasa-dev/argocdcli:main app wait ${{ env.ARGO_APP_BRANCH }}-cli --resource apps:Deployment:cli-${{ env.ARGO_APP_BRANCH }} --health --server argocd.galasa.dev + docker run --env ARGOCD_AUTH_TOKEN=${{ env.ARGOCD_AUTH_TOKEN }} --rm -v ${{ github.workspace }}:/var/workspace ghcr.io/galasa-dev/argocdcli:main app wait ${{ env.BRANCH }}-cli --resource apps:Deployment:cli-${{ env.BRANCH }} --health --server argocd.galasa.dev trigger-isolated-workflow: name: Trigger Isolated workflow diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index 64095264..eb6dd555 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -1,3 +1,8 @@ +# +# Copyright contributors to the Galasa project +# +# SPDX-License-Identifier: EPL-2.0 +# name: PR build on: @@ -28,7 +33,8 @@ jobs: run : | set -o pipefail gradle -b build.gradle installJarsIntoTemplates --info \ - -PsourceMaven=https://development.galasa.dev/gh/maven-repo/maven \ + --no-daemon --console plain \ + -PsourceMaven=https://development.galasa.dev/main/maven-repo/maven \ -PcentralMaven=https://repo.maven.apache.org/maven2/ \ -PtargetMaven=${{ github.workspace }}/repo 2>&1 | tee build.log @@ -84,11 +90,6 @@ jobs: run : | ./test-galasactl-local.sh --buildTool maven - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 - with: - gradle-version: 8.9 - - name: Run local test script with Gradle run : | ./test-galasactl-local.sh --buildTool gradle From b9e61dce5521fecd9b4c2267b7075676246949d5 Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Tue, 10 Sep 2024 11:32:16 +0100 Subject: [PATCH 10/30] Disabling gradle cache to avoid build problems Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui --- .github/workflows/build.yml | 1 + .github/workflows/pr-build.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 90c5dc3c..60254f89 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,6 +36,7 @@ jobs: uses: gradle/actions/setup-gradle@v3 with: gradle-version: 8.9 + cache-disabled: true # Pull down dependencies with Gradle and put them in the right places. - name: Gather dependencies using Gradle diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index eb6dd555..114d7b7b 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -27,6 +27,7 @@ jobs: uses: gradle/actions/setup-gradle@v3 with: gradle-version: 8.9 + cache-disabled: true # Pull down dependencies with Gradle and put them in the right places. - name: Gather dependencies using Gradle From f590606c8bdcd994dbc7787f663ff4474615996c Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Thu, 12 Sep 2024 12:55:25 +0100 Subject: [PATCH 11/30] Change the CLI's base image to ubuntu so it has bash Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui --- dockerfiles/dockerfile.galasactl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dockerfiles/dockerfile.galasactl b/dockerfiles/dockerfile.galasactl index 24f433a3..96f6d5c5 100644 --- a/dockerfiles/dockerfile.galasactl +++ b/dockerfiles/dockerfile.galasactl @@ -1,4 +1,4 @@ -FROM harbor.galasa.dev/docker_proxy_cache/library/alpine:3.18.5 +FROM harbor.galasa.dev/docker_proxy_cache/library/ubuntu:20.04 ARG platform From dca021ec8e9b5345c4f833b8cddcd5ac0bfa9753 Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Thu, 12 Sep 2024 14:14:45 +0100 Subject: [PATCH 12/30] Empty commit for build Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui From ea2126b40a2b5e2f7bdfa558debcdfb7a54395fc Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Fri, 13 Sep 2024 13:33:22 +0100 Subject: [PATCH 13/30] Remove maven cache Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui --- .github/workflows/build.yml | 1 - .github/workflows/pr-build.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 60254f89..31143a61 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,7 +90,6 @@ jobs: with: java-version: '17' distribution: 'semeru' - cache: maven - name: Chmod local test script run: | diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index 114d7b7b..c12afb68 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -81,7 +81,6 @@ jobs: with: java-version: '17' distribution: 'semeru' - cache: maven - name: Chmod local test script run: | From 9e3868e5debcbd724da450decd408a0ce35801bf Mon Sep 17 00:00:00 2001 From: Aashir Siddiqui Date: Wed, 18 Sep 2024 09:53:07 +0100 Subject: [PATCH 14/30] Signing off commits Signed-off-by: Aashir Siddiqui --- Makefile | 107 ++-- README.md | 14 + docs/generated/errors-list.md | 8 + docs/generated/galasactl_runs.md | 1 + docs/generated/galasactl_runs_delete.md | 31 ++ pkg/cmd/commandCollection.go | 6 + pkg/cmd/factory.go | 4 + pkg/cmd/runsDelete.go | 157 ++++++ pkg/cmd/runsSubmit.go | 2 +- pkg/errors/errorMessage.go | 11 + pkg/errors/galasaAPIError.go | 14 +- pkg/runs/runsDelete.go | 165 ++++++ pkg/runs/runsDelete_test.go | 473 ++++++++++++++++++ pkg/runs/submitter.go | 21 +- pkg/runs/submitter_test.go | 2 +- pkg/spi/byteReader.go | 13 + pkg/spi/factory.go | 1 + pkg/utils/byteReader.go | 24 + pkg/utils/byteReaderMock.go | 39 ++ pkg/utils/factoryMock.go | 8 + pkg/utils/httpInteractionMock.go | 74 +++ ...ava_testUtils.go => java_test_fixtures.go} | 0 test-scripts/runs-tests.sh | 100 +++- 23 files changed, 1207 insertions(+), 68 deletions(-) create mode 100644 docs/generated/galasactl_runs_delete.md create mode 100644 pkg/cmd/runsDelete.go create mode 100644 pkg/runs/runsDelete.go create mode 100644 pkg/runs/runsDelete_test.go create mode 100644 pkg/spi/byteReader.go create mode 100644 pkg/utils/byteReader.go create mode 100644 pkg/utils/byteReaderMock.go create mode 100644 pkg/utils/httpInteractionMock.go rename pkg/utils/{java_testUtils.go => java_test_fixtures.go} (100%) diff --git a/Makefile b/Makefile index d512f5a6..9fa93a30 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,15 @@ # # SPDX-License-Identifier: EPL-2.0 # + +# Rather than keep tabs on all of the source folders, lets create the list of things we are dependent upon +# dynamically using a cool shell script. + +# A list of source files. This is completely expanded-out. +# Evaluates to something like this: ./pkg/tokensformatter/tokensFormatter.go ./pkg/tokensformatter/summaryFormatter_test.go +# We want to run some targets if any of the source files have changed. +SOURCE_FILES := $(shell find . -iname "*.go"| tr "\n" " ") embedded_info + all: tests galasactl gendocs-galasactl galasactl: \ @@ -15,51 +24,69 @@ galasactl: \ # 'gendocs-galasactl' is a command-line tool which generates documentation about the galasactl tool. # When executed, the .md produced contain up-to-date information on tool syntax. -gendocs-galasactl: bin/gendocs-galasactl-darwin-arm64 bin/gendocs-galasactl-linux-arm64 bin/gendocs-galasactl-darwin-x86_64 bin/gendocs-galasactl-linux-x86_64 - -tests: galasactl-source build/coverage.txt build/coverage.html - -build/coverage.out : galasactl-source +gendocs-galasactl: \ + bin/gendocs-galasactl-darwin-arm64 \ + bin/gendocs-galasactl-linux-arm64 \ + bin/gendocs-galasactl-darwin-x86_64 \ + bin/gendocs-galasactl-linux-x86_64 + +tests: $(SOURCE_FILES) build/coverage.txt build/coverage.html + +# Build a list of the source packages +# Note: +# - We don't want to include the generated galasaapi +# - We don't want to include the top-level command code at ./cmd +# - We join them into a comma-separated list +# - We smarten-up the begining element (remove ".,") +# - We smarten-up the end element (remove trailing ",") +# - We remove any spaces in the whole thing,. +# So we end up with something like this: ./pkg/api,./pkg/auth,./pkg/cmd,./pkg/embedded,./pkg/errors,./pkg/files ...etc. +COVERAGE_SOURCE_PACKAGES := ${shell find . -iname "*.go" \ + | xargs dirname {} \ + | grep -v "/pkg/galasaapi" \ + | grep -v "[.]/cmd/" \ + | sort \ + | uniq \ + | tr '\n' ',' \ + | sed "s/[.],//g" \ + | sed "s/,$$//g" \ + | sed "s/ //g" \ +} + +# If any source code has changed, re-run the unit tests. +build/coverage.out : $(SOURCE_FILES) mkdir -p build - go test -v -cover -coverprofile=build/coverage.out -coverpkg pkg/api,./pkg/auth,./pkg/cmd,./pkg/runsformatter,./pkg/errors,./pkg/launcher,./pkg/utils,./pkg/runs,./pkg/properties,./pkg/propertiesformatter,./pkg/resources ./pkg/... + echo "Coverage source packages are $(COVERAGE_SOURCE_PACKAGES)" + go test -v -cover -coverprofile=build/coverage.out -coverpkg $(COVERAGE_SOURCE_PACKAGES) ./pkg/... + +build/coverage-sanitised.out : build/coverage.out + cat build/coverage.out \ + | grep -v "Mock" \ + | grep -v "ixture" \ + > build/coverage-sanitised.out -build/coverage.html : build/coverage.out +# Unit test output --> an html report. +build/coverage.html : build/coverage-sanitised.out go tool cover -html=build/coverage.out -o build/coverage.html -build/coverage.txt : build/coverage.out +# Unit test output --> a text file report. +build/coverage.txt : build/coverage-sanitised.out go tool cover -func=build/coverage.out > build/coverage.txt cat build/coverage.txt -galasactl-source : \ - ./cmd/galasactl/*.go \ - ./pkg/api/*.go \ - ./pkg/auth/*.go \ - ./pkg/runsformatter/*.go \ - ./pkg/cmd/*.go \ - ./pkg/utils/*.go \ - ./pkg/runs/*.go \ - ./pkg/launcher/*.go \ - ./pkg/files/*.go \ - ./pkg/images/*.go \ - ./pkg/props/*.go \ - ./pkg/properties/*.go \ - ./pkg/propertiesformatter/*.go \ - ./pkg/resources/*.go \ - ./pkg/spi/*.go \ - ./pkg/tokensformatter/*.go \ - embedded_info +coverage : build/coverage.txt # The build process -embedded_info : \ - pkg/embedded/templates/version/build.properties - +GENERATED_BUILD_PROPERTIES_FILE := pkg/embedded/templates/version/build.properties +embedded_info : $(GENERATED_BUILD_PROPERTIES_FILE) + pkg/embedded/templates/version : mkdir -p $@ # Build a properties file containing versions of things. # Then the galasactl can embed the data and read it at run-time. -pkg/embedded/templates/version/build.properties : VERSION pkg/embedded/templates/version Makefile build.gradle +$(GENERATED_BUILD_PROPERTIES_FILE) : VERSION pkg/embedded/templates/version Makefile build.gradle echo "# Property file generated at build-time" > $@ # Turn the contents of VERSION file into a properties file value. cat VERSION | sed "s/^/galasactl.version = /1" >> $@ ; echo "" >> $@ @@ -72,34 +99,34 @@ pkg/embedded/templates/version/build.properties : VERSION pkg/embedded/templates echo "# version of the rest api that is compiled and the client is expecting from the ecosystem." >> $@ cat build/dependencies/openapi.yaml | grep "version :" | cut -f2 -d'"' | sed "s/^/galasactl.rest.api.version = /" >> $@ -bin/galasactl-linux-x86_64 : galasactl-source +bin/galasactl-linux-x86_64 : $(SOURCE_FILES) CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/galasactl-linux-x86_64 ./cmd/galasactl -bin/galasactl-windows-x86_64.exe : galasactl-source +bin/galasactl-windows-x86_64.exe : $(SOURCE_FILES) CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o bin/galasactl-windows-x86_64.exe ./cmd/galasactl -bin/galasactl-darwin-x86_64 : galasactl-source +bin/galasactl-darwin-x86_64 : $(SOURCE_FILES) CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o bin/galasactl-darwin-x86_64 ./cmd/galasactl -bin/galasactl-darwin-arm64 : galasactl-source +bin/galasactl-darwin-arm64 : $(SOURCE_FILES) CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o bin/galasactl-darwin-arm64 ./cmd/galasactl -bin/galasactl-linux-arm64 : galasactl-source +bin/galasactl-linux-arm64 : $(SOURCE_FILES) CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o bin/galasactl-linux-arm64 ./cmd/galasactl -bin/galasactl-linux-s390x : galasactl-source +bin/galasactl-linux-s390x : $(SOURCE_FILES) CGO_ENABLED=0 GOOS=linux GOARCH=s390x go build -o bin/galasactl-linux-s390x ./cmd/galasactl -bin/gendocs-galasactl-darwin-arm64 : galasactl-source +bin/gendocs-galasactl-darwin-arm64 : $(SOURCE_FILES) CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o bin/gendocs-galasactl-darwin-arm64 ./cmd/gendocs-galasactl -bin/gendocs-galasactl-linux-arm64 : galasactl-source +bin/gendocs-galasactl-linux-arm64 : $(SOURCE_FILES) CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o bin/gendocs-galasactl-linux-arm64 ./cmd/gendocs-galasactl -bin/gendocs-galasactl-linux-x86_64 : galasactl-source +bin/gendocs-galasactl-linux-x86_64 : $(SOURCE_FILES) CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/gendocs-galasactl-linux-x86_64 ./cmd/gendocs-galasactl -bin/gendocs-galasactl-darwin-x86_64 : galasactl-source +bin/gendocs-galasactl-darwin-x86_64 : $(SOURCE_FILES) CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o bin/gendocs-galasactl-darwin-x86_64 ./cmd/gendocs-galasactl clean: diff --git a/README.md b/README.md index c9e00991..7bf1641a 100644 --- a/README.md +++ b/README.md @@ -384,6 +384,20 @@ galasactl runs get --name C1234 --format badFormatterName ``` For a complete list of supported parameters see [here](./docs/generated/galasactl_runs_get.md). +## runs delete + +This command deletes a test run from an ecosystem's RAS. The name of the test run to delete can be provided to delete it along with any associated artifacts that have been stored. + +### Examples + +A run named "C1234" can be deleted using the following command: + +``` +galasactl runs delete --name C1234 +``` + +A complete list of supported parameters for the `runs delete` command is available [here](./docs/generated/galasactl_runs_delete.md) + ## runs download This command downloads all artifacts for a test run that are stored in an ecosystem's RAS. diff --git a/docs/generated/errors-list.md b/docs/generated/errors-list.md index 19496af7..3b75dd85 100644 --- a/docs/generated/errors-list.md +++ b/docs/generated/errors-list.md @@ -152,6 +152,14 @@ The `galasactl` tool can generate the following errors: - GAL1154E: The provided token ID, '{}', does not match formatting requirements. The token ID can contain any character in the 'a'-'z', 'A'-'Z', '0'-'9', '-' (dash), or '_' (underscore) ranges only. - GAL1155E: The id provided by the --id field cannot be an empty string. - GAL1156E: '{}' is not supported as a valid value. Valid values are 'me'. +- GAL1157E: An attempt to delete a run named '{}' failed. Cause is {} +- GAL1158E: An attempt to delete a run named '{}' failed. Sending the delete request to the Galasa service failed. Cause is {} +- GAL1159E: An attempt to delete a run named '{}' failed. Unexpected http status code {} received from the server. +- GAL1160E: An attempt to delete a run named '{}' failed. Unexpected http status code {} received from the server. Error details from the server could not be read. Cause: {} +- GAL1161E: An attempt to delete a run named '{}' failed. Unexpected http status code {} received from the server. Error details from the server are not in a valid json format. Cause: '{}' +- GAL1162E: An attempt to delete a run named '{}' failed. Unexpected http status code {} received from the server. Error details from the server are: '{}' +- GAL1163E: The run named '{}' could not be deleted because it was not found by the Galasa service. Try listing runs using 'galasactl runs get' to identify the one you wish to delete +- GAL1164E: An attempt to delete a run named '{}' failed. Unexpected http status code {} received from the server. Error details from the server are not in the json format. - GAL1225E: Failed to open file '{}' cause: {}. Check that this file exists, and that you have read permissions. - GAL1226E: Internal failure. Contents of gzip could be read, but not decoded. New gzip reader failed: file: {} error: {} - GAL1227E: Internal failure. Contents of gzip could not be decoded. {} error: {} diff --git a/docs/generated/galasactl_runs.md b/docs/generated/galasactl_runs.md index ab8c3551..f85b7169 100644 --- a/docs/generated/galasactl_runs.md +++ b/docs/generated/galasactl_runs.md @@ -24,6 +24,7 @@ Assembles, submits and monitors test runs in Galasa Ecosystem * [galasactl](galasactl.md) - CLI for Galasa * [galasactl runs cancel](galasactl_runs_cancel.md) - cancel an active run in the ecosystem +* [galasactl runs delete](galasactl_runs_delete.md) - Delete a named test run. * [galasactl runs download](galasactl_runs_download.md) - Download the artifacts of a test run which ran. * [galasactl runs get](galasactl_runs_get.md) - Get the details of a test runname which ran or is running. * [galasactl runs prepare](galasactl_runs_prepare.md) - prepares a list of tests diff --git a/docs/generated/galasactl_runs_delete.md b/docs/generated/galasactl_runs_delete.md new file mode 100644 index 00000000..0e54110f --- /dev/null +++ b/docs/generated/galasactl_runs_delete.md @@ -0,0 +1,31 @@ +## galasactl runs delete + +Delete a named test run. + +### Synopsis + +Delete a named test run. + +``` +galasactl runs delete [flags] +``` + +### Options + +``` + -h, --help Displays the options for the 'runs delete' command. + --name string the name of the test run we want to delete. +``` + +### Options inherited from parent commands + +``` + -b, --bootstrap string Bootstrap URL. Should start with 'http://' or 'file://'. If it starts with neither, it is assumed to be a fully-qualified path. If missing, it defaults to use the 'bootstrap.properties' file in your GALASA_HOME. Example: http://example.com/bootstrap, file:///user/myuserid/.galasa/bootstrap.properties , file://C:/Users/myuserid/.galasa/bootstrap.properties + --galasahome string Path to a folder where Galasa will read and write files and configuration settings. The default is '${HOME}/.galasa'. This overrides the GALASA_HOME environment variable which may be set instead. + -l, --log string File to which log information will be sent. Any folder referred to must exist. An existing file will be overwritten. Specify "-" to log to stderr. Defaults to not logging. +``` + +### SEE ALSO + +* [galasactl runs](galasactl_runs.md) - Manage test runs in the ecosystem + diff --git a/pkg/cmd/commandCollection.go b/pkg/cmd/commandCollection.go index a8048eb6..dec89295 100644 --- a/pkg/cmd/commandCollection.go +++ b/pkg/cmd/commandCollection.go @@ -53,6 +53,7 @@ const ( COMMAND_NAME_RUNS_SUBMIT_LOCAL = "runs submit local" COMMAND_NAME_RUNS_RESET = "runs reset" COMMAND_NAME_RUNS_CANCEL = "runs cancel" + COMMAND_NAME_RUNS_DELETE = "runs delete" COMMAND_NAME_RESOURCES = "resources" COMMAND_NAME_RESOURCES_APPLY = "resources apply" COMMAND_NAME_RESOURCES_CREATE = "resources create" @@ -297,6 +298,7 @@ func (commands *commandCollectionImpl) addRunsCommands(factory spi.Factory, root var runsSubmitLocalCommand spi.GalasaCommand var runsResetCommand spi.GalasaCommand var runsCancelCommand spi.GalasaCommand + var runsDeleteCommand spi.GalasaCommand runsCommand, err = NewRunsCmd(rootCommand) if err == nil { @@ -313,6 +315,9 @@ func (commands *commandCollectionImpl) addRunsCommands(factory spi.Factory, root runsResetCommand, err = NewRunsResetCommand(factory, runsCommand, rootCommand) if err == nil { runsCancelCommand, err = NewRunsCancelCommand(factory, runsCommand, rootCommand) + if err == nil { + runsDeleteCommand, err = NewRunsDeleteCommand(factory, runsCommand, rootCommand) + } } } } @@ -330,6 +335,7 @@ func (commands *commandCollectionImpl) addRunsCommands(factory spi.Factory, root commands.commandMap[runsSubmitLocalCommand.Name()] = runsSubmitLocalCommand commands.commandMap[runsResetCommand.Name()] = runsResetCommand commands.commandMap[runsCancelCommand.Name()] = runsCancelCommand + commands.commandMap[runsDeleteCommand.Name()] = runsDeleteCommand } return err diff --git a/pkg/cmd/factory.go b/pkg/cmd/factory.go index 148a364e..f8d2aca3 100644 --- a/pkg/cmd/factory.go +++ b/pkg/cmd/factory.go @@ -60,3 +60,7 @@ func (factory *RealFactory) GetAuthenticator(apiServerUrl string, galasaHome spi jwtCache := auth.NewJwtCache(factory.GetFileSystem(), galasaHome, factory.GetTimeService()) return auth.NewAuthenticator(apiServerUrl, factory.GetFileSystem(), galasaHome, factory.GetTimeService(), factory.GetEnvironment(), jwtCache) } + +func (*RealFactory) GetByteReader() spi.ByteReader { + return utils.NewByteReader() +} diff --git a/pkg/cmd/runsDelete.go b/pkg/cmd/runsDelete.go new file mode 100644 index 00000000..4ffc4720 --- /dev/null +++ b/pkg/cmd/runsDelete.go @@ -0,0 +1,157 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package cmd + +import ( + "log" + + "github.com/galasa-dev/cli/pkg/api" + "github.com/galasa-dev/cli/pkg/galasaapi" + "github.com/galasa-dev/cli/pkg/runs" + "github.com/galasa-dev/cli/pkg/spi" + "github.com/galasa-dev/cli/pkg/utils" + "github.com/spf13/cobra" +) + +// Objective: Allow the user to do this: +// runs delete --name 12345 +// And then show the results in a human-readable form. + +// Variables set by cobra's command-line parsing. +type RunsDeleteCmdValues struct { + runName string +} + +type RunsDeleteCommand struct { + values *RunsDeleteCmdValues + cobraCommand *cobra.Command +} + +func NewRunsDeleteCommand(factory spi.Factory, runsCommand spi.GalasaCommand, rootCommand spi.GalasaCommand) (spi.GalasaCommand, error) { + cmd := new(RunsDeleteCommand) + err := cmd.init(factory, runsCommand, rootCommand) + return cmd, err +} + +// ------------------------------------------------------------------------------------------------ +// Public methods +// ------------------------------------------------------------------------------------------------ +func (cmd *RunsDeleteCommand) Name() string { + return COMMAND_NAME_RUNS_DELETE +} + +func (cmd *RunsDeleteCommand) CobraCommand() *cobra.Command { + return cmd.cobraCommand +} + +func (cmd *RunsDeleteCommand) Values() interface{} { + return cmd.values +} + +// ------------------------------------------------------------------------------------------------ +// Private methods +// ------------------------------------------------------------------------------------------------ + +func (cmd *RunsDeleteCommand) init(factory spi.Factory, runsCommand spi.GalasaCommand, rootCommand spi.GalasaCommand) error { + var err error + cmd.values = &RunsDeleteCmdValues{} + cmd.cobraCommand, err = cmd.createCobraCommand(factory, runsCommand, rootCommand.Values().(*RootCmdValues)) + return err +} + +func (cmd *RunsDeleteCommand) createCobraCommand( + factory spi.Factory, + runsCommand spi.GalasaCommand, + rootCmdValues *RootCmdValues, +) (*cobra.Command, error) { + + var err error + runsCmdValues := runsCommand.Values().(*RunsCmdValues) + + runsDeleteCobraCmd := &cobra.Command{ + Use: "delete", + Short: "Delete a named test run.", + Long: "Delete a named test run.", + Args: cobra.NoArgs, + Aliases: []string{"runs delete"}, + RunE: func(cobraCmd *cobra.Command, args []string) error { + return cmd.executeRunsDelete(factory, runsCmdValues, rootCmdValues) + }, + } + + runsDeleteCobraCmd.Flags().StringVar(&cmd.values.runName, "name", "", "the name of the test run we want to delete.") + + runsDeleteCobraCmd.MarkFlagRequired("name") + + runsCommand.CobraCommand().AddCommand(runsDeleteCobraCmd) + + return runsDeleteCobraCmd, err +} + +func (cmd *RunsDeleteCommand) executeRunsDelete( + factory spi.Factory, + runsCmdValues *RunsCmdValues, + rootCmdValues *RootCmdValues, +) error { + + var err error + + // Operations on the file system will all be relative to the current folder. + fileSystem := factory.GetFileSystem() + + err = utils.CaptureLog(fileSystem, rootCmdValues.logFileName) + if err == nil { + rootCmdValues.isCapturingLogs = true + + log.Println("Galasa CLI - Delete runs about to execute") + + // Get the ability to query environment variables. + env := factory.GetEnvironment() + + var galasaHome spi.GalasaHome + galasaHome, err = utils.NewGalasaHome(fileSystem, env, rootCmdValues.CmdParamGalasaHomePath) + if err == nil { + + // Read the bootstrap properties. + var urlService *api.RealUrlResolutionService = new(api.RealUrlResolutionService) + var bootstrapData *api.BootstrapData + bootstrapData, err = api.LoadBootstrap(galasaHome, fileSystem, env, runsCmdValues.bootstrap, urlService) + if err == nil { + + var console = factory.GetStdOutConsole() + timeService := factory.GetTimeService() + + apiServerUrl := bootstrapData.ApiServerURL + log.Printf("The API server is at '%s'\n", apiServerUrl) + + authenticator := factory.GetAuthenticator( + apiServerUrl, + galasaHome, + ) + + var apiClient *galasaapi.APIClient + apiClient, err = authenticator.GetAuthenticatedAPIClient() + + byteReader := factory.GetByteReader() + + if err == nil { + // Call to process the command in a unit-testable way. + err = runs.RunsDelete( + cmd.values.runName, + console, + apiServerUrl, + apiClient, + timeService, + byteReader, + ) + } + } + } + } + + log.Printf("executeRunsDelete returning %v", err) + return err +} diff --git a/pkg/cmd/runsSubmit.go b/pkg/cmd/runsSubmit.go index db8ce46c..4251cc01 100644 --- a/pkg/cmd/runsSubmit.go +++ b/pkg/cmd/runsSubmit.go @@ -170,7 +170,7 @@ func (cmd *RunsSubmitCommand) executeSubmit( if err == nil { timeService := factory.GetTimeService() - var launcherInstance launcher.Launcher = nil + var launcherInstance launcher.Launcher // The launcher we are going to use to start/monitor tests. apiServerUrl := bootstrapData.ApiServerURL diff --git a/pkg/errors/errorMessage.go b/pkg/errors/errorMessage.go index 3d551704..4a313290 100644 --- a/pkg/errors/errorMessage.go +++ b/pkg/errors/errorMessage.go @@ -250,6 +250,17 @@ var ( GALASA_ERROR_MISSING_USER_LOGIN_ID_FLAG = NewMessageType("GAL1155E: The id provided by the --id field cannot be an empty string.", 1155, STACK_TRACE_NOT_WANTED) GALASA_ERROR_LOGIN_ID_NOT_SUPPORTED = NewMessageType("GAL1156E: '%s' is not supported as a valid value. Valid values are 'me'.", 1156, STACK_TRACE_NOT_WANTED) GALASA_ERROR_INVALID_LOGIN_ID = NewMessageType("GAL1157E: '%s' is not supported as a valid value. Valid value should not contain spaces. A value of 'admin' is valid but 'galasa admin' is not.", 1157, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_DELETE_RUN_FAILED = NewMessageType("GAL1157E: An attempt to delete a run named '%s' failed. Cause is %s", 1157, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_SERVER_DELETE_RUNS_FAILED = NewMessageType("GAL1158E: An attempt to delete a run named '%s' failed. Sending the delete request to the Galasa service failed. Cause is %v", 1158, STACK_TRACE_NOT_WANTED) + + // 4 related but slightly different errors, when an HTTP response arrives from the Galasa server, and we can/can't parse the payload to get the message details out. + GALASA_ERROR_DELETE_RUNS_NO_RESPONSE_CONTENT = NewMessageType("GAL1159E: An attempt to delete a run named '%s' failed. Unexpected http status code %v received from the server.", 1159, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_DELETE_RUNS_RESPONSE_PAYLOAD_UNREADABLE = NewMessageType("GAL1160E: An attempt to delete a run named '%s' failed. Unexpected http status code %v received from the server. Error details from the server could not be read. Cause: %s", 1160, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_DELETE_RUNS_UNPARSEABLE_CONTENT = NewMessageType("GAL1161E: An attempt to delete a run named '%s' failed. Unexpected http status code %v received from the server. Error details from the server are not in a valid json format. Cause: '%s'", 1161, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_DELETE_RUNS_SERVER_REPORTED_ERROR = NewMessageType("GAL1162E: An attempt to delete a run named '%s' failed. Unexpected http status code %v received from the server. Error details from the server are: '%s'", 1162, STACK_TRACE_NOT_WANTED) + + GALASA_ERROR_SERVER_DELETE_RUN_NOT_FOUND = NewMessageType("GAL1163E: The run named '%s' could not be deleted because it was not found by the Galasa service. Try listing runs using 'galasactl runs get' to identify the one you wish to delete", 1163, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_DELETE_RUNS_EXPLANATION_NOT_JSON = NewMessageType("GAL1164E: An attempt to delete a run named '%s' failed. Unexpected http status code %v received from the server. Error details from the server are not in the json format.", 1164, STACK_TRACE_NOT_WANTED) // Warnings... GALASA_WARNING_MAVEN_NO_GALASA_OBR_REPO = NewMessageType("GAL2000W: Warning: Maven configuration file settings.xml should contain a reference to a Galasa repository so that the galasa OBR can be resolved. The official release repository is '%s', and 'pre-release' repository is '%s'", 2000, STACK_TRACE_WANTED) diff --git a/pkg/errors/galasaAPIError.go b/pkg/errors/galasaAPIError.go index 4b756744..eb04bef8 100644 --- a/pkg/errors/galasaAPIError.go +++ b/pkg/errors/galasaAPIError.go @@ -20,7 +20,15 @@ type GalasaAPIError struct { // the reason for the failure. // NOTE: when this function is called ensure that the calling function has the `defer resp.Body.Close()` // called in order to ensure that the response body is closed when the function completes -func GetApiErrorFromResponse(body []byte) (*GalasaAPIError, error){ +func GetApiErrorFromResponse(body []byte) (*GalasaAPIError, error) { + return GetApiErrorFromResponseBytes(body, func(marshallingError error) error{ + err := NewGalasaError(GALASA_ERROR_UNABLE_TO_READ_RESPONSE_BODY, marshallingError) + return err + }, + ) +} + +func GetApiErrorFromResponseBytes(body []byte, marshallingErrorLambda func(marshallingError error) error) (*GalasaAPIError, error) { var err error apiError := new(GalasaAPIError) @@ -28,8 +36,8 @@ func GetApiErrorFromResponse(body []byte) (*GalasaAPIError, error){ err = json.Unmarshal(body, &apiError) if err != nil { - log.Printf("GetApiErrorFromResponse FAIL - %v", err) - err = NewGalasaError(GALASA_ERROR_UNABLE_TO_READ_RESPONSE_BODY, err.Error()) + log.Printf("GetApiErrorFromResponseBytes failed to unmarshal bytes into a galasa api error structure. %v", err.Error()) + err = marshallingErrorLambda(err) } return apiError, err } diff --git a/pkg/runs/runsDelete.go b/pkg/runs/runsDelete.go new file mode 100644 index 00000000..7aae4f46 --- /dev/null +++ b/pkg/runs/runsDelete.go @@ -0,0 +1,165 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package runs + +import ( + "context" + "log" + "net/http" + + "github.com/galasa-dev/cli/pkg/embedded" + galasaErrors "github.com/galasa-dev/cli/pkg/errors" + "github.com/galasa-dev/cli/pkg/galasaapi" + "github.com/galasa-dev/cli/pkg/spi" +) + +// --------------------------------------------------- + +// RunsDelete - performs all the logic to implement the `galasactl runs delete` command, +// but in a unit-testable manner. +func RunsDelete( + runName string, + console spi.Console, + apiServerUrl string, + apiClient *galasaapi.APIClient, + timeService spi.TimeService, + byteReader spi.ByteReader, +) error { + var err error + + log.Printf("RunsDelete entered.") + + if runName != "" { + // Validate the runName as best we can without contacting the ecosystem. + err = ValidateRunName(runName) + } + + if err == nil { + + requestorParameter := "" + resultParameter := "" + fromAgeHours := 0 + toAgeHours := 0 + shouldGetActive := false + var runs []galasaapi.Run + runs, err = GetRunsFromRestApi(runName, requestorParameter, resultParameter, fromAgeHours, toAgeHours, shouldGetActive, timeService, apiClient) + + if err == nil { + + if len(runs) == 0 { + err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_SERVER_DELETE_RUN_NOT_FOUND, runName) + } else { + err = deleteRuns(runs, apiClient, byteReader) + } + } + + if err != nil { + console.WriteString(err.Error()) + } + } + log.Printf("RunsDelete exiting. err is %v\n", err) + return err +} + +func deleteRuns( + runs []galasaapi.Run, + apiClient *galasaapi.APIClient, + byteReader spi.ByteReader, +) error { + var err error + + var restApiVersion string + var context context.Context = nil + + var httpResponse *http.Response + + restApiVersion, err = embedded.GetGalasactlRestApiVersion() + if err == nil { + for _, run := range runs { + runId := run.GetRunId() + runName := *run.GetTestStructure().RunName + + apicall := apiClient.ResultArchiveStoreAPIApi.DeleteRasRunById(context, runId).ClientApiVersion(restApiVersion) + httpResponse, err = apicall.Execute() + + // 200-299 http status codes manifest in an error. + if err != nil { + if httpResponse == nil { + // We never got a response, error sending it or something ? + err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_SERVER_DELETE_RUNS_FAILED, err.Error()) + } else { + err = httpResponseToGalasaError( + httpResponse, + runName, + byteReader, + galasaErrors.GALASA_ERROR_DELETE_RUNS_NO_RESPONSE_CONTENT, + galasaErrors.GALASA_ERROR_DELETE_RUNS_RESPONSE_PAYLOAD_UNREADABLE, + galasaErrors.GALASA_ERROR_DELETE_RUNS_UNPARSEABLE_CONTENT, + galasaErrors.GALASA_ERROR_DELETE_RUNS_SERVER_REPORTED_ERROR, + galasaErrors.GALASA_ERROR_DELETE_RUNS_EXPLANATION_NOT_JSON, + ) + } + } + + if err != nil { + break + } else { + log.Printf("Run with runId '%s' and runName '%s', was deleted OK.\n", runId, run.TestStructure.GetRunName()) + } + } + } + + return err +} + +func httpResponseToGalasaError( + response *http.Response, + identifier string, + byteReader spi.ByteReader, + errorMsgUnexpectedStatusCodeNoResponseBody *galasaErrors.MessageType, + errorMsgUnableToReadResponseBody *galasaErrors.MessageType, + errorMsgResponsePayloadInWrongFormat *galasaErrors.MessageType, + errorMsgReceivedFromApiServer *galasaErrors.MessageType, + errorMsgResponseContentTypeNotJson *galasaErrors.MessageType, +) error { + defer response.Body.Close() + var err error + var responseBodyBytes []byte + statusCode := response.StatusCode + + if response.ContentLength == 0 { + log.Printf("Failed - HTTP response - status code: '%v'\n", statusCode) + err = galasaErrors.NewGalasaError(errorMsgUnexpectedStatusCodeNoResponseBody, identifier, statusCode) + } else { + + contentType := response.Header.Get("Content-Type") + if contentType != "application/json" { + err = galasaErrors.NewGalasaError(errorMsgResponseContentTypeNotJson, identifier, statusCode) + } else { + responseBodyBytes, err = byteReader.ReadAll(response.Body) + if err != nil { + err = galasaErrors.NewGalasaError(errorMsgUnableToReadResponseBody, identifier, statusCode, err.Error()) + } else { + + var errorFromServer *galasaErrors.GalasaAPIError + errorFromServer, err = galasaErrors.GetApiErrorFromResponseBytes( + responseBodyBytes, + func (marshallingError error) error { + log.Printf("Failed - HTTP response - status code: '%v' payload in response is not json: '%v' \n", statusCode, string(responseBodyBytes)) + return galasaErrors.NewGalasaError(errorMsgResponsePayloadInWrongFormat, identifier, statusCode, marshallingError) + }, + ) + + if err == nil { + // server returned galasa api error structure we understand. + log.Printf("Failed - HTTP response - status code: '%v' server responded with error message: '%v' \n", statusCode, errorMsgReceivedFromApiServer) + err = galasaErrors.NewGalasaError(errorMsgReceivedFromApiServer, identifier, statusCode, errorFromServer.Message) + } + } + } + } + return err +} diff --git a/pkg/runs/runsDelete_test.go b/pkg/runs/runsDelete_test.go new file mode 100644 index 00000000..950a4685 --- /dev/null +++ b/pkg/runs/runsDelete_test.go @@ -0,0 +1,473 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package runs + +import ( + "encoding/json" + "net/http" + "strconv" + "testing" + + "github.com/galasa-dev/cli/pkg/api" + "github.com/galasa-dev/cli/pkg/errors" + "github.com/galasa-dev/cli/pkg/galasaapi" + "github.com/galasa-dev/cli/pkg/utils" + "github.com/stretchr/testify/assert" +) + +func createMockRun(runName string, runId string) galasaapi.Run { + run := *galasaapi.NewRun() + run.SetRunId(runId) + testStructure := *galasaapi.NewTestStructure() + testStructure.SetRunName(runName) + + run.SetTestStructure(testStructure) + return run +} + +func TestCanDeleteARun(t *testing.T) { + // Given... + runName := "J20" + runId := "J234567890" + + // Create the mock run to be deleted + runToDelete := createMockRun(runName, runId) + runToDeleteBytes, _ := json.Marshal(runToDelete) + runToDeleteJson := string(runToDeleteBytes) + + // Create the expected HTTP interactions with the API server + getRunsInteraction := utils.NewHttpInteraction("/ras/runs", http.MethodGet) + getRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + WriteMockRasRunsResponse(t, writer, req, runName, []string{ runToDeleteJson }) + } + + deleteRunsInteraction := utils.NewHttpInteraction("/ras/runs/" + runId, http.MethodDelete) + deleteRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.WriteHeader(http.StatusNoContent) + } + + interactions := []utils.HttpInteraction{ + getRunsInteraction, + deleteRunsInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockTimeService := utils.NewMockTimeService() + mockByteReader := utils.NewMockByteReader() + + // When... + err := RunsDelete( + runName, + console, + apiServerUrl, + apiClient, + mockTimeService, + mockByteReader) + + // Then... + assert.Nil(t, err, "RunsDelete returned an unexpected error") + assert.Empty(t, console.ReadText(), "The console was written to on a successful deletion, it should be empty") +} + +func TestCanDeleteRunAndReruns(t *testing.T) { + // Given... + runName := "J20" + runId := "J234567890" + reRun1Id := "ABC123" + reRun2Id := "DEF456" + + // Create the mock runs to be deleted - re-runs should have the same run name but different run IDs + runToDelete := createMockRun(runName, runId) + runToDeleteBytes, _ := json.Marshal(runToDelete) + runToDeleteJson := string(runToDeleteBytes) + + reRun1 := createMockRun(runName, reRun1Id) + reRun1Bytes, _ := json.Marshal(reRun1) + reRun1Json := string(reRun1Bytes) + + reRun2 := createMockRun(runName, reRun2Id) + reRun2Bytes, _ := json.Marshal(reRun2) + reRun2Json := string(reRun2Bytes) + + runsAsJsonStrings := []string{ + runToDeleteJson, + reRun1Json, + reRun2Json, + } + + // Create the expected HTTP interactions with the API server + getRunsInteraction := utils.NewHttpInteraction("/ras/runs", http.MethodGet) + getRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + WriteMockRasRunsResponse(t, writer, req, runName, runsAsJsonStrings) + } + + successfulDeleteFunc := func(writer http.ResponseWriter, req *http.Request) { + writer.WriteHeader(http.StatusNoContent) + } + + deleteRunInteraction := utils.NewHttpInteraction("/ras/runs/" + runId, http.MethodDelete) + deleteRunInteraction.WriteHttpResponseFunc = successfulDeleteFunc + + deleteRerun1Interaction := utils.NewHttpInteraction("/ras/runs/" + reRun1Id, http.MethodDelete) + deleteRerun1Interaction.WriteHttpResponseFunc = successfulDeleteFunc + + deleteRerun2Interaction := utils.NewHttpInteraction("/ras/runs/" + reRun2Id, http.MethodDelete) + deleteRerun2Interaction.WriteHttpResponseFunc = successfulDeleteFunc + + interactions := []utils.HttpInteraction{ + getRunsInteraction, + deleteRunInteraction, + deleteRerun1Interaction, + deleteRerun2Interaction, + } + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockTimeService := utils.NewMockTimeService() + mockByteReader := utils.NewMockByteReader() + + // When... + err := RunsDelete( + runName, + console, + apiServerUrl, + apiClient, + mockTimeService, + mockByteReader) + + // Then... + assert.Nil(t, err, "RunsDelete returned an unexpected error") + assert.Empty(t, console.ReadText(), "The console was written to on a successful deletion, it should be empty") +} + +func TestDeleteNonExistantRunDisplaysError(t *testing.T) { + // Given... + nonExistantRunName := "runDoesNotExist123" + + // Create the expected HTTP interactions with the API server + getRunsInteraction := utils.NewHttpInteraction("/ras/runs", http.MethodGet) + getRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + WriteMockRasRunsResponse(t, writer, req, nonExistantRunName, []string{}) + } + + interactions := []utils.HttpInteraction{ getRunsInteraction } + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockTimeService := utils.NewMockTimeService() + mockByteReader := utils.NewMockByteReader() + + // When... + err := RunsDelete( + nonExistantRunName, + console, + apiServerUrl, + apiClient, + mockTimeService, + mockByteReader) + + // Then... + assert.NotNil(t, err, "RunsDelete did not return an error but it should have") + consoleOutputText := console.ReadText() + assert.Contains(t, consoleOutputText, nonExistantRunName) + assert.Contains(t, consoleOutputText, "GAL1163E") + assert.Contains(t, consoleOutputText, "The run named 'runDoesNotExist123' could not be deleted") +} + +func TestRunsDeleteFailsWithNoExplanationErrorPayloadGivesCorrectMessage(t *testing.T) { + // Given... + runName := "J20" + runId := "J234567890" + + // Create the mock run to be deleted + runToDelete := createMockRun(runName, runId) + runToDeleteBytes, _ := json.Marshal(runToDelete) + runToDeleteJson := string(runToDeleteBytes) + + // Create the expected HTTP interactions with the API server + getRunsInteraction := utils.NewHttpInteraction("/ras/runs", http.MethodGet) + getRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + WriteMockRasRunsResponse(t, writer, req, runName, []string{ runToDeleteJson }) + } + + deleteRunsInteraction := utils.NewHttpInteraction("/ras/runs/" + runId, http.MethodDelete) + deleteRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.WriteHeader(http.StatusInternalServerError) + } + + interactions := []utils.HttpInteraction{ + getRunsInteraction, + deleteRunsInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockTimeService := utils.NewMockTimeService() + mockByteReader := utils.NewMockByteReader() + + // When... + err := RunsDelete( + runName, + console, + apiServerUrl, + apiClient, + mockTimeService, + mockByteReader) + + // Then... + assert.NotNil(t, err, "RunsDelete did not return an error but it should have") + consoleText := console.ReadText() + assert.Contains(t, consoleText , runName) + assert.Contains(t, consoleText , "GAL1159E") +} + +func TestRunsDeleteFailsWithNonJsonContentTypeExplanationErrorPayloadGivesCorrectMessage(t *testing.T) { + // Given... + runName := "J20" + runId := "J234567890" + + // Create the mock run to be deleted + runToDelete := createMockRun(runName, runId) + runToDeleteBytes, _ := json.Marshal(runToDelete) + runToDeleteJson := string(runToDeleteBytes) + + // Create the expected HTTP interactions with the API server + getRunsInteraction := utils.NewHttpInteraction("/ras/runs", http.MethodGet) + getRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + WriteMockRasRunsResponse(t, writer, req, runName, []string{ runToDeleteJson }) + } + + deleteRunsInteraction := utils.NewHttpInteraction("/ras/runs/" + runId, http.MethodDelete) + deleteRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.WriteHeader(http.StatusInternalServerError) + writer.Header().Set("Content-Type", "application/notJsonOnPurpose") + writer.Write([]byte("something not json but non-zero-length.")) + } + + interactions := []utils.HttpInteraction{ + getRunsInteraction, + deleteRunsInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockTimeService := utils.NewMockTimeService() + mockByteReader := utils.NewMockByteReader() + + // When... + err := RunsDelete( + runName, + console, + apiServerUrl, + apiClient, + mockTimeService, + mockByteReader) + + // Then... + assert.NotNil(t, err, "RunsDelete did not return an error but it should have") + consoleText := console.ReadText() + assert.Contains(t, consoleText, runName) + assert.Contains(t, consoleText, strconv.Itoa(http.StatusInternalServerError)) + assert.Contains(t, consoleText, "GAL1164E") + assert.Contains(t, consoleText, "Error details from the server are not in the json format") +} + +func TestRunsDeleteFailsWithBadlyFormedJsonContentExplanationErrorPayloadGivesCorrectMessage(t *testing.T) { + // Given... + runName := "J20" + runId := "J234567890" + + // Create the mock run to be deleted + runToDelete := createMockRun(runName, runId) + runToDeleteBytes, _ := json.Marshal(runToDelete) + runToDeleteJson := string(runToDeleteBytes) + + // Create the expected HTTP interactions with the API server + getRunsInteraction := utils.NewHttpInteraction("/ras/runs", http.MethodGet) + getRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + WriteMockRasRunsResponse(t, writer, req, runName, []string{ runToDeleteJson }) + } + + deleteRunsInteraction := utils.NewHttpInteraction("/ras/runs/" + runId, http.MethodDelete) + deleteRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte(`{ "this": "isBadJson because it doesnt end in a close braces" `)) + } + + interactions := []utils.HttpInteraction{ + getRunsInteraction, + deleteRunsInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockTimeService := utils.NewMockTimeService() + mockByteReader := utils.NewMockByteReader() + + // When... + err := RunsDelete( + runName, + console, + apiServerUrl, + apiClient, + mockTimeService, + mockByteReader) + + // Then... + assert.NotNil(t, err, "RunsDelete did not return an error but it should have") + consoleText := console.ReadText() + assert.Contains(t, consoleText, runName) + assert.Contains(t, consoleText, strconv.Itoa(http.StatusInternalServerError)) + assert.Contains(t, consoleText, "GAL1161E") + assert.Contains(t, consoleText, "Error details from the server are not in a valid json format") + assert.Contains(t, consoleText, "Cause: 'unexpected end of JSON input'") +} + +func TestRunsDeleteFailsWithValidErrorResponsePayloadGivesCorrectMessage(t *testing.T) { + // Given... + runName := "J20" + runId := "J234567890" + apiErrorCode := 5000 + apiErrorMessage := "this is an error from the API server" + + // Create the mock run to be deleted + runToDelete := createMockRun(runName, runId) + runToDeleteBytes, _ := json.Marshal(runToDelete) + runToDeleteJson := string(runToDeleteBytes) + + // Create the expected HTTP interactions with the API server + getRunsInteraction := utils.NewHttpInteraction("/ras/runs", http.MethodGet) + getRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + WriteMockRasRunsResponse(t, writer, req, runName, []string{ runToDeleteJson }) + } + + deleteRunsInteraction := utils.NewHttpInteraction("/ras/runs/" + runId, http.MethodDelete) + deleteRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(http.StatusInternalServerError) + + apiError := errors.GalasaAPIError{ + Code: apiErrorCode, + Message: apiErrorMessage, + } + apiErrorBytes, _ := json.Marshal(apiError) + writer.Write(apiErrorBytes) + } + + interactions := []utils.HttpInteraction{ + getRunsInteraction, + deleteRunsInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockTimeService := utils.NewMockTimeService() + mockByteReader := utils.NewMockByteReader() + + // When... + err := RunsDelete( + runName, + console, + apiServerUrl, + apiClient, + mockTimeService, + mockByteReader) + + // Then... + assert.NotNil(t, err, "RunsDelete did not return an error but it should have") + consoleText := console.ReadText() + assert.Contains(t, consoleText, runName) + assert.Contains(t, consoleText, strconv.Itoa(http.StatusInternalServerError)) + assert.Contains(t, consoleText, "GAL1162E") + assert.Contains(t, consoleText, apiErrorMessage) +} + + +func TestRunsDeleteFailsWithFailureToReadResponseBodyGivesCorrectMessage(t *testing.T) { + // Given... + runName := "J20" + runId := "J234567890" + + // Create the mock run to be deleted + runToDelete := createMockRun(runName, runId) + runToDeleteBytes, _ := json.Marshal(runToDelete) + runToDeleteJson := string(runToDeleteBytes) + + // Create the expected HTTP interactions with the API server + getRunsInteraction := utils.NewHttpInteraction("/ras/runs", http.MethodGet) + getRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + WriteMockRasRunsResponse(t, writer, req, runName, []string{ runToDeleteJson }) + } + + deleteRunsInteraction := utils.NewHttpInteraction("/ras/runs/" + runId, http.MethodDelete) + deleteRunsInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.Header().Set("Content-Type", "application/json") + writer.WriteHeader(http.StatusInternalServerError) + writer.Write([]byte(`{}`)) + } + + interactions := []utils.HttpInteraction{ + getRunsInteraction, + deleteRunsInteraction, + } + + server := utils.NewMockHttpServer(t, interactions) + defer server.Server.Close() + + console := utils.NewMockConsole() + apiServerUrl := server.Server.URL + apiClient := api.InitialiseAPI(apiServerUrl) + mockTimeService := utils.NewMockTimeService() + mockByteReader := utils.NewMockByteReaderAsMock(true) + + // When... + err := RunsDelete( + runName, + console, + apiServerUrl, + apiClient, + mockTimeService, + mockByteReader) + + // Then... + assert.NotNil(t, err, "RunsDelete returned an unexpected error") + consoleText := console.ReadText() + assert.Contains(t, consoleText, runName) + assert.Contains(t, consoleText, strconv.Itoa(http.StatusInternalServerError)) + assert.Contains(t, consoleText, "GAL1160E") + assert.Contains(t, consoleText, "GAL1160E") + assert.Contains(t, consoleText, "Error details from the server could not be read") +} diff --git a/pkg/runs/submitter.go b/pkg/runs/submitter.go index dc012e3e..d2af4c1c 100644 --- a/pkg/runs/submitter.go +++ b/pkg/runs/submitter.go @@ -186,7 +186,7 @@ func (submitter *Submitter) executeSubmitRuns( now := submitter.timeService.Now() if now.After(nextProgressReport) { //convert TestRun - displayInterrimProgressReport(readyRuns, submittedRuns, finishedRuns, lostRuns, throttle) + submitter.displayInterrimProgressReport(readyRuns, submittedRuns, finishedRuns, lostRuns, throttle) nextProgressReport = now.Add(progressReportInterval) } } @@ -206,7 +206,7 @@ func (submitter *Submitter) executeSubmitRuns( return finishedRuns, lostRuns, err } -func displayInterrimProgressReport(readyRuns []TestRun, +func (submitter *Submitter) displayInterrimProgressReport(readyRuns []TestRun, submittedRuns map[string]*TestRun, finishedRuns map[string]*TestRun, lostRuns map[string]*TestRun, @@ -217,17 +217,16 @@ func displayInterrimProgressReport(readyRuns []TestRun, finished := len(finishedRuns) lost := len(lostRuns) - fmt.Println("Progress report") + log.Println("Progress report") for runName, run := range submittedRuns { log.Printf("*** Run %v is currently %v - %v/%v/%v\n", runName, run.Status, run.Stream, run.Bundle, run.Class) - fmt.Printf("Run %v - %v/%v/%v\n", runName, run.Stream, run.Bundle, run.Class) } - fmt.Println("----------------------------------------------------------------------------") - fmt.Printf("Run status: Ready=%v, Submitted=%v, Finished=%v, Lost=%v\n", ready, submitted, finished, lost) - fmt.Printf("Throttle=%v\n", throttle) + log.Println("----------------------------------------------------------------------------") + log.Printf("Run status: Ready=%v, Submitted=%v, Finished=%v, Lost=%v\n", ready, submitted, finished, lost) + log.Printf("Throttle=%v\n", throttle) if finished > 0 { - displayTestRunResults(finishedRuns, lostRuns) + submitter.displayTestRunResults(finishedRuns, lostRuns) } } @@ -453,7 +452,7 @@ func (submitter *Submitter) createReports(params utils.RunsSubmitCmdValues, finishedRuns map[string]*TestRun, lostRuns map[string]*TestRun) error { //convert TestRun tests into formattable data - displayTestRunResults(finishedRuns, lostRuns) + submitter.displayTestRunResults(finishedRuns, lostRuns) var err error if params.ReportYamlFilename != "" { @@ -475,7 +474,7 @@ func (submitter *Submitter) createReports(params utils.RunsSubmitCmdValues, return err } -func displayTestRunResults(finishedRuns map[string]*TestRun, lostRuns map[string]*TestRun) { +func (submitter *Submitter) displayTestRunResults(finishedRuns map[string]*TestRun, lostRuns map[string]*TestRun) { var formatter = runsformatter.NewSummaryFormatter() var err error var outputText string @@ -483,7 +482,7 @@ func displayTestRunResults(finishedRuns map[string]*TestRun, lostRuns map[string formattableTest := FormattableTestFromTestRun(finishedRuns, lostRuns) outputText, err = formatter.FormatRuns(formattableTest) if err == nil { - print(outputText) + submitter.console.WriteString(outputText) } } diff --git a/pkg/runs/submitter_test.go b/pkg/runs/submitter_test.go index b10a92cb..a4785929 100644 --- a/pkg/runs/submitter_test.go +++ b/pkg/runs/submitter_test.go @@ -466,7 +466,7 @@ func TestLocalLaunchCanUseAPortfolioOk(t *testing.T) { assert.Equal(t, obrName, launchesRecorded[0].ObrFromPortfolio) assert.Equal(t, bundleName+"/"+className, launchesRecorded[0].ClassName) } - + assert.Contains(t, console.ReadText(), bundleName + "/" + className) } func TestSubmitRunwithGherkinFile(t *testing.T) { diff --git a/pkg/spi/byteReader.go b/pkg/spi/byteReader.go new file mode 100644 index 00000000..2c6a22db --- /dev/null +++ b/pkg/spi/byteReader.go @@ -0,0 +1,13 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package spi + +import "io" + +// An interface to allow for mocking out "io" package reading-related methods +type ByteReader interface { + ReadAll(reader io.Reader) ([]byte, error) +} diff --git a/pkg/spi/factory.go b/pkg/spi/factory.go index 54084ea2..5ef9b5ae 100644 --- a/pkg/spi/factory.go +++ b/pkg/spi/factory.go @@ -24,4 +24,5 @@ type Factory interface { GetStdErrConsole() Console GetTimeService() TimeService GetAuthenticator(apiServerUrl string, galasaHome GalasaHome) Authenticator + GetByteReader() ByteReader } diff --git a/pkg/utils/byteReader.go b/pkg/utils/byteReader.go new file mode 100644 index 00000000..5a8d22be --- /dev/null +++ b/pkg/utils/byteReader.go @@ -0,0 +1,24 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package utils + +import ( + "io" + + "github.com/galasa-dev/cli/pkg/spi" +) + +// Implementation of a byte reader to allow mocking out methods from the io package +type ByteReaderImpl struct { +} + +func NewByteReader() spi.ByteReader { + return new(ByteReaderImpl) +} + +func (*ByteReaderImpl) ReadAll(reader io.Reader) ([]byte, error) { + return io.ReadAll(reader) +} diff --git a/pkg/utils/byteReaderMock.go b/pkg/utils/byteReaderMock.go new file mode 100644 index 00000000..7c65c206 --- /dev/null +++ b/pkg/utils/byteReaderMock.go @@ -0,0 +1,39 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package utils + +import ( + "errors" + "io" + + "github.com/galasa-dev/cli/pkg/spi" +) + +// Mock implementation of a byte reader to allow for simulating failed read operations +type MockByteReader struct { + throwReadError bool +} + +func NewMockByteReaderAsMock(throwReadError bool) *MockByteReader { + return &MockByteReader{ + throwReadError: throwReadError, + } +} + +func NewMockByteReader() spi.ByteReader { + return NewMockByteReaderAsMock(false) +} + +func (mockReader *MockByteReader) ReadAll(reader io.Reader) ([]byte, error) { + var err error + var bytes []byte + if mockReader.throwReadError { + err = errors.New("simulating a read failure") + } else { + bytes, err = io.ReadAll(reader) + } + return bytes, err +} diff --git a/pkg/utils/factoryMock.go b/pkg/utils/factoryMock.go index 56b043d2..bb1443e0 100644 --- a/pkg/utils/factoryMock.go +++ b/pkg/utils/factoryMock.go @@ -18,6 +18,7 @@ type MockFactory struct { StdErrConsole spi.Console TimeService spi.TimeService Authenticator spi.Authenticator + ByteReader spi.ByteReader } func NewMockFactory() *MockFactory { @@ -72,3 +73,10 @@ func (factory *MockFactory) GetAuthenticator(apiServerUrl string, galasaHome spi } return factory.Authenticator } + +func (factory *MockFactory) GetByteReader() spi.ByteReader { + if factory.ByteReader == nil { + factory.ByteReader = NewMockByteReader() + } + return factory.ByteReader +} diff --git a/pkg/utils/httpInteractionMock.go b/pkg/utils/httpInteractionMock.go new file mode 100644 index 00000000..6bb754fe --- /dev/null +++ b/pkg/utils/httpInteractionMock.go @@ -0,0 +1,74 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ +package utils + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +// The implementation of a HTTP interaction that allows unit tests to define +// interactions with the Galasa API server, with methods to validate requests +// and a lambda to write HTTP responses (which can be overridden as desired) +type HttpInteraction struct { + ExpectedPath string + ExpectedHttpMethod string + + // An override-able function to write a HTTP response for this interaction + WriteHttpResponseFunc func(writer http.ResponseWriter, req *http.Request) +} + +func NewHttpInteraction(expectedPath string, expectedHttpMethod string) HttpInteraction { + httpInteraction := HttpInteraction{ + ExpectedPath: expectedPath, + ExpectedHttpMethod: expectedHttpMethod, + } + + // Set a basic implementation of the lambda to write a default response, which can be overridden by tests + httpInteraction.WriteHttpResponseFunc = func(writer http.ResponseWriter, req *http.Request) { + writer.WriteHeader(http.StatusOK) + } + + return httpInteraction +} + +func (interaction *HttpInteraction) ValidateRequest(t *testing.T, req *http.Request) { + assert.NotEmpty(t, req.Header.Get("ClientApiVersion")) + assert.Equal(t, interaction.ExpectedHttpMethod, req.Method, "Actual HTTP request method did not match the expected method") + assert.Equal(t, interaction.ExpectedPath, req.URL.Path, "Actual request path did not match the expected path") +} + +//----------------------------------------------------------------------------- +// Wrapper of a mock HTTP server that uses HTTP interactions to handle requests +//----------------------------------------------------------------------------- +type MockHttpServer struct { + CurrentInteractionIndex int + Server *httptest.Server +} + +func NewMockHttpServer(t *testing.T, interactions []HttpInteraction) MockHttpServer { + mockHttpServer := MockHttpServer{} + mockHttpServer.CurrentInteractionIndex = 0 + + mockHttpServer.Server = httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) { + + currentInteractionIndex := &mockHttpServer.CurrentInteractionIndex + if *currentInteractionIndex >= len(interactions) { + assert.Fail(t, "Mock server received an unexpected request to '%s' when it should not have", req.URL.Path) + } else { + currentInteraction := interactions[*currentInteractionIndex] + currentInteraction.ValidateRequest(t, req) + currentInteraction.WriteHttpResponseFunc(writer, req) + + // The next request to the server should get the next interaction, so advance the index by one + *currentInteractionIndex++ + } + })) + return mockHttpServer +} diff --git a/pkg/utils/java_testUtils.go b/pkg/utils/java_test_fixtures.go similarity index 100% rename from pkg/utils/java_testUtils.go rename to pkg/utils/java_test_fixtures.go diff --git a/test-scripts/runs-tests.sh b/test-scripts/runs-tests.sh index 83539996..104b21ac 100755 --- a/test-scripts/runs-tests.sh +++ b/test-scripts/runs-tests.sh @@ -158,8 +158,6 @@ function runs_download_check_folder_names_during_test_run { # checks the folder names are correct with timestamps where appropriate h2 "Performing runs download while test is running..." - run_name=$1 - mkdir -p ${BASEDIR}/temp cd ${BASEDIR}/temp @@ -186,7 +184,7 @@ function runs_download_check_folder_names_during_test_run { cd ${BASEDIR}/temp - log_file="runs-submit-output.txt" + log_file="runs-submit-output-for-download.txt" cmd="${ORIGINAL_DIR}/bin/${binary} runs submit \ --bootstrap ${bootstrap} \ @@ -523,22 +521,23 @@ function get_result_with_runname { cd ${BASEDIR}/temp # Get the RunName from the output of galasactl runs submit + # The output of runs submit should look like: + # submitted-time(UTC) name requestor status result test-name + # 2024-09-05 12:45:33 C9955 galasa building Passed inttests/dev.galasa.inttests/dev.galasa.inttests.core.local.CoreLocalJava11Ubuntu + # + # Total:1 Passed:1 - # Gets the line from the last part of the output stream the RunName is found in - cat runs-submit-output.txt | grep -o "Run.*-" | tail -1 > line.txt - - # Get just the RunName from the line. - # There is a line in the output like this: - # Run C6967 - inttests/dev.galasa.inttests/dev.galasa.inttests.core.local.CoreLocalJava11Ubuntu - # Environment failure of the test results in "C6976(EnvFail)" ... so the '('...')' part needs removing also. - sed 's/Run //; s/ -//; s/[(].*[)]//;' line.txt > runname.txt - runname=$(cat runname.txt) + # Gets the run name from the second line of the runs submit output (after the headers). + # The run name should be the third field, after the date and time fields. + runname=$(cat runs-submit-output.txt | sed -n "2{p;q;}" | cut -f3 -d' ') if [[ "$runname" == "" ]]; then error "Run name not captured from previous run launch." exit 1 fi + info "Run name is: ${runname}" + cmd="${ORIGINAL_DIR}/bin/${binary} runs get \ --name ${runname} \ --bootstrap ${bootstrap} \ @@ -975,6 +974,79 @@ function launch_test_from_unknown_portfolio { success "Unknown portfolio could not be read. galasactl reported this error correctly." } +#-------------------------------------------------------------------------- +function runs_delete_check_run_can_be_deleted { + run_name=$1 + + h2 "Attempting to delete the run named '${run_name}' using runs delete..." + + mkdir -p ${BASEDIR}/temp + cd ${BASEDIR}/temp + + cmd="${ORIGINAL_DIR}/bin/${binary} runs delete \ + --name ${run_name} \ + --bootstrap ${bootstrap}" + + info "Command is: $cmd" + + # We expect a return code of '0' because the run should have been deleted successfully. + $cmd + rc=$? + if [[ "${rc}" != "0" ]]; then + error "Failed to delete run '${run_name}'" + exit 1 + fi + + h2 "Checking that the run '${run_name}' no longer exists" + + cmd="${ORIGINAL_DIR}/bin/${binary} runs get \ + --name ${run_name} \ + --bootstrap ${bootstrap}" + + output_file="runs-delete-output.txt" + set -o pipefail + $cmd | tee $output_file | grep -q "Total:0" + + # We expect a return code of '0' because there should be no runs with the given run name anymore. + rc=$? + if [[ "${rc}" != "0" ]]; then + error "Failed when checking if run '${run_name}' has been deleted. The run still exists when it should not." + exit 1 + fi + + success "galasactl runs delete was able to delete an existing run OK." +} + +#-------------------------------------------------------------------------- +function runs_delete_non_existant_run_returns_error { + run_name="NonExistantRun123" + + h2 "Attempting to delete the non-existant run named '${run_name}' using runs delete..." + + mkdir -p ${BASEDIR}/temp + cd ${BASEDIR}/temp + + cmd="${ORIGINAL_DIR}/bin/${binary} runs delete \ + --name ${run_name} \ + --bootstrap ${bootstrap}" + + info "Command is: $cmd" + + output_file="runs-delete-output.txt" + set -o pipefail + $cmd | tee $output_file + + # We expect a return code of '1' because the run does not exist and an error should be reported. + rc=$? + if [[ "${rc}" != "1" ]]; then + error "Failed to return an error when attempting to delete non-existant run '${run_name}'" + exit 1 + fi + + success "galasactl runs delete correctly reported an error when attempting to delete a non-existant run." +} + +#-------------------------------------------------------------------------- function test_runs_commands { # Launch test on ecosystem without a portfolio ... launch_test_on_ecosystem_without_portfolio @@ -1016,6 +1088,10 @@ function test_runs_commands { # Attempt to cancel an active run... # Temporarily commented out as failing and will block CLI builds. # runs_cancel_check_test_is_finished_and_cancelled + + # Attempt to delete a run... + runs_delete_check_run_can_be_deleted $RUN_NAME + runs_delete_non_existant_run_returns_error } From 1097a3cb560cfeed126a9273484638172cef7288 Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Mon, 16 Sep 2024 11:03:13 +0100 Subject: [PATCH 15/30] Install ca certs into galasactl images Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui --- dockerfiles/dockerfile.galasactl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dockerfiles/dockerfile.galasactl b/dockerfiles/dockerfile.galasactl index 96f6d5c5..9363889e 100644 --- a/dockerfiles/dockerfile.galasactl +++ b/dockerfiles/dockerfile.galasactl @@ -1,5 +1,7 @@ FROM harbor.galasa.dev/docker_proxy_cache/library/ubuntu:20.04 +RUN apt-get install -y ca-certificates + ARG platform RUN addgroup galasa && \ From 874ef86f22c059cdd916b7f841538f61e1b8122e Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Mon, 16 Sep 2024 11:22:21 +0100 Subject: [PATCH 16/30] change based on comments Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui --- dockerfiles/dockerfile.galasactl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dockerfiles/dockerfile.galasactl b/dockerfiles/dockerfile.galasactl index 9363889e..e13195e2 100644 --- a/dockerfiles/dockerfile.galasactl +++ b/dockerfiles/dockerfile.galasactl @@ -1,6 +1,7 @@ FROM harbor.galasa.dev/docker_proxy_cache/library/ubuntu:20.04 -RUN apt-get install -y ca-certificates +RUN apt-get update \ + && apt-get install -y ca-certificates ARG platform From 2876b321b99adba4478b7bd962f41a81c54303f2 Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Mon, 16 Sep 2024 13:13:36 +0100 Subject: [PATCH 17/30] Remove isolated trigger - now in Tekton pipeline Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui --- .github/workflows/build.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 31143a61..0fbbd922 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -184,14 +184,6 @@ jobs: run: | docker run --env ARGOCD_AUTH_TOKEN=${{ env.ARGOCD_AUTH_TOKEN }} --rm -v ${{ github.workspace }}:/var/workspace ghcr.io/galasa-dev/argocdcli:main app wait ${{ env.BRANCH }}-cli --resource apps:Deployment:cli-${{ env.BRANCH }} --health --server argocd.galasa.dev - trigger-isolated-workflow: - name: Trigger Isolated workflow - runs-on: ubuntu-latest - needs: build-cli - - steps: - - name: Trigger Isolated workflow dispatch event with GitHub CLI - env: - GH_TOKEN: ${{ secrets.GALASA_TEAM_GITHUB_TOKEN }} - run: | - gh workflow run build.yaml --repo https://github.com/galasa-dev/isolated --ref ${{ env.BRANCH }} \ No newline at end of file + - name: Attempt to trigger test-cli-ecosystem-commands Tekton pipeline + run: | + echo "The Tekton pipeline test-cli-ecosystem-commands should be triggered in the next 2-minutes - check the Tekton dashboard" \ No newline at end of file From 6b8c585cb265aad0006861649f5c332dca5e2ef1 Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Mon, 16 Sep 2024 13:59:57 +0100 Subject: [PATCH 18/30] testing script adjustments Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui --- test-galasactl-ecosystem.sh | 7 ++- .../calculate-galasactl-executables.sh | 63 +++++++++++++------ test-scripts/properties-tests.sh | 52 +++++++-------- test-scripts/resources-tests.sh | 24 +++---- test-scripts/runs-tests.sh | 54 ++++++++-------- 5 files changed, 112 insertions(+), 88 deletions(-) diff --git a/test-galasactl-ecosystem.sh b/test-galasactl-ecosystem.sh index 4aa9872d..d4ec0350 100755 --- a/test-galasactl-ecosystem.sh +++ b/test-galasactl-ecosystem.sh @@ -8,8 +8,8 @@ echo "Running script test-galasactl-ecosystem.sh" # This script can be ran locally or executed in a pipeline to test the various built binaries of galasactl +# This script can also be ran in a pipeline to test a published binary of galasactl in GHCR built by the GitHub workflow # This script tests the 'galasactl' commands against the ecosystem -# Pre-requesite: the CLI must have been built first so the binaries are present in the /bin directory # Where is this script executing from ? @@ -101,6 +101,7 @@ export GALASA_TEST_RUN_GET_EXPECTED_NUMBER_ARTIFACT_RUNNING_COUNT="10" CALLED_BY_MAIN="true" # Bootstrap is in the $bootstrap variable. + source ${BASEDIR}/test-scripts/calculate-galasactl-executables.sh calculate_galasactl_executable @@ -115,6 +116,6 @@ resources_tests # Test the hybrid configuration where the local test runs locally, but # draws it's CPS properties from a remote ecosystem via a REST extension. -source ${BASEDIR}/test-scripts/test-local-run-remote-cps.sh -test_local_run_remote_cps +# source ${BASEDIR}/test-scripts/test-local-run-remote-cps.sh +# test_local_run_remote_cps diff --git a/test-scripts/calculate-galasactl-executables.sh b/test-scripts/calculate-galasactl-executables.sh index ad35ec56..76d11c76 100644 --- a/test-scripts/calculate-galasactl-executables.sh +++ b/test-scripts/calculate-galasactl-executables.sh @@ -6,6 +6,10 @@ # SPDX-License-Identifier: EPL-2.0 # +# Where is this script executing from ? +BASEDIR=$(dirname "$0");pushd $BASEDIR 2>&1 >> /dev/null ;BASEDIR=$(pwd);popd 2>&1 >> /dev/null +export ORIGINAL_DIR=$(pwd) +cd "${BASEDIR}" #-------------------------------------------------------------------------- # @@ -81,26 +85,45 @@ export GALASA_TEST_RUN_GET_EXPECTED_NUMBER_ARTIFACT_RUNNING_COUNT="10" function calculate_galasactl_executable { h2 "Calculate the name of the galasactl executable for this machine/os" - raw_os=$(uname -s) # eg: "Darwin" - os="" - case $raw_os in - Darwin*) - os="darwin" - ;; - Windows*) - os="windows" - ;; - Linux*) - os="linux" - ;; - *) - error "Failed to recognise which operating system is in use. $raw_os" - exit 1 - esac - - architecture=$(uname -m) + # Determine if the /bin directory exists i.e. if the script is testing + # a built binary or if it is testing a published Docker image in GHCR + # If testing a built binary, it will use it from the /bin, otherwise + # `galasactl` will be used as it is installed on the path within the image. + path_to_bin="${BASEDIR}/bin" + + # Check if the /bin directory exists + if [ -d "$directory_path" ]; then + echo "The /bin directory exists so assume the script is testing a locally built binary." + + raw_os=$(uname -s) # eg: "Darwin" + os="" + case $raw_os in + Darwin*) + os="darwin" + ;; + Windows*) + os="windows" + ;; + Linux*) + os="linux" + ;; + *) + error "Failed to recognise which operating system is in use. $raw_os" + exit 1 + esac + + architecture=$(uname -m) + + export binary="galasactl-${os}-${architecture}" + info "galasactl binary is ${binary}" + + export BINARY_LOCATION="${ORIGINAL_DIR}/bin/${binary}" + info "binary location is ${BINARY_LOCATION}" + else + echo "The /bin directory does not exist so assume the script is testing a published image in GHCR." + + export BINARY_LOCATION="galasactl" + fi - export binary="galasactl-${os}-${architecture}" - info "galasactl binary is ${binary}" success "OK" } \ No newline at end of file diff --git a/test-scripts/properties-tests.sh b/test-scripts/properties-tests.sh index d9e5600d..dd21c289 100755 --- a/test-scripts/properties-tests.sh +++ b/test-scripts/properties-tests.sh @@ -105,7 +105,7 @@ function properties_create { prop_name="properties.test.name.value.$PROP_NUM" - cmd="$ORIGINAL_DIR/bin/${binary} properties set --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties set --namespace ecosystemtest \ --name $prop_name \ --value test-value \ --bootstrap $bootstrap \ @@ -122,7 +122,7 @@ function properties_create { fi # check that property has been created - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties get --namespace ecosystemtest \ --name $prop_name \ --bootstrap $bootstrap \ --log -" @@ -167,7 +167,7 @@ function properties_update { prop_name="properties.test.name.value.$PROP_NUM" - cmd="$ORIGINAL_DIR/bin/${binary} properties set --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties set --namespace ecosystemtest \ --name $prop_name \ --value updated-value \ --bootstrap $bootstrap \ @@ -184,7 +184,7 @@ function properties_update { fi # check that property has been updated - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties get --namespace ecosystemtest \ --name $prop_name \ --bootstrap $bootstrap \ --log -" @@ -219,7 +219,7 @@ function properties_delete { prop_name="properties.test.name.value.$PROP_NUM" - cmd="$ORIGINAL_DIR/bin/${binary} properties delete --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties delete --namespace ecosystemtest \ --name $prop_name \ --bootstrap $bootstrap \ --log -" @@ -235,7 +235,7 @@ function properties_delete { fi # check that property has been deleted - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties get --namespace ecosystemtest \ --name $prop_name \ --bootstrap $bootstrap \ --log -" @@ -268,7 +268,7 @@ function properties_delete_invalid_property { set -o pipefail # Fail everything if anything in the pipeline fails. Else we are just checking the 'tee' return code. - cmd="$ORIGINAL_DIR/bin/${binary} properties delete --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties delete --namespace ecosystemtest \ --name this.property.shouldnt.exist \ --bootstrap $bootstrap \ --log -" @@ -292,7 +292,7 @@ function properties_delete_without_name { set -o pipefail # Fail everything if anything in the pipeline fails. Else we are just checking the 'tee' return code. - cmd="$ORIGINAL_DIR/bin/${binary} properties delete --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties delete --namespace ecosystemtest \ --bootstrap $bootstrap \ --log -" @@ -314,7 +314,7 @@ function properties_set_with_name_without_value { prop_name="properties.test.name.$PROP_NUM" - cmd="$ORIGINAL_DIR/bin/${binary} properties set --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties set --namespace ecosystemtest \ --name $prop_name \ --bootstrap $bootstrap \ --log -" @@ -335,7 +335,7 @@ function properties_set_with_name_without_value { function properties_set_without_name_with_value { h2 "Performing properties set without name parameter and with value parameter..." - cmd="$ORIGINAL_DIR/bin/${binary} properties set --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties set --namespace ecosystemtest \ --value random-arbitrary-value \ --bootstrap $bootstrap \ --log -" @@ -359,7 +359,7 @@ function properties_set_without_name_and_value { prop_name="properties.test.name.value.$PROP_NUM" - cmd="$ORIGINAL_DIR/bin/${binary} properties set --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties set --namespace ecosystemtest \ --bootstrap $bootstrap \ --log -" @@ -378,7 +378,7 @@ function properties_set_without_name_and_value { #-------------------------------------------------------------------------- function properties_get_setup { h2 "Performing setup for subsequent properties get commands." - cmd="$ORIGINAL_DIR/bin/${binary} properties set --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties set --namespace ecosystemtest \ --name get.test.property \ --value this-shouldnt-be-deleted \ --bootstrap $bootstrap \ @@ -390,7 +390,7 @@ function properties_get_setup { function properties_get_with_namespace { h2 "Performing properties get with only namespace used, expecting list of properties..." - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties get --namespace ecosystemtest \ --bootstrap $bootstrap \ --log -" @@ -420,7 +420,7 @@ function properties_get_with_namespace { function properties_get_with_name { h2 "Performing properties get with only name used, expecting list of properties..." - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties get --namespace ecosystemtest \ --name get.test.property \ --bootstrap $bootstrap \ --log -" @@ -451,7 +451,7 @@ function properties_get_with_name { function properties_get_with_prefix { h2 "Performing properties get with prefix used, expecting list of properties..." - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties get --namespace ecosystemtest \ --prefix get \ --bootstrap $bootstrap \ --log -" @@ -482,7 +482,7 @@ function properties_get_with_prefix { function properties_get_with_suffix { h2 "Performing properties get with suffix used, expecting list of properties..." - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties get --namespace ecosystemtest \ --suffix property \ --bootstrap $bootstrap \ --log -" @@ -513,7 +513,7 @@ function properties_get_with_suffix { function properties_get_with_infix { h2 "Performing properties get with infix used, expecting list of properties..." - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties get --namespace ecosystemtest \ --infix test \ --bootstrap $bootstrap \ --log -" @@ -544,7 +544,7 @@ function properties_get_with_infix { function properties_get_with_prefix_infix_and_suffix { h2 "Performing properties get with prefix, infix, and suffix used, expecting list of properties..." - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties get --namespace ecosystemtest \ --prefix get \ --suffix property \ --infix test \ @@ -577,7 +577,7 @@ function properties_get_with_prefix_infix_and_suffix { function properties_get_with_namespace_raw_format { h2 "Performing properties get with only namespace used, expecting list of properties..." - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties get --namespace ecosystemtest \ --bootstrap $bootstrap \ --format raw \ --log -" @@ -610,7 +610,7 @@ function properties_secure_namespace_set { prop_name="properties.secure.namespace" - cmd="$ORIGINAL_DIR/bin/${binary} properties set --namespace secure \ + cmd="${BINARY_LOCATION} properties set --namespace secure \ --name $prop_name \ --value dummy.value --bootstrap $bootstrap \ @@ -626,7 +626,7 @@ function properties_secure_namespace_set { fi # check that property resource has been created - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace secure \ + cmd="${BINARY_LOCATION} properties get --namespace secure \ --name $prop_name \ --bootstrap $bootstrap \ --log -" @@ -665,7 +665,7 @@ function properties_secure_namespace_delete { prop_name="properties.secure.namespace.test" - cmd="$ORIGINAL_DIR/bin/${binary} properties delete --namespace secure \ + cmd="${BINARY_LOCATION} properties delete --namespace secure \ --name $prop_name \ --bootstrap $bootstrap \ --log -" @@ -681,7 +681,7 @@ function properties_secure_namespace_delete { fi # check that property has been deleted - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace secure \ + cmd="${BINARY_LOCATION} properties get --namespace secure \ --name $prop_name \ --bootstrap $bootstrap \ --log -" @@ -719,7 +719,7 @@ function properties_secure_namespace_non_existent_prop_delete { prop_name="properties.test.name.value.$PROP_NUM" - cmd="$ORIGINAL_DIR/bin/${binary} properties delete --namespace secure \ + cmd="${BINARY_LOCATION} properties delete --namespace secure \ --name $prop_name \ --bootstrap $bootstrap \ --log -" @@ -735,7 +735,7 @@ function properties_secure_namespace_non_existent_prop_delete { fi # check that property has been deleted - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace secure \ + cmd="${BINARY_LOCATION} properties get --namespace secure \ --name $prop_name \ --bootstrap $bootstrap \ --log -" @@ -769,7 +769,7 @@ function properties_secure_namespace_non_existent_prop_delete { function properties_namespaces_get { h2 "Performing namespaces get, expecting a list of all namespaces in the cps..." - cmd="$ORIGINAL_DIR/bin/${binary} properties namespaces get \ + cmd="${BINARY_LOCATION} properties namespaces get \ --bootstrap $bootstrap \ --log -" diff --git a/test-scripts/resources-tests.sh b/test-scripts/resources-tests.sh index ba5c9ac3..eaa78fe2 100755 --- a/test-scripts/resources-tests.sh +++ b/test-scripts/resources-tests.sh @@ -118,7 +118,7 @@ data: value: $prop_value EOF - cmd="$ORIGINAL_DIR/bin/${binary} resources create \ + cmd="${BINARY_LOCATION} resources create \ --bootstrap $bootstrap \ -f $input_file \ --log -" @@ -133,7 +133,7 @@ EOF fi # check that property resource has been created - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties get --namespace ecosystemtest \ --name $prop_name \ --bootstrap $bootstrap \ --log -" @@ -187,7 +187,7 @@ data: value: $prop_value EOF - cmd="$ORIGINAL_DIR/bin/${binary} resources update \ + cmd="${BINARY_LOCATION} resources update \ --bootstrap $bootstrap \ -f $input_file \ --log -" @@ -202,7 +202,7 @@ EOF fi # check that property resource has been updated - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties get --namespace ecosystemtest \ --name $prop_name \ --bootstrap $bootstrap \ --log -" @@ -267,7 +267,7 @@ data: value: $prop_value_to_create EOF - cmd="$ORIGINAL_DIR/bin/${binary} resources apply \ + cmd="${BINARY_LOCATION} resources apply \ --bootstrap $bootstrap \ -f $input_file \ --log -" @@ -282,7 +282,7 @@ EOF fi # check that property resource has been applied - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties get --namespace ecosystemtest \ --infix properties.test \ --bootstrap $bootstrap \ --log -" @@ -347,7 +347,7 @@ data: value: $prop_value_to_create EOF - cmd="$ORIGINAL_DIR/bin/${binary} resources delete \ + cmd="${BINARY_LOCATION} resources delete \ --bootstrap $bootstrap \ -f $input_file \ --log -" @@ -363,7 +363,7 @@ EOF fi # check that property has been deleted - cmd="$ORIGINAL_DIR/bin/${binary} properties get --namespace ecosystemtest \ + cmd="${BINARY_LOCATION} properties get --namespace ecosystemtest \ --name $prop_name \ --bootstrap $bootstrap \ --log -" @@ -414,7 +414,7 @@ data: value: inexistent EOF - cmd="$ORIGINAL_DIR/bin/${binary} resources delete \ + cmd="${BINARY_LOCATION} resources delete \ --bootstrap $bootstrap \ -f $input_file \ --log -" @@ -453,7 +453,7 @@ data: value: EOF - cmd="$ORIGINAL_DIR/bin/${binary} resources create \ + cmd="${BINARY_LOCATION} resources create \ --bootstrap $bootstrap \ -f $input_file \ --log -" @@ -491,7 +491,7 @@ data: value: value EOF - cmd="$ORIGINAL_DIR/bin/${binary} resources create \ + cmd="${BINARY_LOCATION} resources create \ --bootstrap $bootstrap \ -f $input_file \ --log -" @@ -527,7 +527,7 @@ data: value: value EOF - cmd="$ORIGINAL_DIR/bin/${binary} resources create \ + cmd="${BINARY_LOCATION} resources create \ --bootstrap $bootstrap \ -f $input_file \ --log -" diff --git a/test-scripts/runs-tests.sh b/test-scripts/runs-tests.sh index 104b21ac..85b6c0cc 100755 --- a/test-scripts/runs-tests.sh +++ b/test-scripts/runs-tests.sh @@ -105,7 +105,7 @@ function launch_test_on_ecosystem_with_portfolio { mkdir -p ${BASEDIR}/temp cd ${BASEDIR}/temp - cmd="${ORIGINAL_DIR}/bin/${binary} runs prepare \ + cmd="${BINARY_LOCATION} runs prepare \ --bootstrap $bootstrap \ --stream inttests \ --portfolio portfolio.yaml \ @@ -127,7 +127,7 @@ function launch_test_on_ecosystem_with_portfolio { cd ${BASEDIR}/temp - cmd="${ORIGINAL_DIR}/bin/${binary} runs submit \ + cmd="${BINARY_LOCATION} runs submit \ --bootstrap ${bootstrap} \ --portfolio portfolio.yaml \ --throttle 1 \ @@ -162,7 +162,7 @@ function runs_download_check_folder_names_during_test_run { cd ${BASEDIR}/temp # Create the portfolio. - cmd="${ORIGINAL_DIR}/bin/${binary} runs prepare \ + cmd="${BINARY_LOCATION} runs prepare \ --bootstrap $bootstrap \ --stream inttests \ --portfolio portfolio.yaml \ @@ -186,7 +186,7 @@ function runs_download_check_folder_names_during_test_run { log_file="runs-submit-output-for-download.txt" - cmd="${ORIGINAL_DIR}/bin/${binary} runs submit \ + cmd="${BINARY_LOCATION} runs submit \ --bootstrap ${bootstrap} \ --portfolio portfolio.yaml \ --throttle 1 \ @@ -229,7 +229,7 @@ function runs_download_check_folder_names_during_test_run { info "Run name is $run_name" # Now download the test results which are available from the test which is being submitted in the background process. - cmd="${ORIGINAL_DIR}/bin/${binary} runs download \ + cmd="${BINARY_LOCATION} runs download \ --name ${run_name} \ --bootstrap ${bootstrap} \ --force" @@ -314,7 +314,7 @@ function runs_reset_check_retry_present { runs_submit_log_file="runs-submit-output-for-reset.txt" - cmd="${ORIGINAL_DIR}/bin/${binary} runs submit \ + cmd="${BINARY_LOCATION} runs submit \ --bootstrap $bootstrap \ --class dev.galasa.inttests/dev.galasa.inttests.core.local.CoreLocalJava11Ubuntu \ --stream inttests @@ -361,7 +361,7 @@ function runs_reset_check_retry_present { h2 "Now attempting to reset the run while it's running in the background process." - cmd="${ORIGINAL_DIR}/bin/${binary} runs reset \ + cmd="${BINARY_LOCATION} runs reset \ --name ${run_name} \ --bootstrap ${bootstrap}" @@ -373,7 +373,7 @@ function runs_reset_check_retry_present { runs_get_log_file="runs-get-output-for-reset.txt" # Now poll runs get to check when the test is finished - cmd="${ORIGINAL_DIR}/bin/${binary} runs get \ + cmd="${BINARY_LOCATION} runs get \ --name ${run_name} \ --bootstrap ${bootstrap}" @@ -424,7 +424,7 @@ function runs_cancel_check_test_is_finished_and_cancelled { runs_submit_log_file="runs-submit-output-for-cancel.txt" - cmd="${ORIGINAL_DIR}/bin/${binary} runs submit \ + cmd="${BINARY_LOCATION} runs submit \ --bootstrap $bootstrap \ --class dev.galasa.inttests/dev.galasa.inttests.core.local.CoreLocalJava11Ubuntu \ --stream inttests @@ -471,7 +471,7 @@ function runs_cancel_check_test_is_finished_and_cancelled { h2 "Now attempting to cancel the run while it's running in the background process." - cmd="${ORIGINAL_DIR}/bin/${binary} runs cancel \ + cmd="${BINARY_LOCATION} runs cancel \ --name ${run_name} \ --bootstrap ${bootstrap}" @@ -538,7 +538,7 @@ function get_result_with_runname { info "Run name is: ${runname}" - cmd="${ORIGINAL_DIR}/bin/${binary} runs get \ + cmd="${BINARY_LOCATION} runs get \ --name ${runname} \ --bootstrap ${bootstrap} \ --log -" @@ -567,7 +567,7 @@ function runs_get_check_summary_format_output { cd ${BASEDIR}/temp - cmd="${ORIGINAL_DIR}/bin/${binary} runs get \ + cmd="${BINARY_LOCATION} runs get \ --name ${run_name} \ --format summary \ --bootstrap ${bootstrap} " @@ -619,7 +619,7 @@ function runs_get_check_details_format_output { cd ${BASEDIR}/temp - cmd="${ORIGINAL_DIR}/bin/${binary} runs get \ + cmd="${BINARY_LOCATION} runs get \ --name ${run_name} \ --format details \ --bootstrap ${bootstrap} " @@ -673,7 +673,7 @@ function runs_get_check_raw_format_output { cd ${BASEDIR}/temp - cmd="${ORIGINAL_DIR}/bin/${binary} runs get \ + cmd="${BINARY_LOCATION} runs get \ --name ${run_name} \ --format raw \ --bootstrap ${bootstrap} " @@ -711,7 +711,7 @@ function runs_get_check_raw_format_output_with_from_and_to { cd ${BASEDIR}/temp - cmd="${ORIGINAL_DIR}/bin/${binary} runs get \ + cmd="${BINARY_LOCATION} runs get \ --age 1h:0h \ --format raw \ --bootstrap ${bootstrap}" @@ -742,7 +742,7 @@ function runs_get_check_raw_format_output_with_just_from { cd ${BASEDIR}/temp - cmd="${ORIGINAL_DIR}/bin/${binary} runs get \ + cmd="${BINARY_LOCATION} runs get \ --age 1d \ --format raw \ --bootstrap ${bootstrap}" @@ -769,7 +769,7 @@ function runs_get_check_raw_format_output_with_just_from { function runs_get_check_raw_format_output_with_no_runname_and_no_age_param { h2 "Performing runs get with raw format providing no run name and no age..." - cmd="${ORIGINAL_DIR}/bin/${binary} runs get \ + cmd="${BINARY_LOCATION} runs get \ --format raw \ --bootstrap ${bootstrap}" @@ -791,7 +791,7 @@ function runs_get_check_raw_format_output_with_invalid_age_param { h2 "Performing runs get with raw format providing an age parameter with an invalid value..." - cmd="${ORIGINAL_DIR}/bin/${binary} runs get \ + cmd="${BINARY_LOCATION} runs get \ --age 1y:1m \ --format raw \ --bootstrap ${bootstrap}" @@ -814,7 +814,7 @@ function runs_get_check_raw_format_output_with_older_to_than_from_age { h2 "Performing runs get with raw format providing an age parameter with an older to than from age..." - cmd="${ORIGINAL_DIR}/bin/${binary} runs get \ + cmd="${BINARY_LOCATION} runs get \ --age 1h:1d \ --format raw \ --bootstrap ${bootstrap}" @@ -839,7 +839,7 @@ function runs_get_check_requestor_parameter { cd ${BASEDIR}/temp - cmd="${ORIGINAL_DIR}/bin/${binary} runs get \ + cmd="${BINARY_LOCATION} runs get \ --age 1d \ --requestor $requestor \ --format details \ @@ -869,7 +869,7 @@ function runs_get_check_result_parameter { cd ${BASEDIR}/temp - cmd="${ORIGINAL_DIR}/bin/${binary} runs get \ + cmd="${BINARY_LOCATION} runs get \ --age 1d \ --result ${result} \ --format details \ @@ -898,7 +898,7 @@ function launch_test_on_ecosystem_without_portfolio { cd ${BASEDIR}/temp - cmd="${ORIGINAL_DIR}/bin/${binary} runs submit \ + cmd="${BINARY_LOCATION} runs submit \ --bootstrap $bootstrap \ --class dev.galasa.inttests/dev.galasa.inttests.core.local.CoreLocalJava11Ubuntu \ --stream inttests @@ -928,7 +928,7 @@ function create_portfolio_with_unknown_test { cd ${BASEDIR}/temp - cmd="${ORIGINAL_DIR}/bin/${binary} runs prepare \ + cmd="${BINARY_LOCATION} runs prepare \ --bootstrap $bootstrap \ --stream inttests \ --portfolio unknown-portfolio.yaml \ @@ -953,7 +953,7 @@ function launch_test_from_unknown_portfolio { cd ${BASEDIR}/temp - cmd="${ORIGINAL_DIR}/bin/${binary} runs submit \ + cmd="${BINARY_LOCATION} runs submit \ --bootstrap $bootstrap \ --portfolio unknown-portfolio.yaml \ --throttle 1 \ @@ -983,7 +983,7 @@ function runs_delete_check_run_can_be_deleted { mkdir -p ${BASEDIR}/temp cd ${BASEDIR}/temp - cmd="${ORIGINAL_DIR}/bin/${binary} runs delete \ + cmd="${BINARY_LOCATION} runs delete \ --name ${run_name} \ --bootstrap ${bootstrap}" @@ -999,7 +999,7 @@ function runs_delete_check_run_can_be_deleted { h2 "Checking that the run '${run_name}' no longer exists" - cmd="${ORIGINAL_DIR}/bin/${binary} runs get \ + cmd="${BINARY_LOCATION} runs get \ --name ${run_name} \ --bootstrap ${bootstrap}" @@ -1026,7 +1026,7 @@ function runs_delete_non_existant_run_returns_error { mkdir -p ${BASEDIR}/temp cd ${BASEDIR}/temp - cmd="${ORIGINAL_DIR}/bin/${binary} runs delete \ + cmd="${BINARY_LOCATION} runs delete \ --name ${run_name} \ --bootstrap ${bootstrap}" From 3aef41c77372210eb32e3f888c12f7f55daf9cbd Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Mon, 16 Sep 2024 14:32:34 +0100 Subject: [PATCH 19/30] test adjusments to remote cps script Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui --- test-scripts/test-local-run-remote-cps.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test-scripts/test-local-run-remote-cps.sh b/test-scripts/test-local-run-remote-cps.sh index 19d8a617..3d0d2d93 100755 --- a/test-scripts/test-local-run-remote-cps.sh +++ b/test-scripts/test-local-run-remote-cps.sh @@ -127,7 +127,7 @@ function remote_cps_run_tests { export REMOTE_MAVEN=https://development.galasa.dev/main/maven-repo/obr/ baseName="dev.galasa.cps.rest.test" - cmd="${PROJECT_DIR}/bin/${binary} runs submit local --obr mvn:${baseName}/${baseName}.obr/0.0.1-SNAPSHOT/obr \ + cmd="${BINARY_LOCATION} runs submit local --obr mvn:${baseName}/${baseName}.obr/0.0.1-SNAPSHOT/obr \ --class ${baseName}.http/${baseName}.http.TestHttp \ --bootstrap file://$TEMP_DIR/home/bootstrap.properties \ --remoteMaven ${REMOTE_MAVEN} \ @@ -157,7 +157,7 @@ function build_galasa_home { info "GALASA_HOME is $GALASA_HOME" - cmd="${PROJECT_DIR}/bin/${binary} local init --development" + cmd="${BINARY_LOCATION} local init --development" info "Command is $cmd" $cmd rc=$? ; if [[ "${rc}" != "0" ]]; then error "Failed to build galasa home. Return code: ${rc}" ; exit 1 ; fi @@ -185,7 +185,7 @@ EOF function login_to_ecosystem { h2 "Logging into the ecosystem" info "GALASA_BOOTSTRAP is $GALASA_BOOTSTRAP" - cmd="${PROJECT_DIR}/bin/${binary} auth login --log -" + cmd="${BINARY_LOCATION} auth login --log -" info "Command is $cmd" $cmd rc=$? ; if [[ "${rc}" != "0" ]]; then error "Failed to login to the galasa server. Return code: ${rc}" ; exit 1 ; fi @@ -195,7 +195,7 @@ function login_to_ecosystem { function logout_of_ecosystem { h2 "Logging out of the ecosystem" info "GALASA_BOOTSTRAP is $GALASA_BOOTSTRAP" - cmd="${PROJECT_DIR}/bin/${binary} auth logout" + cmd="${BINARY_LOCATION} auth logout" info "Command is $cmd" $cmd rc=$? ; if [[ "${rc}" != "0" ]]; then error "Failed to logout to the galasa server. Return code: ${rc}" ; exit 1 ; fi @@ -205,7 +205,7 @@ function logout_of_ecosystem { function generating_galasa_test_project { h2 "Generating galasa test project code..." cd $TEMP_DIR - cmd="${PROJECT_DIR}/bin/${binary} project create --package dev.galasa.cps.rest.test --features http --obr --gradle --force --development " + cmd="${BINARY_LOCATION} project create --package dev.galasa.cps.rest.test --features http --obr --gradle --force --development " info "Command is $cmd" $cmd rc=$? ; if [[ "${rc}" != "0" ]]; then error "Failed to generate galasa test project. Return code: ${rc}" ; exit 1 ; fi From 8031bade74e7ca8fa06dfc80f401be1cac037d77 Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Mon, 16 Sep 2024 14:33:19 +0100 Subject: [PATCH 20/30] uncomment script Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui --- test-galasactl-ecosystem.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-galasactl-ecosystem.sh b/test-galasactl-ecosystem.sh index d4ec0350..2b139c14 100755 --- a/test-galasactl-ecosystem.sh +++ b/test-galasactl-ecosystem.sh @@ -116,6 +116,6 @@ resources_tests # Test the hybrid configuration where the local test runs locally, but # draws it's CPS properties from a remote ecosystem via a REST extension. -# source ${BASEDIR}/test-scripts/test-local-run-remote-cps.sh -# test_local_run_remote_cps +source ${BASEDIR}/test-scripts/test-local-run-remote-cps.sh +test_local_run_remote_cps From 8b8b1ece0761fcaa34d3516fd2d3f5dd09ff4c23 Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Mon, 16 Sep 2024 15:04:30 +0100 Subject: [PATCH 21/30] add new image for testing purposes Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui --- .github/workflows/build.yml | 21 +++++++++++++++ dockerfiles/dockerfile.galasactl-ibm-testing | 28 ++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 dockerfiles/dockerfile.galasactl-ibm-testing diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0fbbd922..e020f3f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -156,6 +156,27 @@ jobs: dockerRepository=ghcr.io tag=${{ env.BRANCH }} + - name: Extract metadata for galasactl-ibm-testing image + id: metadata-galasactl-ibm-testing + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/galasactl-ibm-x86_64-testing + + # This image is used in a Tekton pipeline triggered after this workflow + # to test the CLI's ecosystem commands + - name: Build galasactl-ibm-testing image + id: build-galasactl-ibm-testing + uses: docker/build-push-action@v5 + with: + context: . + file: dockerfiles/dockerfile.galasactl-ibm-testing + push: true + tags: ${{ steps.metadata-galasactl-ibm-testing.outputs.tags }} + labels: ${{ steps.metadata-galasactl-ibm-testing.outputs.labels }} + build-args: | + dockerRepository=ghcr.io + tag=${{ env.BRANCH }} + - name: Extract metadata for galasactl-executables image id: metadata-galasactl-executables uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 diff --git a/dockerfiles/dockerfile.galasactl-ibm-testing b/dockerfiles/dockerfile.galasactl-ibm-testing new file mode 100644 index 00000000..4266a6d5 --- /dev/null +++ b/dockerfiles/dockerfile.galasactl-ibm-testing @@ -0,0 +1,28 @@ +ARG dockerRepository +ARG tag +FROM ${dockerRepository}/galasa-dev/galasactl-ibm-x86_64:${tag} + +# This image is used for testing the ecosystem CLI commands in the galasactl image built by GHCR +# As well as `galasactl`, the image needs Gradle and JDK for the local run remote CPS tests + +# Environment variables +ENV GRADLE_VERSION=8.9 +ENV GRADLE_HOME=/opt/gradle + +# Install required packages +RUN apt-get update && \ + apt-get install -y \ + openjdk-17-jdk \ + wget \ + unzip && \ + # Download and install Gradle + wget https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip -O /tmp/gradle.zip && \ + mkdir -p ${GRADLE_HOME} && \ + unzip /tmp/gradle.zip -d ${GRADLE_HOME} && \ + rm /tmp/gradle.zip && \ + ln -s ${GRADLE_HOME}/gradle-${GRADLE_VERSION} ${GRADLE_HOME}/latest && \ + ln -s ${GRADLE_HOME}/latest/bin/gradle /usr/local/bin/gradle && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +RUN java -version && gradle --version \ No newline at end of file From a84fbef7758bb31d2680887033d09795ac479edb Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Mon, 16 Sep 2024 16:20:50 +0100 Subject: [PATCH 22/30] install java, maven and gradle into normal galasactl image Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui --- .github/workflows/build.yml | 21 ------------ dockerfiles/dockerfile.galasactl | 36 ++++++++++++++++++-- dockerfiles/dockerfile.galasactl-ibm-testing | 28 --------------- 3 files changed, 34 insertions(+), 51 deletions(-) delete mode 100644 dockerfiles/dockerfile.galasactl-ibm-testing diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e020f3f0..0fbbd922 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -156,27 +156,6 @@ jobs: dockerRepository=ghcr.io tag=${{ env.BRANCH }} - - name: Extract metadata for galasactl-ibm-testing image - id: metadata-galasactl-ibm-testing - uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 - with: - images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/galasactl-ibm-x86_64-testing - - # This image is used in a Tekton pipeline triggered after this workflow - # to test the CLI's ecosystem commands - - name: Build galasactl-ibm-testing image - id: build-galasactl-ibm-testing - uses: docker/build-push-action@v5 - with: - context: . - file: dockerfiles/dockerfile.galasactl-ibm-testing - push: true - tags: ${{ steps.metadata-galasactl-ibm-testing.outputs.tags }} - labels: ${{ steps.metadata-galasactl-ibm-testing.outputs.labels }} - build-args: | - dockerRepository=ghcr.io - tag=${{ env.BRANCH }} - - name: Extract metadata for galasactl-executables image id: metadata-galasactl-executables uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 diff --git a/dockerfiles/dockerfile.galasactl b/dockerfiles/dockerfile.galasactl index e13195e2..b04b8fd2 100644 --- a/dockerfiles/dockerfile.galasactl +++ b/dockerfiles/dockerfile.galasactl @@ -1,7 +1,39 @@ FROM harbor.galasa.dev/docker_proxy_cache/library/ubuntu:20.04 -RUN apt-get update \ - && apt-get install -y ca-certificates +# Install Java, Maven and Gradle as they are pre-reqs for galasactl + +ENV MAVEN_VERSION=3.8.5 +ENV MAVEN_HOME=/opt/maven +ENV GRADLE_VERSION=8.9 +ENV GRADLE_HOME=/opt/gradle + +RUN apt-get update && \ + apt-get install -y \ + ca-certificates \ + openjdk-17-jdk \ + wget \ + unzip \ + tar && \ + # Install Gradle + wget https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip -O /tmp/gradle.zip && \ + mkdir -p ${GRADLE_HOME} && \ + unzip /tmp/gradle.zip -d ${GRADLE_HOME} && \ + rm /tmp/gradle.zip && \ + ln -s ${GRADLE_HOME}/gradle-${GRADLE_VERSION} ${GRADLE_HOME}/latest && \ + ln -s ${GRADLE_HOME}/latest/bin/gradle /usr/local/bin/gradle && \ + # Install Maven + wget https://dlcdn.apache.org/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz -O /tmp/maven.tar.gz && \ + mkdir -p ${MAVEN_HOME} && \ + tar -xzf /tmp/maven.tar.gz -C ${MAVEN_HOME} --strip-components=1 && \ + rm /tmp/maven.tar.gz && \ + ln -s ${MAVEN_HOME}/bin/mvn /usr/local/bin/mvn && \ + # Clean up + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +RUN java -version && \ + gradle --version && \ + mvn --version ARG platform diff --git a/dockerfiles/dockerfile.galasactl-ibm-testing b/dockerfiles/dockerfile.galasactl-ibm-testing deleted file mode 100644 index 4266a6d5..00000000 --- a/dockerfiles/dockerfile.galasactl-ibm-testing +++ /dev/null @@ -1,28 +0,0 @@ -ARG dockerRepository -ARG tag -FROM ${dockerRepository}/galasa-dev/galasactl-ibm-x86_64:${tag} - -# This image is used for testing the ecosystem CLI commands in the galasactl image built by GHCR -# As well as `galasactl`, the image needs Gradle and JDK for the local run remote CPS tests - -# Environment variables -ENV GRADLE_VERSION=8.9 -ENV GRADLE_HOME=/opt/gradle - -# Install required packages -RUN apt-get update && \ - apt-get install -y \ - openjdk-17-jdk \ - wget \ - unzip && \ - # Download and install Gradle - wget https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip -O /tmp/gradle.zip && \ - mkdir -p ${GRADLE_HOME} && \ - unzip /tmp/gradle.zip -d ${GRADLE_HOME} && \ - rm /tmp/gradle.zip && \ - ln -s ${GRADLE_HOME}/gradle-${GRADLE_VERSION} ${GRADLE_HOME}/latest && \ - ln -s ${GRADLE_HOME}/latest/bin/gradle /usr/local/bin/gradle && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -RUN java -version && gradle --version \ No newline at end of file From b1ecb9d56cfe01a8fc7ae3da4571bd6bff4189dc Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Tue, 17 Sep 2024 10:03:39 +0100 Subject: [PATCH 23/30] just install gradle for now Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui --- dockerfiles/dockerfile.galasactl | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/dockerfiles/dockerfile.galasactl b/dockerfiles/dockerfile.galasactl index b04b8fd2..a556aa19 100644 --- a/dockerfiles/dockerfile.galasactl +++ b/dockerfiles/dockerfile.galasactl @@ -1,9 +1,6 @@ FROM harbor.galasa.dev/docker_proxy_cache/library/ubuntu:20.04 -# Install Java, Maven and Gradle as they are pre-reqs for galasactl - -ENV MAVEN_VERSION=3.8.5 -ENV MAVEN_HOME=/opt/maven +# Install Java and Gradle as they are pre-reqs for galasactl ENV GRADLE_VERSION=8.9 ENV GRADLE_HOME=/opt/gradle @@ -21,19 +18,12 @@ RUN apt-get update && \ rm /tmp/gradle.zip && \ ln -s ${GRADLE_HOME}/gradle-${GRADLE_VERSION} ${GRADLE_HOME}/latest && \ ln -s ${GRADLE_HOME}/latest/bin/gradle /usr/local/bin/gradle && \ - # Install Maven - wget https://dlcdn.apache.org/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz -O /tmp/maven.tar.gz && \ - mkdir -p ${MAVEN_HOME} && \ - tar -xzf /tmp/maven.tar.gz -C ${MAVEN_HOME} --strip-components=1 && \ - rm /tmp/maven.tar.gz && \ - ln -s ${MAVEN_HOME}/bin/mvn /usr/local/bin/mvn && \ # Clean up apt-get clean && \ rm -rf /var/lib/apt/lists/* RUN java -version && \ - gradle --version && \ - mvn --version + gradle --version ARG platform From b153c0e1f3bd730aa72ab16720d7a47c0685fd96 Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Tue, 17 Sep 2024 10:19:21 +0100 Subject: [PATCH 24/30] empty for build Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui From 106046543d832f033a4b6a36ca3414dede0e5685 Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Tue, 17 Sep 2024 10:40:19 +0100 Subject: [PATCH 25/30] Fix problem with script Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui --- .../calculate-galasactl-executables.sh | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) mode change 100644 => 100755 test-scripts/calculate-galasactl-executables.sh diff --git a/test-scripts/calculate-galasactl-executables.sh b/test-scripts/calculate-galasactl-executables.sh old mode 100644 new mode 100755 index 76d11c76..0ea94bfe --- a/test-scripts/calculate-galasactl-executables.sh +++ b/test-scripts/calculate-galasactl-executables.sh @@ -85,17 +85,7 @@ export GALASA_TEST_RUN_GET_EXPECTED_NUMBER_ARTIFACT_RUNNING_COUNT="10" function calculate_galasactl_executable { h2 "Calculate the name of the galasactl executable for this machine/os" - # Determine if the /bin directory exists i.e. if the script is testing - # a built binary or if it is testing a published Docker image in GHCR - # If testing a built binary, it will use it from the /bin, otherwise - # `galasactl` will be used as it is installed on the path within the image. - path_to_bin="${BASEDIR}/bin" - - # Check if the /bin directory exists - if [ -d "$directory_path" ]; then - echo "The /bin directory exists so assume the script is testing a locally built binary." - - raw_os=$(uname -s) # eg: "Darwin" + raw_os=$(uname -s) # eg: "Darwin" os="" case $raw_os in Darwin*) @@ -117,6 +107,17 @@ function calculate_galasactl_executable { export binary="galasactl-${os}-${architecture}" info "galasactl binary is ${binary}" + # Determine if the /bin directory exists i.e. if the script is testing + # a built binary or if it is testing a published Docker image in GHCR + # If testing a built binary, it will use it from the /bin, otherwise + # `galasactl` will be used as it is installed on the path within the image. + path_to_bin="${BASEDIR}/bin" + echo $path_to_bin + + # Check if the /bin directory exists + if [ -d "$path_to_bin" ]; then + echo "The /bin directory exists so assume the script is testing a locally built binary." + export BINARY_LOCATION="${ORIGINAL_DIR}/bin/${binary}" info "binary location is ${BINARY_LOCATION}" else From fca9a56074ff0ea883ef9a7bdcbd96b6c1adfeb7 Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Tue, 17 Sep 2024 11:41:16 +0100 Subject: [PATCH 26/30] add java_home env var to docker image Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui --- dockerfiles/dockerfile.galasactl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dockerfiles/dockerfile.galasactl b/dockerfiles/dockerfile.galasactl index a556aa19..2c3cbc9b 100644 --- a/dockerfiles/dockerfile.galasactl +++ b/dockerfiles/dockerfile.galasactl @@ -22,6 +22,9 @@ RUN apt-get update && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* +ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 +ENV PATH="$JAVA_HOME/bin:$PATH" + RUN java -version && \ gradle --version From 0faa4f7818d9282a16ce0149db0ea5cae401246b Mon Sep 17 00:00:00 2001 From: Jade Carino Date: Tue, 17 Sep 2024 12:32:58 +0100 Subject: [PATCH 27/30] Create new testing image Signed-off-by: Jade Carino Signed-off-by: Aashir Siddiqui --- .github/workflows/build.yml | 34 ++++++++++++++++++++ dockerfiles/dockerfile.galasactl | 29 ++--------------- dockerfiles/dockerfile.galasactl-ibm-testing | 10 ++++++ 3 files changed, 46 insertions(+), 27 deletions(-) create mode 100644 dockerfiles/dockerfile.galasactl-ibm-testing diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0fbbd922..ab18ec77 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -184,6 +184,40 @@ jobs: run: | docker run --env ARGOCD_AUTH_TOKEN=${{ env.ARGOCD_AUTH_TOKEN }} --rm -v ${{ github.workspace }}:/var/workspace ghcr.io/galasa-dev/argocdcli:main app wait ${{ env.BRANCH }}-cli --resource apps:Deployment:cli-${{ env.BRANCH }} --health --server argocd.galasa.dev + build-galasactl-ibm-testing-image-and-trigger-tekton-pipeline: + name: Build image containing galasactl, OpenJDK and Gradle for testing + runs-on: ubuntu-latest + needs: build-cli + + steps: + - name: Checkout CLI + uses: actions/checkout@v4 + + - name: Login to Github Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for galasactl-ibm-testing image + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/galasactl-ibm-x86_64-testing + + - name: Build galasactl-ibm-testing image + id: build + uses: docker/build-push-action@v5 + with: + context: . + file: dockerfiles/dockerfile.galasactl-ibm-testing + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + branch=${{ env.BRANCH }} + - name: Attempt to trigger test-cli-ecosystem-commands Tekton pipeline run: | echo "The Tekton pipeline test-cli-ecosystem-commands should be triggered in the next 2-minutes - check the Tekton dashboard" \ No newline at end of file diff --git a/dockerfiles/dockerfile.galasactl b/dockerfiles/dockerfile.galasactl index 2c3cbc9b..6390dc5f 100644 --- a/dockerfiles/dockerfile.galasactl +++ b/dockerfiles/dockerfile.galasactl @@ -1,32 +1,7 @@ FROM harbor.galasa.dev/docker_proxy_cache/library/ubuntu:20.04 -# Install Java and Gradle as they are pre-reqs for galasactl -ENV GRADLE_VERSION=8.9 -ENV GRADLE_HOME=/opt/gradle - -RUN apt-get update && \ - apt-get install -y \ - ca-certificates \ - openjdk-17-jdk \ - wget \ - unzip \ - tar && \ - # Install Gradle - wget https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip -O /tmp/gradle.zip && \ - mkdir -p ${GRADLE_HOME} && \ - unzip /tmp/gradle.zip -d ${GRADLE_HOME} && \ - rm /tmp/gradle.zip && \ - ln -s ${GRADLE_HOME}/gradle-${GRADLE_VERSION} ${GRADLE_HOME}/latest && \ - ln -s ${GRADLE_HOME}/latest/bin/gradle /usr/local/bin/gradle && \ - # Clean up - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 -ENV PATH="$JAVA_HOME/bin:$PATH" - -RUN java -version && \ - gradle --version +RUN apt-get update \ + && apt-get install -y ca-certificates ARG platform diff --git a/dockerfiles/dockerfile.galasactl-ibm-testing b/dockerfiles/dockerfile.galasactl-ibm-testing new file mode 100644 index 00000000..179ac19b --- /dev/null +++ b/dockerfiles/dockerfile.galasactl-ibm-testing @@ -0,0 +1,10 @@ +FROM ghcr.io/galasa-dev/openjdk17-ibm-gradle:main + +ARG branch + +RUN wget https://development.galasa.dev/${branch}/binary/cli/galasactl-linux-x86_64 -O /usr/local/bin/galasactl && \ + chmod +x /usr/local/bin/galasactl + +RUN galasactl --version + +ENV PATH="/usr/local/bin:$PATH" \ No newline at end of file From 9d9e8ca248f4aef219489d5d6084017a3dee75ba Mon Sep 17 00:00:00 2001 From: Aashir Siddiqui Date: Wed, 18 Sep 2024 09:59:49 +0100 Subject: [PATCH 28/30] Empty commit to trigger rebuild Signed-off-by: Aashir Siddiqui From ec7bdfd372cada5c32873dd271b1982cc8664d3e Mon Sep 17 00:00:00 2001 From: Aashir Siddiqui Date: Wed, 18 Sep 2024 10:31:13 +0100 Subject: [PATCH 29/30] Squashed commits Signed-off-by: Aashir Siddiqui --- pkg/cmd/authTokens.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/cmd/authTokens.go b/pkg/cmd/authTokens.go index e8d03c57..5016c699 100644 --- a/pkg/cmd/authTokens.go +++ b/pkg/cmd/authTokens.go @@ -77,3 +77,4 @@ func (cmd *AuthTokensCommand) createAuthTokensCobraCmd( return authTokensCmd, err } + From ad615a2930d1814dd61af0047ec54a2fc2c390aa Mon Sep 17 00:00:00 2001 From: Aashir Siddiqui Date: Fri, 20 Sep 2024 12:00:05 +0100 Subject: [PATCH 30/30] Implemented the changes requested Signed-off-by: Aashir Siddiqui --- docs/generated/errors-list.md | 2 + docs/generated/galasactl_auth_tokens_get.md | 2 +- pkg/auth/authTokensGet.go | 31 +-- pkg/auth/authTokensGet_test.go | 6 +- pkg/cmd/authTokensGet.go | 2 +- pkg/errors/errorMessage.go | 3 +- test-galasactl-ecosystem.sh | 3 + test-scripts/auth-tests.sh | 217 ++++++++++++++++++++ 8 files changed, 246 insertions(+), 20 deletions(-) create mode 100755 test-scripts/auth-tests.sh diff --git a/docs/generated/errors-list.md b/docs/generated/errors-list.md index 3b75dd85..6ea0a1a6 100644 --- a/docs/generated/errors-list.md +++ b/docs/generated/errors-list.md @@ -160,6 +160,8 @@ The `galasactl` tool can generate the following errors: - GAL1162E: An attempt to delete a run named '{}' failed. Unexpected http status code {} received from the server. Error details from the server are: '{}' - GAL1163E: The run named '{}' could not be deleted because it was not found by the Galasa service. Try listing runs using 'galasactl runs get' to identify the one you wish to delete - GAL1164E: An attempt to delete a run named '{}' failed. Unexpected http status code {} received from the server. Error details from the server are not in the json format. +- GAL1165E: '{}' is not supported as a valid value. LoginId should not contain spaces. +- GAL1166E: The loginId provided by the --user field cannot be an empty string. - GAL1225E: Failed to open file '{}' cause: {}. Check that this file exists, and that you have read permissions. - GAL1226E: Internal failure. Contents of gzip could be read, but not decoded. New gzip reader failed: file: {} error: {} - GAL1227E: Internal failure. Contents of gzip could not be decoded. {} error: {} diff --git a/docs/generated/galasactl_auth_tokens_get.md b/docs/generated/galasactl_auth_tokens_get.md index 61803bc7..14cfe9f3 100644 --- a/docs/generated/galasactl_auth_tokens_get.md +++ b/docs/generated/galasactl_auth_tokens_get.md @@ -14,7 +14,7 @@ galasactl auth tokens get [flags] ``` -h, --help Displays the options for the 'auth tokens get' command. - --user string An optional flag that is used to retrieve the access tokens of the currently logged in user. The input must be a string. + --user string Optional. Retrieves a list of access tokens for the user with the given username. ``` ### Options inherited from parent commands diff --git a/pkg/auth/authTokensGet.go b/pkg/auth/authTokensGet.go index bae63fe2..4c9af276 100644 --- a/pkg/auth/authTokensGet.go +++ b/pkg/auth/authTokensGet.go @@ -8,6 +8,7 @@ package auth import ( "context" "log" + "net/http" "strings" galasaErrors "github.com/galasa-dev/cli/pkg/errors" @@ -48,19 +49,21 @@ func getAuthTokensFromRestApi(apiClient *galasaapi.APIClient, loginId string) ([ } } - if err != nil { - return authTokens, err - } + if err == nil { - tokens, resp, err := apiCall.Execute() + var tokens *galasaapi.AuthTokens + var resp *http.Response - if err != nil { - log.Println("getAuthTokensFromRestApi - Failed to retrieve list of tokens from API server") - err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_RETRIEVING_TOKEN_LIST_FROM_API_SERVER, err.Error()) - } else { - defer resp.Body.Close() - authTokens = tokens.GetTokens() - log.Printf("getAuthTokensFromRestApi - %v tokens collected", len(authTokens)) + tokens, resp, err = apiCall.Execute() + + if err != nil { + log.Println("getAuthTokensFromRestApi - Failed to retrieve list of tokens from API server") + err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_RETRIEVING_TOKEN_LIST_FROM_API_SERVER, err.Error()) + } else { + defer resp.Body.Close() + authTokens = tokens.GetTokens() + log.Printf("getAuthTokensFromRestApi - %v tokens collected", len(authTokens)) + } } return authTokens, err @@ -85,13 +88,13 @@ func validateLoginIdFlag(loginId string) (string, error) { var err error loginId = strings.TrimSpace(loginId) - splits := strings.Split(loginId, " ") + hasSpace := strings.Contains(loginId, " ") if loginId == "" { - err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_MISSING_USER_LOGIN_ID_FLAG) + err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_INVALID_USER_FLAG_VALUE) } - if len(splits) > 1 { + if hasSpace { err = galasaErrors.NewGalasaError(galasaErrors.GALASA_ERROR_INVALID_LOGIN_ID, loginId) } diff --git a/pkg/auth/authTokensGet_test.go b/pkg/auth/authTokensGet_test.go index 67ac4205..b9a03511 100644 --- a/pkg/auth/authTokensGet_test.go +++ b/pkg/auth/authTokensGet_test.go @@ -148,7 +148,7 @@ func TestInvalidPathReturnsError(t *testing.T) { console := utils.NewMockConsole() //When - err := GetTokens(apiClient, console, "") + err := GetTokens(apiClient, console, "admin") //Then assert.NotNil(t, err) @@ -164,7 +164,7 @@ func TestMissingLoginIdFlagReturnsBadRequest(t *testing.T) { defer server.Close() console := utils.NewMockConsole() - expectedOutput := `GAL1155E: The id provided by the --id field cannot be an empty string.` + expectedOutput := `GAL1166E: The loginId provided by the --user field cannot be an empty string.` //When err := GetTokens(apiClient, console, " ") @@ -182,7 +182,7 @@ func TestLoginIdWithSpacesReturnsBadRequest(t *testing.T) { defer server.Close() console := utils.NewMockConsole() - expectedOutput := `GAL1157E: 'galasa admin' is not supported as a valid value. Valid value should not contain spaces. A value of 'admin' is valid but 'galasa admin' is not.` + expectedOutput := `GAL1165E: 'galasa admin' is not supported as a valid value. LoginId should not contain spaces.` //When err := GetTokens(apiClient, console, "galasa admin") diff --git a/pkg/cmd/authTokensGet.go b/pkg/cmd/authTokensGet.go index 3541dc7e..7b32d306 100644 --- a/pkg/cmd/authTokensGet.go +++ b/pkg/cmd/authTokensGet.go @@ -147,7 +147,7 @@ func (cmd *AuthTokensGetCommand) executeAuthTokensGet( func addLoginIdFlagToAuthTokensGet(cmd *cobra.Command, authTokensGetCmdValues *AuthTokensCmdValues) { flagName := "user" - var description string = "An optional flag that is used to retrieve the access tokens of the currently logged in user. The input must be a string." + var description string = "Optional. Retrieves a list of access tokens for the user with the given username." cmd.Flags().StringVar(&authTokensGetCmdValues.loginId, flagName, "", description) } diff --git a/pkg/errors/errorMessage.go b/pkg/errors/errorMessage.go index 4a313290..ed177419 100644 --- a/pkg/errors/errorMessage.go +++ b/pkg/errors/errorMessage.go @@ -249,9 +249,10 @@ var ( GALASA_ERROR_INVALID_TOKEN_ID_FORMAT = NewMessageType("GAL1154E: The provided token ID, '%s', does not match formatting requirements. The token ID can contain any character in the 'a'-'z', 'A'-'Z', '0'-'9', '-' (dash), or '_' (underscore) ranges only.", 1154, STACK_TRACE_NOT_WANTED) GALASA_ERROR_MISSING_USER_LOGIN_ID_FLAG = NewMessageType("GAL1155E: The id provided by the --id field cannot be an empty string.", 1155, STACK_TRACE_NOT_WANTED) GALASA_ERROR_LOGIN_ID_NOT_SUPPORTED = NewMessageType("GAL1156E: '%s' is not supported as a valid value. Valid values are 'me'.", 1156, STACK_TRACE_NOT_WANTED) - GALASA_ERROR_INVALID_LOGIN_ID = NewMessageType("GAL1157E: '%s' is not supported as a valid value. Valid value should not contain spaces. A value of 'admin' is valid but 'galasa admin' is not.", 1157, STACK_TRACE_NOT_WANTED) GALASA_ERROR_DELETE_RUN_FAILED = NewMessageType("GAL1157E: An attempt to delete a run named '%s' failed. Cause is %s", 1157, STACK_TRACE_NOT_WANTED) GALASA_ERROR_SERVER_DELETE_RUNS_FAILED = NewMessageType("GAL1158E: An attempt to delete a run named '%s' failed. Sending the delete request to the Galasa service failed. Cause is %v", 1158, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_INVALID_LOGIN_ID = NewMessageType("GAL1165E: '%s' is not supported as a valid value. LoginId should not contain spaces.", 1165, STACK_TRACE_NOT_WANTED) + GALASA_ERROR_INVALID_USER_FLAG_VALUE = NewMessageType("GAL1166E: The loginId provided by the --user field cannot be an empty string.", 1166, STACK_TRACE_NOT_WANTED) // 4 related but slightly different errors, when an HTTP response arrives from the Galasa server, and we can/can't parse the payload to get the message details out. GALASA_ERROR_DELETE_RUNS_NO_RESPONSE_CONTENT = NewMessageType("GAL1159E: An attempt to delete a run named '%s' failed. Unexpected http status code %v received from the server.", 1159, STACK_TRACE_NOT_WANTED) diff --git a/test-galasactl-ecosystem.sh b/test-galasactl-ecosystem.sh index 2b139c14..f95d3414 100755 --- a/test-galasactl-ecosystem.sh +++ b/test-galasactl-ecosystem.sh @@ -114,6 +114,9 @@ properties_tests source ${BASEDIR}/test-scripts/resources-tests.sh --bootstrap "${bootstrap}" resources_tests +source ${BASEDIR}/test-scripts/auth-tests.sh --bootstrap "${bootstrap}" +auth_tests + # Test the hybrid configuration where the local test runs locally, but # draws it's CPS properties from a remote ecosystem via a REST extension. source ${BASEDIR}/test-scripts/test-local-run-remote-cps.sh diff --git a/test-scripts/auth-tests.sh b/test-scripts/auth-tests.sh new file mode 100755 index 00000000..b8545810 --- /dev/null +++ b/test-scripts/auth-tests.sh @@ -0,0 +1,217 @@ +#!/bin/bash + +# +# Copyright contributors to the Galasa project +# +# SPDX-License-Identifier: EPL-2.0 +# +echo "Running script runs-tests.sh" +# This script can be ran locally or executed in a pipeline to test the various built binaries of galasactl +# This script tests the 'galasactl runs submit' command against a test that is in our ecosystem's testcatalog already +# Pre-requesite: the CLI must have been built first so the binaries are present in the /bin directory + +if [[ "$CALLED_BY_MAIN" == "" ]]; then + # Where is this script executing from ? + BASEDIR=$(dirname "$0");pushd $BASEDIR 2>&1 >> /dev/null ;BASEDIR=$(pwd);popd 2>&1 >> /dev/null + export ORIGINAL_DIR=$(pwd) + cd "${BASEDIR}" + + #-------------------------------------------------------------------------- + # + # Set Colors + # + #-------------------------------------------------------------------------- + bold=$(tput bold) + underline=$(tput sgr 0 1) + reset=$(tput sgr0) + + red=$(tput setaf 1) + green=$(tput setaf 76) + white=$(tput setaf 7) + tan=$(tput setaf 202) + blue=$(tput setaf 25) + + #-------------------------------------------------------------------------- + # + # Headers and Logging + # + #-------------------------------------------------------------------------- + underline() { printf "${underline}${bold}%s${reset}\n" "$@" ;} + h1() { printf "\n${underline}${bold}${blue}%s${reset}\n" "$@" ;} + h2() { printf "\n${underline}${bold}${white}%s${reset}\n" "$@" ;} + debug() { printf "${white}%s${reset}\n" "$@" ;} + info() { printf "${white}➜ %s${reset}\n" "$@" ;} + success() { printf "${green}✔ %s${reset}\n" "$@" ;} + error() { printf "${red}✖ %s${reset}\n" "$@" ;} + warn() { printf "${tan}➜ %s${reset}\n" "$@" ;} + bold() { printf "${bold}%s${reset}\n" "$@" ;} + note() { printf "\n${underline}${bold}${blue}Note:${reset} ${blue}%s${reset}\n" "$@" ;} + + #----------------------------------------------------------------------------------------- + # Process parameters + #----------------------------------------------------------------------------------------- + bootstrap="" + + while [ "$1" != "" ]; do + case $1 in + --bootstrap ) shift + bootstrap="$1" + ;; + -h | --help ) usage + exit + ;; + * ) error "Unexpected argument $1" + usage + exit 1 + esac + shift + done + + # Can't really verify that the bootstrap provided is a valid one, but galasactl will pick this up later if not + if [[ "${bootstrap}" == "" ]]; then + export bootstrap="https://prod1-galasa-dev.cicsk8s.hursley.ibm.com/api/bootstrap" + info "No bootstrap supplied. Defaulting the --bootstrap to be ${bootstrap}" + fi + + info "Running tests against ecosystem bootstrap ${bootstrap}" + + #----------------------------------------------------------------------------------------- + # Constants + #----------------------------------------------------------------------------------------- + export GALASA_TEST_NAME_SHORT="local.CoreLocalJava11Ubuntu" + export GALASA_TEST_NAME_LONG="dev.galasa.inttests.core.${GALASA_TEST_NAME_SHORT}" + export GALASA_TEST_RUN_GET_EXPECTED_SUMMARY_LINE_COUNT="4" + export GALASA_TEST_RUN_GET_EXPECTED_DETAILS_LINE_COUNT="13" + export GALASA_TEST_RUN_GET_EXPECTED_RAW_PIPE_COUNT="10" + export GALASA_TEST_RUN_GET_EXPECTED_NUMBER_ARTIFACT_RUNNING_COUNT="10" + +fi + +# generate a random number to append to test names to avoid multiple running at once overriding each other +function get_random_property_name_number { + minimum=100 + maximum=999 + PROP_NUM=$(($minimum + $RANDOM % $maximum)) + echo $PROP_NUM +} + +#----------------------------------------------------------------------------------------- +# Tests +#----------------------------------------------------------------------------------------- + +function auth_tokens_get { + + h2 "Performing auth tokens get without loginId: get..." + + set -o pipefail # Fail everything if anything in the pipeline fails. Else we are just checking the 'tee' return code. + + cmd="${BINARY_LOCATION} auth tokens get \ + --bootstrap $bootstrap \ + --log - + " + + info "Command is: $cmd" + + $cmd + rc=$? + + # We expect a return code of 0 because this is a properly formed auth tokens get command. + if [[ "${rc}" != "0" ]]; then + error "Failed to get access tokens." + exit 1 + fi + + output_file="$ORIGINAL_DIR/temp/auth-get-output.txt" + $cmd | tee $output_file + + if [[ "${rc}" != "0" ]]; then + error "Failed to get access tokens." + exit 1 + fi + + # Check that the previous properties set created a property + cat $output_file | grep "Total:" -q + + success "All access tokens fetched from database successfully." + +} + +function auth_tokens_get_with_missing_loginId_throws_error { + + h2 "Performing auth tokens get with loginId: get..." + loginId="" + + cmd="${BINARY_LOCATION} auth tokens get \ + --user $loginId \ + --bootstrap $bootstrap \ + --log - + " + + info "Command is: $cmd" + + output_file="$ORIGINAL_DIR/temp/auth-get-output.txt" + set -o pipefail # Fail everything if anything in the pipeline fails. Else we are just checking the 'tee' return code. + $cmd | tee $output_file + + rc=$? + if [[ "${rc}" != "1" ]]; then + error "Failed to get access tokens." + exit 1 + fi + + success "galasactl auth tokens get command correctly threw an error due to missing loginId" + +} + +function auth_tokens_get_by_loginId { + + h2 "Performing auth tokens get with loginId: get..." + loginId="Aashir.Siddiqui@ibm.com" + + set -o pipefail # Fail everything if anything in the pipeline fails. Else we are just checking the 'tee' return code. + + cmd="${BINARY_LOCATION} auth tokens get \ + --user $loginId \ + --bootstrap $bootstrap \ + --log - + " + + info "Command is: $cmd" + + $cmd + rc=$? + + # We expect a return code of 0 because this is a properly formed auth tokens get command. + if [[ "${rc}" != "0" ]]; then + error "Failed to create property with name and value used." + exit 1 + fi + + output_file="$ORIGINAL_DIR/temp/auth-get-output.txt" + $cmd | tee $output_file + if [[ "${rc}" != "0" ]]; then + error "Failed to get property with name used: command failed." + exit 1 + fi + + # Check that the previous properties set created a property + cat $output_file | grep "Total: 1" -q + + success "All access tokens by loginId fetched from database successfully." + +} + +#-------------------------------------------------------------------------- + +function auth_tests { + auth_tokens_get + auth_tokens_get_by_loginId + auth_tokens_get_with_missing_loginId_throws_error +} + +# checks if it's been called by main, set this variable if it is +if [[ "$CALLED_BY_MAIN" == "" ]]; then + source $BASEDIR/calculate-galasactl-executables.sh + calculate_galasactl_executable + auth_tests +fi \ No newline at end of file