From 9954b83bbfe3704f313d595c4b99751b58f83656 Mon Sep 17 00:00:00 2001 From: Miguel Crespo Date: Fri, 16 Aug 2024 11:46:49 +0100 Subject: [PATCH] team locks --- cli/pkg/cmd/cli.go | 2 + cli/pkg/cmd/handlers.go | 10 ++ cli/pkg/locks/app_lock_parsing_test.go | 1 - cli/pkg/locks/lock.go | 30 ++++ cli/pkg/locks/team_lock_parsing.go | 106 +++++++++++++ cli/pkg/locks/team_lock_parsing_test.go | 190 ++++++++++++++++++++++++ 6 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 cli/pkg/locks/team_lock_parsing.go create mode 100644 cli/pkg/locks/team_lock_parsing_test.go diff --git a/cli/pkg/cmd/cli.go b/cli/pkg/cmd/cli.go index 6efd4638ae..142218e0af 100644 --- a/cli/pkg/cmd/cli.go +++ b/cli/pkg/cmd/cli.go @@ -71,6 +71,8 @@ func RunCLI() ReturnCode { return handleCreateEnvLock(*kpClientParams, subflags) case "create-app-lock": return handleCreateAppLock(*kpClientParams, subflags) + case "create-team-lock": + return handleCreateTeamLock(*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 19f6be1000..5f81e654b0 100644 --- a/cli/pkg/cmd/handlers.go +++ b/cli/pkg/cmd/handlers.go @@ -73,6 +73,16 @@ func handleCreateAppLock(kpClientParams kuberpultClientParameters, args []string return handleCreateLock(kpClientParams, parsedArgs) } +func handleCreateTeamLock(kpClientParams kuberpultClientParameters, args []string) ReturnCode { + parsedArgs, err := locks.ParseArgsCreateTeamLock(args) + + if err != nil { + log.Printf("error while parsing command line args, error: %v", err) + return ReturnCodeInvalidArguments + } + return handleCreateLock(kpClientParams, parsedArgs) +} + func handleCreateLock(kpClientParams kuberpultClientParameters, parsedArgs locks.LockParameters) ReturnCode { authParams := kutil.AuthenticationParameters{ IapToken: kpClientParams.iapToken, diff --git a/cli/pkg/locks/app_lock_parsing_test.go b/cli/pkg/locks/app_lock_parsing_test.go index fd44a4a2af..6bcdef5b0d 100644 --- a/cli/pkg/locks/app_lock_parsing_test.go +++ b/cli/pkg/locks/app_lock_parsing_test.go @@ -158,7 +158,6 @@ func TestParseArgsCreateAppLock(t *testing.T) { Application: "my-app", }, }, - { 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"}, diff --git a/cli/pkg/locks/lock.go b/cli/pkg/locks/lock.go index 0e61bb5f78..9abadd4fc5 100644 --- a/cli/pkg/locks/lock.go +++ b/cli/pkg/locks/lock.go @@ -47,6 +47,14 @@ type AppLockParameters struct { UseDexAuthentication bool } +type TeamLockParameters struct { + Environment string + LockId string + Message string + Team string + UseDexAuthentication bool +} + type LockJsonData struct { Message string `json:"message"` } @@ -119,6 +127,28 @@ func (e *AppLockParameters) FillForm() (*HttpFormDataInfo, error) { }, nil } +func (e *TeamLockParameters) GetPath() string { + prefix := "environments" + if e.UseDexAuthentication { + 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) { + d := LockJsonData{ + Message: e.Message, + } + var jsonData, err = json.Marshal(d) + if err != nil { + return nil, fmt.Errorf("Could not marshal TeamLockParameters data to json: %w\n", err) + } + return &HttpFormDataInfo{ + jsonData: jsonData, + ContentType: "application/json", + }, nil +} + func createHttpRequest(url string, path string, authParams kutil.AuthenticationParameters, requestInfo *HttpFormDataInfo) (*http.Request, error) { urlStruct, err := urllib.Parse(url) if err != nil { diff --git a/cli/pkg/locks/team_lock_parsing.go b/cli/pkg/locks/team_lock_parsing.go new file mode 100644 index 0000000000..e6aea54141 --- /dev/null +++ b/cli/pkg/locks/team_lock_parsing.go @@ -0,0 +1,106 @@ +/*This file is part of kuberpult. + +Kuberpult is free software: you can redistribute it and/or modify +it under the terms of the Expat(MIT) License as published by +the Free Software Foundation. + +Kuberpult is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +MIT License for more details. + +You should have received a copy of the MIT License +along with kuberpult. If not, see . + +Copyright freiheit.com*/ + +package locks + +import ( + "flag" + "fmt" + "strings" + + "github.com/freiheit-com/kuberpult/cli/pkg/cli_utils" +) + +type CreateTeamLockCommandLineArguments struct { + environment cli_utils.RepeatedString + lockId cli_utils.RepeatedString + message cli_utils.RepeatedString + team cli_utils.RepeatedString + useDexAuthentication bool +} + +func argsValidCreateTeamLock(cmdArgs *CreateTeamLockCommandLineArguments) (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.team.Values) != 1 { + return false, "the --team arg must be set exactly once" + } + if len(cmdArgs.message.Values) > 1 { + return false, "the --message arg must be set at most once" + } + + return true, "" +} + +func readCreateTeamLockArgs(args []string) (*CreateTeamLockCommandLineArguments, error) { + cmdArgs := CreateTeamLockCommandLineArguments{} //exhaustruct:ignore + + fs := flag.NewFlagSet("flag set", flag.ContinueOnError) + + fs.Var(&cmdArgs.lockId, "lockID", "the ID of the lock you are trying to create") + fs.Var(&cmdArgs.environment, "environment", "the environment to lock") + fs.Var(&cmdArgs.message, "message", "lock message") + fs.Var(&cmdArgs.team, "team", "application to lock") + fs.BoolVar(&cmdArgs.useDexAuthentication, "use_dex_auth", false, "use /api/* endpoint, if set to true, dex must be enabled and dex token must be provided otherwise the request will be denied") + + 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 := argsValidCreateTeamLock(&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 release endpoint +func convertToCreateTeamLockParams(cmdArgs CreateTeamLockCommandLineArguments) (LockParameters, error) { + if ok, msg := argsValidCreateTeamLock(&cmdArgs); !ok { + // this should never happen, as the validation is already peformed by the readArgs function + return nil, fmt.Errorf("the provided command line arguments structure is invalid, cause: %s", msg) + } + + rp := TeamLockParameters{} //exhaustruct:ignore + rp.LockId = cmdArgs.lockId.Values[0] + rp.Environment = cmdArgs.environment.Values[0] + if len(cmdArgs.message.Values) != 0 { + rp.Message = cmdArgs.message.Values[0] + } + rp.Team = cmdArgs.team.Values[0] + rp.UseDexAuthentication = cmdArgs.useDexAuthentication + return &rp, nil +} + +func ParseArgsCreateTeamLock(args []string) (LockParameters, error) { + cmdArgs, err := readCreateTeamLockArgs(args) + if err != nil { + return nil, fmt.Errorf("error while reading command line arguments, error: %w", err) + } + rp, err := convertToCreateTeamLockParams(*cmdArgs) + if err != nil { + return nil, fmt.Errorf("error while creating /release endpoint params, error: %w", err) + } + return rp, nil +} diff --git a/cli/pkg/locks/team_lock_parsing_test.go b/cli/pkg/locks/team_lock_parsing_test.go new file mode 100644 index 0000000000..93483a3f43 --- /dev/null +++ b/cli/pkg/locks/team_lock_parsing_test.go @@ -0,0 +1,190 @@ +/*This file is part of kuberpult. + +Kuberpult is free software: you can redistribute it and/or modify +it under the terms of the Expat(MIT) License as published by +the Free Software Foundation. + +Kuberpult is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +MIT License for more details. + +You should have received a copy of the MIT License +along with kuberpult. If not, see . + +Copyright freiheit.com*/ + +package locks + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/freiheit-com/kuberpult/cli/pkg/cli_utils" +) + +func TestReadArgsTeamLock(t *testing.T) { + type testCase struct { + name string + args []string + expectedCmdArgs *CreateTeamLockCommandLineArguments + 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", "--team", "my-team", "--message", "\"my message\""}, + expectedError: errMatcher{ + msg: "the --lockID arg must be set exactly once", + }, + }, + { + name: "environment is not provided", + args: []string{"--team", "my-team", "--lockID", "my-lock", "--message", "\"my message\""}, + expectedError: errMatcher{ + msg: "the --environment arg must be set exactly once", + }, + }, + { + name: "application is not provided", + args: []string{"--environment", "development", "--lockID", "my-lock", "--message", "\"my message\""}, + expectedError: errMatcher{ + msg: "the --team 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: "environment, lockID, application and message are are specified", + args: []string{"--environment", "development", "--team", "my-team", "--lockID", "my-lock", "--message", "\"my message\""}, + expectedCmdArgs: &CreateTeamLockCommandLineArguments{ + environment: cli_utils.RepeatedString{ + Values: []string{ + "development", + }, + }, + lockId: cli_utils.RepeatedString{ + Values: []string{ + "my-lock", + }, + }, + message: cli_utils.RepeatedString{ + Values: []string{ + "\"my message\"", + }, + }, + team: cli_utils.RepeatedString{ + Values: []string{ + "my-team", + }, + }, + }, + }, + } + + for _, tc := range tcs { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + cmdArgs, err := readCreateTeamLockArgs(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(CreateTeamLockCommandLineArguments{})); diff != "" { + t.Fatalf("expected args:\n %v\n, got:\n %v, diff:\n %s\n", tc.expectedCmdArgs, cmdArgs, diff) + } + }) + } +} + +func TestParseArgsCreateTeamLock(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", "--team", "my-team", "--lockID", "my-lock", "--message", "message"}, + expectedParams: &TeamLockParameters{ + Environment: "development", + LockId: "my-lock", + Message: "message", + Team: "my-team", + }, + }, + { + name: "with environment, app and lockID and no message", + cmdArgs: []string{"--environment", "development", "--team", "my-team", "--lockID", "my-lock"}, + expectedParams: &TeamLockParameters{ + Environment: "development", + LockId: "my-lock", + Message: "", + Team: "my-team", + }, + }, + { + name: "with environment and lockID and multi word message message", + cmdArgs: []string{"--environment", "development", "--team", "my-team", "--lockID", "my-lock", "--message", "this is a very long message"}, + expectedParams: &TeamLockParameters{ + Environment: "development", + LockId: "my-lock", + Team: "my-team", + Message: "this is a very long message", + }, + }, + } + + for _, tc := range tcs { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + params, err := ParseArgsCreateTeamLock(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) + } + }) + } +}