diff --git a/cli/pkg/cmd/cli.go b/cli/pkg/cmd/cli.go index 02802830b..295968fb7 100644 --- a/cli/pkg/cmd/cli.go +++ b/cli/pkg/cmd/cli.go @@ -77,6 +77,8 @@ func RunCLI() ReturnCode { return handleCreateGroupLock(*kpClientParams, subflags) case "delete-env-lock": return handleDeleteEnvLock(*kpClientParams, subflags) + case "delete-app-lock": + return handleDeleteAppLock(*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 229766ce1..a2e1e8dd4 100644 --- a/cli/pkg/cmd/handlers.go +++ b/cli/pkg/cmd/handlers.go @@ -73,6 +73,16 @@ func handleCreateEnvLock(kpClientParams kuberpultClientParameters, args []string return handleLockRequest(kpClientParams, parsedArgs) } +func handleDeleteAppLock(kpClientParams kuberpultClientParameters, args []string) ReturnCode { + parsedArgs, err := locks.ParseArgsDeleteAppLock(args) + + if err != nil { + log.Printf("error while parsing command line args, error: %v", err) + return ReturnCodeInvalidArguments + } + return handleLockRequest(kpClientParams, parsedArgs) +} + func handleCreateAppLock(kpClientParams kuberpultClientParameters, args []string) ReturnCode { parsedArgs, err := locks.ParseArgsCreateAppLock(args) diff --git a/cli/pkg/locks/app_lock_parsing.go b/cli/pkg/locks/app_lock_parsing.go index a76ac9159..0d868be3e 100644 --- a/cli/pkg/locks/app_lock_parsing.go +++ b/cli/pkg/locks/app_lock_parsing.go @@ -78,7 +78,7 @@ func convertToCreateAppLockParams(cmdArgs CreateAppLockCommandLineArguments) (Lo return nil, fmt.Errorf("the provided command line arguments structure is invalid, cause: %s", msg) } - rp := AppLockParameters{ + rp := CreateAppLockParameters{ LockId: cmdArgs.lockId.Values[0], Environment: cmdArgs.environment.Values[0], Application: cmdArgs.application.Values[0], @@ -102,3 +102,73 @@ func ParseArgsCreateAppLock(args []string) (LockParameters, error) { } return rp, nil } + +type DeleteAppLockCommandLineArguments struct { + environment cli_utils.RepeatedString + lockId cli_utils.RepeatedString + application cli_utils.RepeatedString +} + +func argsValidDeleteAppLock(cmdArgs *DeleteAppLockCommandLineArguments) (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" + } + if len(cmdArgs.application.Values) != 1 { + return false, "the --application arg must be set exactly once" + } + + return true, "" +} + +func readDeleteAppLockArgs(args []string) (*DeleteAppLockCommandLineArguments, error) { + cmdArgs := DeleteAppLockCommandLineArguments{} //exhaustruct:ignore + + fs := flag.NewFlagSet("flag set", flag.ContinueOnError) + + fs.Var(&cmdArgs.lockId, "lockID", "the ID of the lock you are trying to delete") + fs.Var(&cmdArgs.environment, "environment", "the environment of the lock you are trying to delete") + fs.Var(&cmdArgs.application, "application", "the application 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 := argsValidDeleteAppLock(&cmdArgs); !ok { + return nil, fmt.Errorf(msg) + } + + return &cmdArgs, nil +} + +func convertToDeleteAppLockParams(cmdArgs DeleteAppLockCommandLineArguments) (LockParameters, error) { + if ok, msg := argsValidDeleteAppLock(&cmdArgs); !ok { + return nil, fmt.Errorf("the provided command line arguments structure is invalid, cause: %s", msg) + } + + rp := DeleteAppLockParameters{ + LockId: cmdArgs.lockId.Values[0], + Environment: cmdArgs.environment.Values[0], + Application: cmdArgs.application.Values[0], + UseDexAuthentication: false, + } + return &rp, nil +} + +func ParseArgsDeleteAppLock(args []string) (LockParameters, error) { + cmdArgs, err := readDeleteAppLockArgs(args) + if err != nil { + return nil, fmt.Errorf("error while reading command line arguments for deleting an app lock, error: %w", err) + } + rp, err := convertToDeleteAppLockParams(*cmdArgs) + if err != nil { + return nil, fmt.Errorf("error while creating parameters for deleting an application lock, error: %w", err) + } + return rp, nil +} diff --git a/cli/pkg/locks/app_lock_parsing_test.go b/cli/pkg/locks/app_lock_parsing_test.go index fd44a4a2a..3daea5beb 100644 --- a/cli/pkg/locks/app_lock_parsing_test.go +++ b/cli/pkg/locks/app_lock_parsing_test.go @@ -129,6 +129,112 @@ func TestReadArgsAppLock(t *testing.T) { } } +func TestReadArgsDeleteAppLock(t *testing.T) { + type testCase struct { + name string + args []string + expectedCmdArgs *DeleteAppLockCommandLineArguments + 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: "lockID is not provided", + args: []string{"--environment", "development", "--application", "my-app"}, + expectedError: errMatcher{ + msg: "the --lockID arg must be set exactly once", + }, + }, + { + name: "environment is not provided", + args: []string{"--application", "my-app", "--lockID", "my-lock"}, + expectedError: errMatcher{ + msg: "the --environment arg must be set exactly once", + }, + }, + { + name: "application is not provided", + args: []string{"--environment", "development", "--lockID", "my-lock"}, + expectedError: errMatcher{ + msg: "the --application 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 by delete", + args: []string{"--environment", "development", "--application", "my-app", "--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 and application are specified", + args: []string{"--environment", "development", "--application", "my-app", "--lockID", "my-lock"}, + expectedCmdArgs: &DeleteAppLockCommandLineArguments{ + environment: cli_utils.RepeatedString{ + Values: []string{ + "development", + }, + }, + lockId: cli_utils.RepeatedString{ + Values: []string{ + "my-lock", + }, + }, + application: cli_utils.RepeatedString{ + Values: []string{ + "my-app", + }, + }, + }, + }, + } + + for _, tc := range tcs { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + cmdArgs, err := readDeleteAppLockArgs(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(DeleteAppLockCommandLineArguments{})); diff != "" { + t.Fatalf("expected args:\n %v\n, got:\n %v, diff:\n %s\n", tc.expectedCmdArgs, cmdArgs, diff) + } + }) + } +} + func TestParseArgsCreateAppLock(t *testing.T) { type testCase struct { name string @@ -141,7 +247,7 @@ func TestParseArgsCreateAppLock(t *testing.T) { { name: "with environment and lockID and message", cmdArgs: []string{"--environment", "development", "--application", "my-app", "--lockID", "my-lock", "--message", "message"}, - expectedParams: &AppLockParameters{ + expectedParams: &CreateAppLockParameters{ Environment: "development", LockId: "my-lock", Message: "message", @@ -151,7 +257,7 @@ func TestParseArgsCreateAppLock(t *testing.T) { { name: "with environment, app and lockID and no message", cmdArgs: []string{"--environment", "development", "--application", "my-app", "--lockID", "my-lock"}, - expectedParams: &AppLockParameters{ + expectedParams: &CreateAppLockParameters{ Environment: "development", LockId: "my-lock", Message: "", @@ -162,7 +268,7 @@ func TestParseArgsCreateAppLock(t *testing.T) { { name: "with environment and lockID and multi word message message", cmdArgs: []string{"--environment", "development", "--application", "my-app", "--lockID", "my-lock", "--message", "this is a very long message"}, - expectedParams: &AppLockParameters{ + expectedParams: &CreateAppLockParameters{ Environment: "development", LockId: "my-lock", Application: "my-app", @@ -189,3 +295,42 @@ func TestParseArgsCreateAppLock(t *testing.T) { }) } } + +func TestParseArgsDeleteAppLock(t *testing.T) { + type testCase struct { + name string + cmdArgs []string + expectedParams LockParameters + expectedError error + } + + tcs := []testCase{ + { + name: "with environment, lockID and app", + cmdArgs: []string{"--environment", "development", "--application", "my-app", "--lockID", "my-lock"}, + expectedParams: &DeleteAppLockParameters{ + Environment: "development", + LockId: "my-lock", + Application: "my-app", + }, + }, + } + + for _, tc := range tcs { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + params, err := ParseArgsDeleteAppLock(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 281a2362b..4030b6082 100644 --- a/cli/pkg/locks/lock.go +++ b/cli/pkg/locks/lock.go @@ -44,13 +44,19 @@ type DeleteEnvironmentLockParameters struct { UseDexAuthentication bool } -type AppLockParameters struct { +type CreateAppLockParameters struct { Environment string LockId string Message string Application string UseDexAuthentication bool } +type DeleteAppLockParameters struct { + Environment string + LockId string + Application string + UseDexAuthentication bool +} type TeamLockParameters struct { Environment string @@ -98,13 +104,15 @@ 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) } prefix := "environments" + if e.UseDexAuthentication { + prefix = "api/environments" + } return &HttpInfo{ jsonData: jsonData, ContentType: "application/json", @@ -126,15 +134,18 @@ func (e *DeleteEnvironmentLockParameters) FillHttpInfo() (*HttpInfo, error) { }, nil } -func (e *AppLockParameters) FillHttpInfo() (*HttpInfo, error) { +func (e *CreateAppLockParameters) FillHttpInfo() (*HttpInfo, error) { d := LockJsonData{ Message: e.Message, } var jsonData, err = json.Marshal(d) if err != nil { - return nil, fmt.Errorf("Could not marshal AppLockParameters data to json: %w\n", err) + return nil, fmt.Errorf("Could not marshal CreateAppLockParameters data to json: %w\n", err) } prefix := "environments" + if e.UseDexAuthentication { + prefix = "api/environments" + } return &HttpInfo{ jsonData: jsonData, ContentType: "application/json", @@ -143,6 +154,19 @@ func (e *AppLockParameters) FillHttpInfo() (*HttpInfo, error) { }, nil } +func (e *DeleteAppLockParameters) FillHttpInfo() (*HttpInfo, error) { + prefix := "environments" + if e.UseDexAuthentication { + prefix = "api/environments" + } + return &HttpInfo{ + jsonData: []byte{}, + ContentType: "application/json", + HttpMethod: http.MethodDelete, + RestPath: fmt.Sprintf("%s/%s/applications/%s/locks/%s", prefix, e.Environment, e.Application, e.LockId), + }, nil +} + func (e *TeamLockParameters) FillHttpInfo() (*HttpInfo, error) { d := LockJsonData{ Message: e.Message, @@ -151,6 +175,7 @@ func (e *TeamLockParameters) FillHttpInfo() (*HttpInfo, error) { if err != nil { return nil, fmt.Errorf("Could not marshal TeamLockParameters data to json: %w\n", err) } + prefix := "api/environments" return &HttpInfo{ jsonData: jsonData,