diff --git a/cli/pkg/cmd/cli.go b/cli/pkg/cmd/cli.go index 7834b140c..02802830b 100644 --- a/cli/pkg/cmd/cli.go +++ b/cli/pkg/cmd/cli.go @@ -75,6 +75,8 @@ func RunCLI() ReturnCode { return handleCreateTeamLock(*kpClientParams, subflags) case "create-group-lock": return handleCreateGroupLock(*kpClientParams, subflags) + case "delete-env-lock": + return handleDeleteEnvLock(*kpClientParams, subflags) default: log.Printf("unknown subcommand %s\n", subcommand) return ReturnCodeInvalidArguments diff --git a/cli/pkg/cmd/handlers.go b/cli/pkg/cmd/handlers.go index 27c10b2d3..229766ce1 100644 --- a/cli/pkg/cmd/handlers.go +++ b/cli/pkg/cmd/handlers.go @@ -53,6 +53,16 @@ func handleRelease(kpClientParams kuberpultClientParameters, args []string) Retu return ReturnCodeSuccess } +func handleDeleteEnvLock(kpClientParams kuberpultClientParameters, args []string) ReturnCode { + parsedArgs, err := locks.ParseArgsDeleteEnvironmentLock(args) + + if err != nil { + log.Printf("error while parsing command line args, error: %v", err) + return ReturnCodeInvalidArguments + } + return handleLockRequest(kpClientParams, parsedArgs) +} + func handleCreateEnvLock(kpClientParams kuberpultClientParameters, args []string) ReturnCode { parsedArgs, err := locks.ParseArgsCreateEnvironmentLock(args) @@ -60,7 +70,7 @@ func handleCreateEnvLock(kpClientParams kuberpultClientParameters, args []string log.Printf("error while parsing command line args, error: %v", err) return ReturnCodeInvalidArguments } - return handleCreateLock(kpClientParams, parsedArgs) + return handleLockRequest(kpClientParams, parsedArgs) } func handleCreateAppLock(kpClientParams kuberpultClientParameters, args []string) ReturnCode { @@ -70,7 +80,7 @@ func handleCreateAppLock(kpClientParams kuberpultClientParameters, args []string log.Printf("error while parsing command line args, error: %v", err) return ReturnCodeInvalidArguments } - return handleCreateLock(kpClientParams, parsedArgs) + return handleLockRequest(kpClientParams, parsedArgs) } func handleCreateTeamLock(kpClientParams kuberpultClientParameters, args []string) ReturnCode { @@ -80,7 +90,7 @@ func handleCreateTeamLock(kpClientParams kuberpultClientParameters, args []strin log.Printf("error while parsing command line args, error: %v", err) return ReturnCodeInvalidArguments } - return handleCreateLock(kpClientParams, parsedArgs) + return handleLockRequest(kpClientParams, parsedArgs) } func handleCreateGroupLock(kpClientParams kuberpultClientParameters, args []string) ReturnCode { @@ -90,10 +100,10 @@ func handleCreateGroupLock(kpClientParams kuberpultClientParameters, args []stri log.Printf("error while parsing command line args, error: %v", err) return ReturnCodeInvalidArguments } - return handleCreateLock(kpClientParams, parsedArgs) + return handleLockRequest(kpClientParams, parsedArgs) } -func handleCreateLock(kpClientParams kuberpultClientParameters, parsedArgs locks.LockParameters) ReturnCode { +func handleLockRequest(kpClientParams kuberpultClientParameters, parsedArgs locks.LockParameters) ReturnCode { authParams := kutil.AuthenticationParameters{ IapToken: kpClientParams.iapToken, DexToken: kpClientParams.dexToken, @@ -107,7 +117,7 @@ func handleCreateLock(kpClientParams kuberpultClientParameters, parsedArgs locks HttpTimeout: cli_utils.HttpDefaultTimeout, } - if err := locks.CreateLock(requestParameters, authParams, parsedArgs); err != nil { + if err := locks.HandleLockRequest(requestParameters, authParams, parsedArgs); err != nil { log.Printf("error creating lock, error: %v", err) return ReturnCodeFailure } diff --git a/cli/pkg/locks/env_lock_parsing.go b/cli/pkg/locks/env_lock_parsing.go index 259cc45db..3ff2e9de7 100644 --- a/cli/pkg/locks/env_lock_parsing.go +++ b/cli/pkg/locks/env_lock_parsing.go @@ -75,7 +75,7 @@ func convertToCreateEnvironmentLockParams(cmdArgs CreateEnvLockCommandLineArgume return nil, fmt.Errorf("the provided command line arguments structure is invalid, cause: %s", msg) } - rp := EnvironmentLockParameters{ + rp := CreateEnvironmentLockParameters{ LockId: cmdArgs.lockId.Values[0], Environment: cmdArgs.environment.Values[0], UseDexAuthentication: false, //For now there is no ambiguity as to which endpoint to use @@ -99,3 +99,68 @@ func ParseArgsCreateEnvironmentLock(args []string) (LockParameters, error) { return rp, nil } + +type DeleteEnvLockCommandLineArguments struct { + environment cli_utils.RepeatedString + lockId cli_utils.RepeatedString +} + +func argsValidDeleteEnvLock(cmdArgs *DeleteEnvLockCommandLineArguments) (result bool, errorMessage string) { + if len(cmdArgs.lockId.Values) != 1 { + return false, "the --lockID arg must be set exactly once" + } + if len(cmdArgs.environment.Values) != 1 { + return false, "the --environment arg must be set exactly once" + } + + return true, "" +} + +// takes the raw command line flags and converts them to an intermediate represnetations for easy validation +func readDeleteEnvLockArgs(args []string) (*DeleteEnvLockCommandLineArguments, error) { + cmdArgs := DeleteEnvLockCommandLineArguments{} //exhaustruct:ignore + + fs := flag.NewFlagSet("flag set", flag.ContinueOnError) + + fs.Var(&cmdArgs.environment, "environment", "the environment to delete the lock for") + fs.Var(&cmdArgs.lockId, "lockID", "the ID of the lock you are trying to delete") + + if err := fs.Parse(args); err != nil { + return nil, fmt.Errorf("error while parsing command line arguments, error: %w", err) + } + + if len(fs.Args()) != 0 { // kuberpult-cli release does not accept any positional arguments, so this is an error + return nil, fmt.Errorf("these arguments are not recognized: \"%v\"", strings.Join(fs.Args(), " ")) + } + + if ok, msg := argsValidDeleteEnvLock(&cmdArgs); !ok { + return nil, fmt.Errorf(msg) + } + + return &cmdArgs, nil +} + +// converts the intermediate representation of the command line flags into the final structure containing parameters for the create lock endpoint +func convertToDeleteEnvironmentLockParams(cmdArgs DeleteEnvLockCommandLineArguments) (LockParameters, error) { + if ok, msg := argsValidDeleteEnvLock(&cmdArgs); !ok { + // this should never happen, as the validation is already performed by the readArgs function + return nil, fmt.Errorf("the provided command line arguments structure is invalid, cause: %s", msg) + } + return &DeleteEnvironmentLockParameters{ + LockId: cmdArgs.lockId.Values[0], + Environment: cmdArgs.environment.Values[0], + UseDexAuthentication: false, + }, nil +} + +func ParseArgsDeleteEnvironmentLock(args []string) (LockParameters, error) { + cmdArgs, err := readDeleteEnvLockArgs(args) + if err != nil { + return nil, fmt.Errorf("error while reading command line arguments for deleting env locks, error: %w", err) + } + rp, err := convertToDeleteEnvironmentLockParams(*cmdArgs) + if err != nil { + return nil, fmt.Errorf("error while creating parameters for deleting an environment lock, error: %w", err) + } + return rp, nil +} diff --git a/cli/pkg/locks/env_lock_parsing_test.go b/cli/pkg/locks/env_lock_parsing_test.go index e3231846d..06c49d482 100644 --- a/cli/pkg/locks/env_lock_parsing_test.go +++ b/cli/pkg/locks/env_lock_parsing_test.go @@ -140,6 +140,93 @@ func TestReadArgs(t *testing.T) { } } +func TestReadDeleteEnvLockArgs(t *testing.T) { + type testCase struct { + name string + args []string + expectedCmdArgs *DeleteEnvLockCommandLineArguments + expectedError error + } + + tcs := []testCase{ + { + name: "some unrecognized positional arguments", + args: []string{"potato", "tomato"}, + expectedError: errMatcher{ + msg: "these arguments are not recognized: \"potato tomato\"", + }, + }, + { + name: "some flags that don't exist", + args: []string{"--environment", "development", "--potato", "tomato"}, + expectedError: errMatcher{ + msg: "error while parsing command line arguments, error: flag provided but not defined: -potato", + }, + }, + { + name: "nothing provided", + args: []string{}, + expectedError: errMatcher{ + msg: "the --lockID arg must be set exactly once", + }, + }, + { + name: "only --environment is properly provided but without --lockID", + args: []string{"--environment", "potato"}, + expectedError: errMatcher{ + msg: "the --lockID arg must be set exactly once", + }, + }, + { + name: "only --lockID is properly provided but without --environment", + args: []string{"--lockID", "potato"}, + expectedError: errMatcher{ + msg: "the --environment arg must be set exactly once", + }, + }, + { + name: "--message is not accepted", + args: []string{"--environment", "development", "--lockID", "my-lock", "--message", "\"my message\""}, + expectedError: errMatcher{ + msg: "error while parsing command line arguments, error: flag provided but not defined: -message", + }, + }, + { + name: "--environment, lockID are are specified, but not the message", + args: []string{"--environment", "development", "--lockID", "my-lock"}, + expectedCmdArgs: &DeleteEnvLockCommandLineArguments{ + environment: cli_utils.RepeatedString{ + Values: []string{ + "development", + }, + }, + lockId: cli_utils.RepeatedString{ + Values: []string{ + "my-lock", + }, + }, + }, + }, + } + + for _, tc := range tcs { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + cmdArgs, err := readDeleteEnvLockArgs(tc.args) + // check errors + if diff := cmp.Diff(tc.expectedError, err, cmpopts.EquateErrors()); diff != "" { + t.Fatalf("error mismatch (-want, +got):\n%s", diff) + } + + if diff := cmp.Diff(cmdArgs, tc.expectedCmdArgs, cmp.AllowUnexported(DeleteEnvLockCommandLineArguments{})); diff != "" { + t.Fatalf("expected args:\n %v\n, got:\n %v, diff:\n %s\n", tc.expectedCmdArgs, cmdArgs, diff) + } + }) + } +} + func TestParseArgs(t *testing.T) { type testCase struct { name string @@ -152,7 +239,7 @@ func TestParseArgs(t *testing.T) { { name: "with environment and lockID and message", cmdArgs: []string{"--environment", "development", "--lockID", "my-lock", "--message", "message"}, - expectedParams: &EnvironmentLockParameters{ + expectedParams: &CreateEnvironmentLockParameters{ Environment: "development", LockId: "my-lock", Message: "message", @@ -161,7 +248,7 @@ func TestParseArgs(t *testing.T) { { name: "with environment and lockID and no message", cmdArgs: []string{"--environment", "development", "--lockID", "my-lock"}, - expectedParams: &EnvironmentLockParameters{ + expectedParams: &CreateEnvironmentLockParameters{ Environment: "development", LockId: "my-lock", Message: "", @@ -171,7 +258,7 @@ func TestParseArgs(t *testing.T) { { name: "with environment and lockID and multi word message message", cmdArgs: []string{"--environment", "development", "--lockID", "my-lock", "--message", "this is a very long message"}, - expectedParams: &EnvironmentLockParameters{ + expectedParams: &CreateEnvironmentLockParameters{ Environment: "development", LockId: "my-lock", Message: "this is a very long message", @@ -197,3 +284,42 @@ func TestParseArgs(t *testing.T) { }) } } + +func TestParseArgsDeleteEnvLock(t *testing.T) { + type testCase struct { + name string + cmdArgs []string + expectedParams LockParameters + expectedError error + } + + tcs := []testCase{ + { + name: "with environment and lockID and message", + cmdArgs: []string{"--environment", "development", "--lockID", "my-lock"}, + expectedParams: &DeleteEnvironmentLockParameters{ + Environment: "development", + LockId: "my-lock", + UseDexAuthentication: false, + }, + }, + } + + for _, tc := range tcs { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + params, err := ParseArgsDeleteEnvironmentLock(tc.cmdArgs) + // check errors + if diff := cmp.Diff(tc.expectedError, err, cmpopts.EquateErrors()); diff != "" { + t.Fatalf("error mismatch (-want, +got):\n%s", diff) + } + + // check result + if diff := cmp.Diff(tc.expectedParams, params); diff != "" { + t.Fatalf("expected args:\n %v\n, got:\n %v\n, diff:\n %s\n", tc.expectedParams, params, diff) + } + }) + } +} diff --git a/cli/pkg/locks/lock.go b/cli/pkg/locks/lock.go index 9a1480b6b..281a2362b 100644 --- a/cli/pkg/locks/lock.go +++ b/cli/pkg/locks/lock.go @@ -28,17 +28,22 @@ import ( ) type LockParameters interface { - GetRestPath() string - FillForm() (*HttpFormDataInfo, error) + FillHttpInfo() (*HttpInfo, error) } -type EnvironmentLockParameters struct { +type CreateEnvironmentLockParameters struct { Environment string LockId string Message string UseDexAuthentication bool } +type DeleteEnvironmentLockParameters struct { + Environment string + LockId string + UseDexAuthentication bool +} + type AppLockParameters struct { Environment string LockId string @@ -66,18 +71,19 @@ type LockJsonData struct { Message string `json:"message"` } -type HttpFormDataInfo struct { +type HttpInfo struct { jsonData []byte ContentType string + HttpMethod string + RestPath string } -func CreateLock(requestParams kutil.RequestParameters, authParams kutil.AuthenticationParameters, params LockParameters) error { - restPath := params.GetRestPath() - data, err := params.FillForm() +func HandleLockRequest(requestParams kutil.RequestParameters, authParams kutil.AuthenticationParameters, params LockParameters) error { + data, err := params.FillHttpInfo() if err != nil { return fmt.Errorf("error while preparing HTTP request. Could not fill form error: %w", err) } - req, err := createHttpRequest(*requestParams.Url, restPath, authParams, data) + req, err := createHttpRequest(*requestParams.Url, authParams, data) if err != nil { return fmt.Errorf("error while preparing HTTP request, error: %w", err) @@ -88,39 +94,39 @@ func CreateLock(requestParams kutil.RequestParameters, authParams kutil.Authenti return nil } -func (e *EnvironmentLockParameters) GetRestPath() string { - prefix := "environments" - if e.UseDexAuthentication { - prefix = "api/environments" - } - - return fmt.Sprintf("%s/%s/locks/%s", prefix, e.Environment, e.LockId) -} - -func (e *EnvironmentLockParameters) FillForm() (*HttpFormDataInfo, error) { +func (e *CreateEnvironmentLockParameters) FillHttpInfo() (*HttpInfo, error) { d := LockJsonData{ Message: e.Message, } + var jsonData, err = json.Marshal(d) if err != nil { return nil, fmt.Errorf("Could not EnvironmentLockParameters data to json: %w\n", err) } - return &HttpFormDataInfo{ + + prefix := "environments" + return &HttpInfo{ jsonData: jsonData, ContentType: "application/json", + HttpMethod: http.MethodPut, + RestPath: fmt.Sprintf("%s/%s/locks/%s", prefix, e.Environment, e.LockId), }, nil } -func (e *AppLockParameters) GetRestPath() string { +func (e *DeleteEnvironmentLockParameters) FillHttpInfo() (*HttpInfo, error) { prefix := "environments" if e.UseDexAuthentication { prefix = "api/environments" } - - return fmt.Sprintf("%s/%s/applications/%s/locks/%s", prefix, e.Environment, e.Application, e.LockId) + return &HttpInfo{ + jsonData: []byte{}, + ContentType: "application/json", + HttpMethod: http.MethodDelete, + RestPath: fmt.Sprintf("%s/%s/locks/%s", prefix, e.Environment, e.LockId), + }, nil } -func (e *AppLockParameters) FillForm() (*HttpFormDataInfo, error) { +func (e *AppLockParameters) FillHttpInfo() (*HttpInfo, error) { d := LockJsonData{ Message: e.Message, } @@ -128,18 +134,16 @@ func (e *AppLockParameters) FillForm() (*HttpFormDataInfo, error) { if err != nil { return nil, fmt.Errorf("Could not marshal AppLockParameters data to json: %w\n", err) } - return &HttpFormDataInfo{ + prefix := "environments" + return &HttpInfo{ jsonData: jsonData, ContentType: "application/json", + HttpMethod: http.MethodPut, + RestPath: fmt.Sprintf("%s/%s/applications/%s/locks/%s", prefix, e.Environment, e.Application, e.LockId), }, nil } -func (e *TeamLockParameters) GetRestPath() string { - prefix := "api/environments" - return fmt.Sprintf("%s/%s/lock/team/%s/%s", prefix, e.Environment, e.Team, e.LockId) -} - -func (e *TeamLockParameters) FillForm() (*HttpFormDataInfo, error) { +func (e *TeamLockParameters) FillHttpInfo() (*HttpInfo, error) { d := LockJsonData{ Message: e.Message, } @@ -147,22 +151,16 @@ func (e *TeamLockParameters) FillForm() (*HttpFormDataInfo, error) { if err != nil { return nil, fmt.Errorf("Could not marshal TeamLockParameters data to json: %w\n", err) } - return &HttpFormDataInfo{ + prefix := "api/environments" + return &HttpInfo{ jsonData: jsonData, ContentType: "application/json", + HttpMethod: http.MethodPut, + RestPath: fmt.Sprintf("%s/%s/lock/team/%s/%s", prefix, e.Environment, e.Team, e.LockId), }, nil } -func (e *EnvironmentGroupLockParameters) GetRestPath() string { - prefix := "environment-groups" - if e.UseDexAuthentication { - prefix = "api/environment-groups" - } - - return fmt.Sprintf("%s/%s/locks/%s", prefix, e.EnvironmentGroup, e.LockId) -} - -func (e *EnvironmentGroupLockParameters) FillForm() (*HttpFormDataInfo, error) { +func (e *EnvironmentGroupLockParameters) FillHttpInfo() (*HttpInfo, error) { d := LockJsonData{ Message: e.Message, } @@ -170,19 +168,25 @@ func (e *EnvironmentGroupLockParameters) FillForm() (*HttpFormDataInfo, error) { if err != nil { return nil, fmt.Errorf("Could not marshal EnvironmentGroupLockParameters data to json: %w\n", err) } - return &HttpFormDataInfo{ + prefix := "environment-groups" + if e.UseDexAuthentication { + prefix = "api/environment-groups" + } + return &HttpInfo{ jsonData: jsonData, ContentType: "application/json", + HttpMethod: http.MethodPut, + RestPath: fmt.Sprintf("%s/%s/locks/%s", prefix, e.EnvironmentGroup, e.LockId), }, nil } -func createHttpRequest(url string, path string, authParams kutil.AuthenticationParameters, requestInfo *HttpFormDataInfo) (*http.Request, error) { +func createHttpRequest(url string, authParams kutil.AuthenticationParameters, requestInfo *HttpInfo) (*http.Request, error) { urlStruct, err := urllib.Parse(url) if err != nil { return nil, fmt.Errorf("the provided url %s is invalid, error: %w", url, err) } - req, err := http.NewRequest(http.MethodPut, urlStruct.JoinPath(path).String(), bytes.NewBuffer(requestInfo.jsonData)) + req, err := http.NewRequest(requestInfo.HttpMethod, urlStruct.JoinPath(requestInfo.RestPath).String(), bytes.NewBuffer(requestInfo.jsonData)) if err != nil { return nil, fmt.Errorf("error creating the HTTP request, error: %w", err) }