Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): create team locks #1888

Merged
merged 20 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cli/pkg/cmd/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions cli/pkg/cmd/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
30 changes: 30 additions & 0 deletions cli/pkg/locks/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
Expand Down Expand Up @@ -119,6 +127,28 @@ func (e *AppLockParameters) FillForm() (*HttpFormDataInfo, error) {
}, nil
}

func (e *TeamLockParameters) GetRestPath() 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 {
Expand Down
108 changes: 108 additions & 0 deletions cli/pkg/locks/team_lock_parsing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*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 <https://directory.fsf.org/wiki/License:Expat>.

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, "if set to true, the /api/* endpoint will be used. Dex must be enabled on the server side and a 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{
LockId: cmdArgs.lockId.Values[0],
Environment: cmdArgs.environment.Values[0],
Team: cmdArgs.team.Values[0],
UseDexAuthentication: cmdArgs.useDexAuthentication,
Message: "",
}
if len(cmdArgs.message.Values) != 0 {
rp.Message = cmdArgs.message.Values[0]
}
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 for team lock, error: %w", err)
}
rp, err := convertToCreateTeamLockParams(*cmdArgs)
if err != nil {
return nil, fmt.Errorf("error while creating parameters for team lock, error: %w", err)
}
return rp, nil
}
190 changes: 190 additions & 0 deletions cli/pkg/locks/team_lock_parsing_test.go
Original file line number Diff line number Diff line change
@@ -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 <https://directory.fsf.org/wiki/License:Expat>.

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)
}
})
}
}
Loading