diff --git a/cloud/apitoken/apitoken.go b/cloud/apitoken/apitoken.go new file mode 100644 index 000000000..79339fad4 --- /dev/null +++ b/cloud/apitoken/apitoken.go @@ -0,0 +1,257 @@ +package apitoken + +import ( + httpContext "context" + "errors" + "fmt" + "io" + "os" + "strconv" + "time" + + astrocore "github.com/astronomer/astro-cli/astro-client-core" + "github.com/astronomer/astro-cli/context" + "github.com/astronomer/astro-cli/pkg/ansi" + "github.com/astronomer/astro-cli/pkg/input" + "github.com/astronomer/astro-cli/pkg/printutil" +) + +var ( + ErrInvalidAPITokenKey = errors.New("invalid ApiToken selected") + ErrNoAPITokenNameProvided = errors.New("you must give your ApiToken a name") + ErrNoAPITokensFoundInDeployment = errors.New("no ApiTokens found in your deployment") + apiTokenPagnationLimit = 100 +) + +func CreateDeploymentAPIToken(name, role, description, deployment string, out io.Writer, client astrocore.CoreClient) error { + ctx, err := context.GetCurrentContext() + if err != nil { + return err + } + + if name == "" { + fmt.Println("Please specify a name for your ApiToken") + name = input.Text(ansi.Bold("\nApiToken name: ")) + if name == "" { + return ErrNoAPITokenNameProvided + } + } + + mutateAPITokenInput := astrocore.CreateDeploymentApiTokenRequest{ + Role: role, + Name: name, + Description: &description, + } + resp, err := client.CreateDeploymentApiTokenWithResponse(httpContext.Background(), ctx.Organization, deployment, mutateAPITokenInput) + if err != nil { + return err + } + err = astrocore.NormalizeAPIError(resp.HTTPResponse, resp.Body) + if err != nil { + return err + } + fmt.Fprintf(out, "The apiToken was successfully created with the role %s\n", role) + return nil +} + +func UpdateDeploymentAPITokenRole(apiTokenID, role, deployment string, out io.Writer, client astrocore.CoreClient) error { + ctx, err := context.GetCurrentContext() + if err != nil { + return err + } + var apiToken *astrocore.ApiToken + + if apiTokenID == "" { + // Get all dep apiTokens. Setting limit to 1000 for now + limit := 1000 + apiTokens, err := GetDeploymentAPITokens(client, deployment, limit) + if err != nil { + return err + } + apiToken, err = getAPIToken(apiTokens) + if err != nil { + return err + } + apiTokenID = apiToken.Id + } else { + resp, err := client.GetDeploymentApiTokenWithResponse(httpContext.Background(), ctx.Organization, deployment, apiTokenID) + if err != nil { + fmt.Println("error in GetDeploymentApiTokenWithResponse") + return err + } + err = astrocore.NormalizeAPIError(resp.HTTPResponse, resp.Body) + if err != nil { + return err + } + apiToken = resp.JSON200 + } + + mutateAPITokenInput := astrocore.UpdateDeploymentApiTokenRequest{ + Role: role, + Name: apiToken.Name, + } + fmt.Println("deployment: " + deployment) + resp, err := client.UpdateDeploymentApiTokenWithResponse(httpContext.Background(), ctx.Organization, deployment, apiToken.Id, mutateAPITokenInput) + if err != nil { + fmt.Println("error in MutateDeploymentApiTokenRoleWithResponse") + return err + } + err = astrocore.NormalizeAPIError(resp.HTTPResponse, resp.Body) + if err != nil { + return err + } + fmt.Fprintf(out, "The deployment apiToken %s role was successfully updated to %s\n", apiTokenID, role) + return nil +} + +func getAPIToken(apitokens []astrocore.ApiToken) (*astrocore.ApiToken, error) { + if len(apitokens) == 0 { + return nil, ErrNoAPITokensFoundInDeployment + } + apiToken, err := SelectDeploymentAPIToken(apitokens) + if err != nil { + return nil, err + } + + return &apiToken, nil +} + +func SelectDeploymentAPIToken(apiTokens []astrocore.ApiToken) (astrocore.ApiToken, error) { + table := printutil.Table{ + Padding: []int{30, 50, 10, 50, 10, 10, 10}, + DynamicPadding: true, + Header: []string{"#", "NAME", "DEPLOYMENT_ROLE", "DESCRIPTION", "ID", "CREATE DATE", "UPDATE DATE"}, + } + + fmt.Println("\nPlease select the api token:") + + apiTokenMap := map[string]astrocore.ApiToken{} + for i := range apiTokens { + index := i + 1 + var role string + for _, tokenRole := range apiTokens[i].Roles { + if tokenRole.EntityType == "DEPLOYMENT" { + role = tokenRole.Role + } + } + + table.AddRow([]string{ + strconv.Itoa(index), + apiTokens[i].Name, + role, + apiTokens[i].Description, + apiTokens[i].Id, + apiTokens[i].CreatedAt.Format(time.RFC3339), + apiTokens[i].UpdatedAt.Format(time.RFC3339), + }, false) + + apiTokenMap[strconv.Itoa(index)] = apiTokens[i] + } + + table.Print(os.Stdout) + choice := input.Text("\n> ") + selected, ok := apiTokenMap[choice] + if !ok { + return astrocore.ApiToken{}, ErrInvalidAPITokenKey + } + return selected, nil +} + +func DeleteDeploymentAPIToken(apiTokenID, deployment string, out io.Writer, client astrocore.CoreClient) error { + ctx, err := context.GetCurrentContext() + if err != nil { + return err + } + + if apiTokenID == "" { + apiTokens, err := GetDeploymentAPITokens(client, deployment, apiTokenPagnationLimit) + if err != nil { + return err + } + apiToken, err := getAPIToken(apiTokens) + if err != nil { + return err + } + apiTokenID = apiToken.Id + } + + resp, err := client.DeleteDeploymentApiTokenWithResponse(httpContext.Background(), ctx.Organization, deployment, apiTokenID) + if err != nil { + return err + } + err = astrocore.NormalizeAPIError(resp.HTTPResponse, resp.Body) + if err != nil { + return err + } + fmt.Fprintf(out, "Astro ApiToken %s was successfully deleted from deployment %s\n", apiTokenID, deployment) + return nil +} + +// Returns a list of all of a deployments apiTokens +func GetDeploymentAPITokens(client astrocore.CoreClient, deployment string, limit int) ([]astrocore.ApiToken, error) { + offset := 0 + var apiTokens []astrocore.ApiToken + + ctx, err := context.GetCurrentContext() + if err != nil { + return nil, err + } + + for { + resp, err := client.ListDeploymentApiTokensWithResponse(httpContext.Background(), ctx.Organization, deployment, &astrocore.ListDeploymentApiTokensParams{ + Offset: &offset, + Limit: &limit, + }) + if err != nil { + return nil, err + } + err = astrocore.NormalizeAPIError(resp.HTTPResponse, resp.Body) + if err != nil { + return nil, err + } + apiTokens = append(apiTokens, resp.JSON200.ApiTokens...) + + if resp.JSON200.TotalCount <= offset { + break + } + + offset += limit + } + + return apiTokens, nil +} + +// Prints a list of all of an deployments apiTokens +// +//nolint:dupl +func ListDeploymentAPITokens(out io.Writer, client astrocore.CoreClient, deployment string) error { + table := printutil.Table{ + Padding: []int{30, 50, 10, 50, 10, 10, 10}, + DynamicPadding: true, + Header: []string{"NAME", "DESCRIPTION", "ID", "DEPLOYMENT ROLE", "CREATE DATE", "UPDATE DATE"}, + } + apiTokens, err := GetDeploymentAPITokens(client, deployment, apiTokenPagnationLimit) + if err != nil { + return err + } + + for i := range apiTokens { + var deploymentRole string + for _, role := range apiTokens[i].Roles { + if role.EntityId == deployment { + deploymentRole = role.Role + } + } + table.AddRow([]string{ + apiTokens[i].Name, + apiTokens[i].Description, + apiTokens[i].Id, + deploymentRole, + apiTokens[i].CreatedAt.Format(time.RFC3339), + apiTokens[i].UpdatedAt.Format(time.RFC3339), + }, false) + } + + table.Print(out) + return nil +} diff --git a/cloud/apitoken/apitoken_test.go b/cloud/apitoken/apitoken_test.go new file mode 100644 index 000000000..89e071e32 --- /dev/null +++ b/cloud/apitoken/apitoken_test.go @@ -0,0 +1,387 @@ +package apitoken + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + "testing" + "time" + + astrocore "github.com/astronomer/astro-cli/astro-client-core" + + astrocore_mocks "github.com/astronomer/astro-cli/astro-client-core/mocks" + "github.com/stretchr/testify/mock" + + testUtil "github.com/astronomer/astro-cli/pkg/testing" + "github.com/stretchr/testify/assert" +) + +var ( + description1 = "Description 1" + description2 = "Description 2" + fullName1 = "User 1" + fullName2 = "User 2" + token = "token" + workspaceID = "ck05r3bor07h40d02y2hw4n4v" + deploymentID = "ck05r3bor07h40d02y2hw4n4d" + role = "DEPLOYMENT_ADMIN" + deploymentAPIToken1 = astrocore.ApiToken{Id: "token1", Name: "Token 1", Token: &token, Description: description1, Type: "Type 1", Roles: []astrocore.ApiTokenRole{{EntityId: "DEPLOYMENT", Role: role}}, CreatedAt: time.Now(), CreatedBy: &astrocore.BasicSubjectProfile{FullName: &fullName1}} + errorNetwork = errors.New("network error") + CreateDeploymentAPITokenResponseOK = astrocore.CreateDeploymentApiTokenResponse{ + HTTPResponse: &http.Response{ + StatusCode: 200, + }, + JSON200: &deploymentAPIToken1, + } + errorBodyCreate, _ = json.Marshal(astrocore.Error{ + Message: "failed to create apiToken", + }) + CreateDeploymentAPITokenResponseError = astrocore.CreateDeploymentApiTokenResponse{ + HTTPResponse: &http.Response{ + StatusCode: 500, + }, + Body: errorBodyCreate, + } + + errorBodyDelete, _ = json.Marshal(astrocore.Error{ + Message: "failed to delete apiToken", + }) + + DeleteDeploymentAPITokenResponseOK = astrocore.DeleteDeploymentApiTokenResponse{ + HTTPResponse: &http.Response{ + StatusCode: 200, + }, + } + DeleteDeploymentAPITokenResponseError = astrocore.DeleteDeploymentApiTokenResponse{ + HTTPResponse: &http.Response{ + StatusCode: 500, + }, + Body: errorBodyDelete, + } + + paginatedAPITokens = astrocore.ListApiTokensPaginated{ + ApiTokens: []astrocore.ApiToken{ + deploymentAPIToken1, + }, + } + + ListDeploymentAPITokensResponseOK = astrocore.ListDeploymentApiTokensResponse{ + HTTPResponse: &http.Response{ + StatusCode: 200, + }, + JSON200: &paginatedAPITokens, + } + + paginatedAPITokensEmpty = astrocore.ListApiTokensPaginated{ + ApiTokens: []astrocore.ApiToken{}, + } + + ListDeploymentAPITokensResponseEmpty = astrocore.ListDeploymentApiTokensResponse{ + HTTPResponse: &http.Response{ + StatusCode: 200, + }, + JSON200: &paginatedAPITokensEmpty, + } + + errorListDeploymentAPITokens, _ = json.Marshal(astrocore.Error{ + Message: "failed to list deployment apiTokens", + }) + ListDeploymentAPITokensResponseError = astrocore.ListDeploymentApiTokensResponse{ + HTTPResponse: &http.Response{ + StatusCode: 500, + }, + Body: errorListDeploymentAPITokens, + } + GetDeploymentAPITokenWithResponseOK = astrocore.GetDeploymentApiTokenResponse{ + HTTPResponse: &http.Response{ + StatusCode: 200, + }, + JSON200: &deploymentAPIToken1, + } + errorGetDeploymentAPITokens, _ = json.Marshal(astrocore.Error{ + Message: "failed to get deployment apiToken", + }) + GetDeploymentAPITokenWithResponseError = astrocore.GetDeploymentApiTokenResponse{ + HTTPResponse: &http.Response{ + StatusCode: 500, + }, + Body: errorGetDeploymentAPITokens, + } + + UpdateDeploymentAPITokenRoleResponseOK = astrocore.UpdateDeploymentApiTokenResponse{ + HTTPResponse: &http.Response{ + StatusCode: 200, + }, + JSON200: &deploymentAPIToken1, + } + errorUpdateDeploymentAPIToken, _ = json.Marshal(astrocore.Error{ + Message: "failed to update deployment apiToken", + }) + UpdateDeploymentAPITokenRoleResponseError = astrocore.UpdateDeploymentApiTokenResponse{ + HTTPResponse: &http.Response{ + StatusCode: 500, + }, + Body: errorUpdateDeploymentAPIToken, + } +) + +func TestCreateDeploymentAPIToken(t *testing.T) { + testUtil.InitTestConfig(testUtil.LocalPlatform) + t.Run("happy path Create", func(t *testing.T) { + expectedOutMessage := fmt.Sprintf("The apiToken was successfully created with the role %s\n", role) + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("CreateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateDeploymentAPITokenResponseOK, nil).Once() + err := CreateDeploymentAPIToken(deploymentAPIToken1.Name, role, deploymentAPIToken1.Description, deploymentID, out, mockClient) + assert.NoError(t, err) + assert.Equal(t, expectedOutMessage, out.String()) + }) + + t.Run("happy path no name passed so user types one in when prompted", func(t *testing.T) { + teamName := "Test ApiToken Name" + expectedOutMessage := fmt.Sprintf("The apiToken was successfully created with the role %s\n", role) + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("CreateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateDeploymentAPITokenResponseOK, nil).Once() + defer testUtil.MockUserInput(t, teamName)() + err := CreateDeploymentAPIToken("", role, deploymentAPIToken1.Description, deploymentID, out, mockClient) + assert.NoError(t, err) + assert.Equal(t, expectedOutMessage, out.String()) + }) + + t.Run("error path when CreateDeploymentApiTokenWithResponse return network error", func(t *testing.T) { + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("CreateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errorNetwork).Once() + err := CreateDeploymentAPIToken(deploymentAPIToken1.Name, role, deploymentAPIToken1.Description, deploymentID, out, mockClient) + assert.EqualError(t, err, "network error") + }) + + t.Run("error path when CreateDeploymentApiTokenWithResponse returns an error", func(t *testing.T) { + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("CreateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateDeploymentAPITokenResponseError, nil).Once() + err := CreateDeploymentAPIToken(deploymentAPIToken1.Name, role, deploymentAPIToken1.Description, deploymentID, out, mockClient) + assert.EqualError(t, err, "failed to create apiToken") + }) + + t.Run("error path when getting current context returns an error", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.Initial) + expectedOutMessage := "" + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + err := CreateDeploymentAPIToken(deploymentAPIToken1.Name, role, deploymentAPIToken1.Description, deploymentID, out, mockClient) + assert.Error(t, err) + assert.Equal(t, expectedOutMessage, out.String()) + }) + t.Run("error path no name passed in and user doesn't type one in when prompted", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.LocalPlatform) + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + err := CreateDeploymentAPIToken("", role, deploymentAPIToken1.Description, deploymentID, out, mockClient) + assert.EqualError(t, err, "you must give your ApiToken a name") + }) +} + +func TestUpdateDeploymentAPITokenRole(t *testing.T) { + testUtil.InitTestConfig(testUtil.LocalPlatform) + t.Run("happy path UpdateDeploymentApiTokenRole", func(t *testing.T) { + expectedOutMessage := fmt.Sprintf("The deployment apiToken %s role was successfully updated to %s\n", deploymentAPIToken1.Id, role) + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("GetDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentAPITokenWithResponseOK, nil).Twice() + mockClient.On("UpdateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateDeploymentAPITokenRoleResponseOK, nil).Once() + err := UpdateDeploymentAPITokenRole(deploymentAPIToken1.Id, "DEPLOYMENT_ADMIN", deploymentID, out, mockClient) + assert.NoError(t, err) + assert.Equal(t, expectedOutMessage, out.String()) + }) + + t.Run("error path no deployment apiTokens found", func(t *testing.T) { + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseEmpty, nil).Twice() + err := UpdateDeploymentAPITokenRole("", "DEPLOYMENT_ADMIN", deploymentID, out, mockClient) + assert.EqualError(t, err, "no ApiTokens found in your deployment") + }) + + t.Run("error path when GetDeploymentApiTokenWithResponse return network error", func(t *testing.T) { + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("GetDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errorNetwork).Twice() + err := UpdateDeploymentAPITokenRole(deploymentAPIToken1.Id, "DEPLOYMENT_ADMIN", deploymentID, out, mockClient) + assert.EqualError(t, err, "network error") + }) + + t.Run("error path when UpdateDeploymentApiTokenWithResponse return network error", func(t *testing.T) { + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("GetDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentAPITokenWithResponseOK, nil).Twice() + mockClient.On("UpdateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errorNetwork).Once() + err := UpdateDeploymentAPITokenRole(deploymentAPIToken1.Id, "DEPLOYMENT_ADMIN", deploymentID, out, mockClient) + assert.EqualError(t, err, "network error") + }) + + t.Run("error path when UpdateDeploymentApiTokenWithResponse returns an error", func(t *testing.T) { + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("GetDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentAPITokenWithResponseOK, nil).Twice() + mockClient.On("UpdateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateDeploymentAPITokenRoleResponseError, nil).Once() + err := UpdateDeploymentAPITokenRole(deploymentAPIToken1.Id, "DEPLOYMENT_ADMIN", deploymentID, out, mockClient) + assert.EqualError(t, err, "failed to update deployment apiToken") + }) + + t.Run("error path when getting current context returns an error", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.Initial) + expectedOutMessage := "" + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + err := UpdateDeploymentAPITokenRole(deploymentAPIToken1.Id, "DEPLOYMENT_ADMIN", deploymentID, out, mockClient) + assert.Error(t, err) + assert.Equal(t, expectedOutMessage, out.String()) + }) + + t.Run("UpdateDeploymentApiTokenRole no id passed", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.LocalPlatform) + out := new(bytes.Buffer) + + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil).Twice() + // mock os.Stdin + expectedInput := []byte("1") + r, w, err := os.Pipe() + assert.NoError(t, err) + _, err = w.Write(expectedInput) + assert.NoError(t, err) + w.Close() + stdin := os.Stdin + // Restore stdin right after the test. + defer func() { os.Stdin = stdin }() + os.Stdin = r + + expectedOutMessage := fmt.Sprintf("The deployment apiToken %s role was successfully updated to %s\n", deploymentAPIToken1.Id, role) + mockClient.On("UpdateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&UpdateDeploymentAPITokenRoleResponseOK, nil).Once() + + err = UpdateDeploymentAPITokenRole("", "DEPLOYMENT_ADMIN", deploymentID, out, mockClient) + assert.NoError(t, err) + assert.Equal(t, expectedOutMessage, out.String()) + }) +} + +func TestDeleteDeploymentAPIToken(t *testing.T) { + testUtil.InitTestConfig(testUtil.LocalPlatform) + t.Run("happy path DeleteDeploymentApiToken", func(t *testing.T) { + expectedOutMessage := fmt.Sprintf("Astro ApiToken %s was successfully deleted from deployment %s\n", deploymentAPIToken1.Id, deploymentID) + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("DeleteDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentAPITokenResponseOK, nil).Once() + err := DeleteDeploymentAPIToken(deploymentAPIToken1.Id, deploymentID, out, mockClient) + assert.NoError(t, err) + assert.Equal(t, expectedOutMessage, out.String()) + }) + + t.Run("error path when DeleteDeploymentApiTokenWithResponse return network error", func(t *testing.T) { + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("DeleteDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errorNetwork).Once() + err := DeleteDeploymentAPIToken(deploymentAPIToken1.Id, "", out, mockClient) + assert.EqualError(t, err, "network error") + }) + + t.Run("error path when DeleteDeploymentApiTokenWithResponse returns an error", func(t *testing.T) { + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("DeleteDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentAPITokenResponseError, nil).Once() + err := DeleteDeploymentAPIToken(deploymentAPIToken1.Id, "", out, mockClient) + assert.EqualError(t, err, "failed to delete apiToken") + }) + + t.Run("error path when getting current context returns an error", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.Initial) + expectedOutMessage := "" + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + err := DeleteDeploymentAPIToken(deploymentAPIToken1.Id, "", out, mockClient) + assert.Error(t, err) + assert.Equal(t, expectedOutMessage, out.String()) + }) + + t.Run("RemoveDeploymentApiToken no apiToken id passed", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.LocalPlatform) + out := new(bytes.Buffer) + + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil).Twice() + // mock os.Stdin + expectedInput := []byte("1") + r, w, err := os.Pipe() + assert.NoError(t, err) + _, err = w.Write(expectedInput) + assert.NoError(t, err) + w.Close() + stdin := os.Stdin + // Restore stdin right after the test. + defer func() { os.Stdin = stdin }() + os.Stdin = r + + expectedOutMessage := fmt.Sprintf("Astro ApiToken %s was successfully deleted from deployment %s\n", deploymentAPIToken1.Id, deploymentID) + mockClient.On("DeleteDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentAPITokenResponseOK, nil).Once() + + err = DeleteDeploymentAPIToken("", deploymentID, out, mockClient) + assert.NoError(t, err) + assert.Equal(t, expectedOutMessage, out.String()) + }) + + t.Run("error path - RemoveDeploymentApiToken no apiToken id passed error on ListDeploymentApiTokensWithResponse", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.LocalPlatform) + out := new(bytes.Buffer) + + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseError, nil).Twice() + + err := DeleteDeploymentAPIToken("", deploymentID, out, mockClient) + assert.EqualError(t, err, "failed to list deployment apiTokens") + }) +} + +func TestListDeploymentAPITokens(t *testing.T) { + testUtil.InitTestConfig(testUtil.LocalPlatform) + t.Run("happy path TestListDeploymentApiTokens", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.LocalPlatform) + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil).Twice() + err := ListDeploymentAPITokens(out, mockClient, deploymentID) + assert.NoError(t, err) + }) + + t.Run("error path when ListDeploymentApiTokensWithResponse return network error", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.LocalPlatform) + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errorNetwork).Once() + err := ListDeploymentAPITokens(out, mockClient, deploymentID) + assert.EqualError(t, err, "network error") + }) + + t.Run("error path when ListDeploymentApiTokensWithResponse returns an error", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.LocalPlatform) + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseError, nil).Twice() + err := ListDeploymentAPITokens(out, mockClient, deploymentID) + assert.EqualError(t, err, "failed to list deployment apiTokens") + }) + + t.Run("error path when getting current context returns an error", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.Initial) + expectedOutMessage := "" + out := new(bytes.Buffer) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + err := ListDeploymentAPITokens(out, mockClient, deploymentID) + assert.Error(t, err) + assert.Equal(t, expectedOutMessage, out.String()) + }) +} diff --git a/cloud/user/user.go b/cloud/user/user.go index 8d14031e6..841bf3de0 100644 --- a/cloud/user/user.go +++ b/cloud/user/user.go @@ -320,7 +320,6 @@ func UpdateWorkspaceUserRole(email, role, workspace string, out io.Writer, clien } err = astrocore.NormalizeAPIError(resp.HTTPResponse, resp.Body) if err != nil { - fmt.Println("error in NormalizeAPIError") return err } fmt.Fprintf(out, "The workspace user %s role was successfully updated to %s\n", email, role) @@ -540,7 +539,6 @@ func UpdateDeploymentUserRole(email, role, deployment string, out io.Writer, cli } err = astrocore.NormalizeAPIError(resp.HTTPResponse, resp.Body) if err != nil { - fmt.Println("error in NormalizeAPIError") return err } fmt.Fprintf(out, "The deployment user %s role was successfully updated to %s\n", email, role) diff --git a/cmd/cloud/deployment.go b/cmd/cloud/deployment.go index 60c530f86..e8e4e62fd 100644 --- a/cmd/cloud/deployment.go +++ b/cmd/cloud/deployment.go @@ -9,6 +9,7 @@ import ( airflowversions "github.com/astronomer/astro-cli/airflow_versions" astrocore "github.com/astronomer/astro-cli/astro-client-core" astroplatformcore "github.com/astronomer/astro-cli/astro-client-platform-core" + "github.com/astronomer/astro-cli/cloud/apitoken" "github.com/astronomer/astro-cli/cloud/deployment" "github.com/astronomer/astro-cli/cloud/deployment/fromfile" "github.com/astronomer/astro-cli/cloud/organization" @@ -28,6 +29,9 @@ const ( ) var ( + apiTokenName string + apiTokenRole string + apiTokenDescription string label string runtimeVersion string deploymentID string @@ -120,6 +124,7 @@ func newDeploymentRootCmd(out io.Writer) *cobra.Command { newDeploymentPoolRootCmd(out), newDeploymentUserRootCmd(out), newDeploymentTeamRootCmd(out), + newDeploymentAPITokenRootCmd(out), newDeploymentHibernateCmd(), newDeploymentWakeUpCmd(), ) @@ -920,6 +925,148 @@ func removeDeploymentUser(cmd *cobra.Command, args []string, out io.Writer) erro return user.RemoveDeploymentUser(email, deploymentID, out, astroCoreClient) } +//nolint:dupl +func newDeploymentAPITokenRootCmd(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "api-token", + Aliases: []string{"at", "apiTokens"}, + Short: "Manage apiTokens in your Astro Deployment", + Long: "Manage apiTokens in your Astro Deployment.", + } + cmd.SetOut(out) + cmd.AddCommand( + newDeploymentAPITokenListCmd(out), + newDeploymentAPITokenUpdateCmd(out), + newDeploymentAPITokenDeleteCmd(out), + newDeploymentAPITokenCreateCmd(out), + ) + cmd.PersistentFlags().StringVar(&deploymentID, "deployment-id", "", "deployment where you'd like to manage apiTokens") + return cmd +} + +func newDeploymentAPITokenCreateCmd(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "create [apiTokenID]", + Short: "Create a apiToken in an Astro Deployment with a specific role", + Long: "Create a apiToken in an Astro Deployment with a specific role\n$astro deployment apiToken add [apiTokenID] --role [ DEPLOYMENT_ADMIN or the custom role name ].", + RunE: func(cmd *cobra.Command, args []string) error { + return createDeploymentAPIToken(cmd, nil, out) + }, + } + cmd.Flags().StringVarP(&apiTokenRole, "role", "r", "", "The role for the "+ + "new apiToken. Possible values are DEPLOYMENT_ADMIN or the custom role name.") + cmd.Flags().StringVarP(&apiTokenName, "name", "n", "", "The unique name for the "+ + "new apiToken.") + cmd.Flags().StringVarP(&apiTokenDescription, "description", "d", "", "A short description for the "+ + "new apiToken.") + return cmd +} + +func newDeploymentAPITokenListCmd(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List all the apiTokens in an Astro Deployment", + Long: "List all the apiTokens in an Astro Deployment", + RunE: func(cmd *cobra.Command, args []string) error { + return listDeploymentAPIToken(cmd, out) + }, + } + return cmd +} + +func newDeploymentAPITokenUpdateCmd(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "update [apiTokenID]", + Aliases: []string{"up"}, + Short: "Update a the role of a apiToken in an Astro Deployment", + Long: "Update the role of a apiToken in an Astro Deployment\n$astro deployment apiToken update [apiTokenID] --role [ DEPLOYMENT_ADMIN or the custom role name ].", + RunE: func(cmd *cobra.Command, args []string) error { + return updateDeploymentAPIToken(cmd, args, out) + }, + } + cmd.Flags().StringVarP(&updateDeploymentRole, "role", "r", "", "The new role for the "+ + "apiToken. Possible values are DEPLOYMENT_ADMIN or the custom role name.") + return cmd +} + +func newDeploymentAPITokenDeleteCmd(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "delete", + Aliases: []string{"d"}, + Short: "Delete a apiToken from an Astro Deployment", + Long: "Delete a apiToken from an Astro Deployment", + RunE: func(cmd *cobra.Command, args []string) error { + return deleteDeploymentAPIToken(cmd, args, out) + }, + } + return cmd +} + +func createDeploymentAPIToken(cmd *cobra.Command, _, out io.Writer) error { + if deploymentID == "" { + return errors.New("flag --deployment-id is required") + } + + if apiTokenName == "" { + // no name was provided so ask the user for it + apiTokenName = input.Text("Enter a unique name for the apiToken: ") + } + + if apiTokenRole == "" { + // no role was provided so ask the user for it + apiTokenRole = input.Text("Enter a Deployment role or custom role name to update apiToken. Can be DEPLOYMENT_ADMIN or the id of a custom role: ") + } + + cmd.SilenceUsage = true + return apitoken.CreateDeploymentAPIToken(apiTokenName, apiTokenRole, apiTokenDescription, deploymentID, out, astroCoreClient) +} + +func listDeploymentAPIToken(cmd *cobra.Command, out io.Writer) error { + if deploymentID == "" { + return errors.New("flag --deployment-id is required") + } + cmd.SilenceUsage = true + return apitoken.ListDeploymentAPITokens(out, astroCoreClient, deploymentID) +} + +func updateDeploymentAPIToken(cmd *cobra.Command, args []string, out io.Writer) error { + if deploymentID == "" { + return errors.New("flag --deployment-id is required") + } + var apiTokenID string + + // if an apiTokenID was provided in the args we use it + if len(args) > 0 { + // make sure the apiTokenID is lowercase + apiTokenID = strings.ToLower(args[0]) + } + + if updateDeploymentRole == "" { + // no role was provided so ask the apiToken for it + updateDeploymentRole = input.Text("Enter a apiToken Deployment role or custom role name to update apiToken: ") + } + + cmd.SilenceUsage = true + return apitoken.UpdateDeploymentAPITokenRole(apiTokenID, updateDeploymentRole, deploymentID, out, astroCoreClient) +} + +func deleteDeploymentAPIToken(cmd *cobra.Command, args []string, out io.Writer) error { + if deploymentID == "" { + return errors.New("flag --deployment-id is required") + } + var apiTokenID string + + // if an apiTokenID was provided in the args we use it + if len(args) > 0 { + // make sure the apiTokenID is lowercase + apiTokenID = strings.ToLower(args[0]) + } + + cmd.SilenceUsage = true + return apitoken.DeleteDeploymentAPIToken(apiTokenID, deploymentID, out, astroCoreClient) +} + func getOverrideUntil(until, forDuration string) (*time.Time, error) { if until != "" { untilParsed, err := time.Parse(time.RFC3339, until) diff --git a/cmd/cloud/deployment_test.go b/cmd/cloud/deployment_test.go index 3620806bc..d04d4b80c 100644 --- a/cmd/cloud/deployment_test.go +++ b/cmd/cloud/deployment_test.go @@ -1342,6 +1342,116 @@ var ( }, Body: teamRequestErrorDelete, } + + tokenDeploymentRole = astrocore.ApiTokenRole{ + EntityType: "DEPLOYMENT", + EntityId: deploymentID, + Role: "DEPLOYMENT_ADMIN", + } + + tokenDeploymentRole2 = astrocore.ApiTokenRole{ + EntityType: "DEPLOYMENT", + EntityId: deploymentID, + Role: "custom role", + } + + deploymentAPITokens = []astrocore.ApiToken{ + { + Name: "mock name", + Description: "mock description", + Roles: []astrocore.ApiTokenRole{ + tokenDeploymentRole, + }, + Id: "mock id", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + { + Name: "mock name2", + Description: "mock description2", + Roles: []astrocore.ApiTokenRole{ + tokenDeploymentRole2, + }, + Id: "mock id2", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + } + + ListDeploymentAPITokensResponseOK = astrocore.ListDeploymentApiTokensResponse{ + HTTPResponse: &http.Response{ + StatusCode: 200, + }, + JSON200: &astrocore.ListApiTokensPaginated{ + Limit: 1, + Offset: 0, + TotalCount: 1, + ApiTokens: deploymentAPITokens, + }, + } + apiTokenRequestErrorBodyList, _ = json.Marshal(astrocore.Error{ + Message: "failed to list api tokens", + }) + ListDeploymentAPITokensResponseError = astrocore.ListDeploymentApiTokensResponse{ + HTTPResponse: &http.Response{ + StatusCode: 500, + }, + Body: apiTokenRequestErrorBodyList, + JSON200: nil, + } + CreateDeploymentAPITokenRoleResponseOK = astrocore.CreateDeploymentApiTokenResponse{ + HTTPResponse: &http.Response{ + StatusCode: 200, + }, + JSON200: &deploymentAPITokens[0], + } + apiTokenRequestErrorBodyCreate, _ = json.Marshal(astrocore.Error{ + Message: "failed to create api token", + }) + CreateDeploymentAPITokenRoleResponseError = astrocore.CreateDeploymentApiTokenResponse{ + HTTPResponse: &http.Response{ + StatusCode: 500, + }, + Body: apiTokenRequestErrorBodyCreate, + JSON200: nil, + } + MutateDeploymentAPITokenRoleResponseOK = astrocore.UpdateDeploymentApiTokenResponse{ + HTTPResponse: &http.Response{ + StatusCode: 200, + }, + JSON200: &deploymentAPITokens[0], + } + apiTokenRequestErrorBodyUpdate, _ = json.Marshal(astrocore.Error{ + Message: "failed to update api token", + }) + MutateDeploymentAPITokenRoleResponseError = astrocore.UpdateDeploymentApiTokenResponse{ + HTTPResponse: &http.Response{ + StatusCode: 500, + }, + Body: apiTokenRequestErrorBodyUpdate, + JSON200: nil, + } + DeleteDeploymentAPITokenResponseOK = astrocore.DeleteDeploymentApiTokenResponse{ + HTTPResponse: &http.Response{ + StatusCode: 200, + }, + } + + apiTokenRequestErrorDelete, _ = json.Marshal(astrocore.Error{ + Message: "failed to delete api token", + }) + DeleteDeploymentAPITokenResponseError = astrocore.DeleteDeploymentApiTokenResponse{ + HTTPResponse: &http.Response{ + StatusCode: 500, + }, + Body: apiTokenRequestErrorDelete, + } + GetDeploymentAPITokenWithResponseOK = astrocore.GetDeploymentApiTokenResponse{ + HTTPResponse: &http.Response{ + StatusCode: 200, + }, + JSON200: &deploymentAPITokens[0], + } ) func TestDeploymentUserList(t *testing.T) { @@ -1681,6 +1791,7 @@ func TestDeploymentTeamUpdate(t *testing.T) { _, err := execDeploymentCmd(cmdArgs...) assert.Error(t, err) }) + t.Run("command asks for input when no team id is passed in as an arg", func(t *testing.T) { testUtil.InitTestConfig(testUtil.LocalPlatform) @@ -1756,6 +1867,7 @@ func TestDeploymentTeamAdd(t *testing.T) { _, err := execDeploymentCmd(cmdArgs...) assert.Error(t, err) }) + t.Run("command asks for input when no team id is passed in as an arg", func(t *testing.T) { testUtil.InitTestConfig(testUtil.LocalPlatform) @@ -1858,3 +1970,238 @@ func TestDeploymentTeamRemove(t *testing.T) { assert.Contains(t, resp, expectedOut) }) } + +// TOP +func TestDeploymentApiTokenList(t *testing.T) { + expectedHelp := "List all the apiTokens in an Astro Deployment" + testUtil.InitTestConfig(testUtil.LocalPlatform) + + t.Run("-h prints list help", func(t *testing.T) { + cmdArgs := []string{"api-token", "list", "-h"} + resp, err := execDeploymentCmd(cmdArgs...) + assert.NoError(t, err) + assert.Contains(t, resp, expectedHelp) + }) + t.Run("will error if deployment id flag is not provided", func(t *testing.T) { + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + astroCoreClient = mockClient + cmdArgs := []string{"api-token", "list"} + _, err := execDeploymentCmd(cmdArgs...) + assert.EqualError(t, err, "flag --deployment-id is required") + }) + t.Run("any errors from api are returned and apiTokens are not listed", func(t *testing.T) { + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseError, nil).Once() + astroCoreClient = mockClient + cmdArgs := []string{"api-token", "list", "--deployment-id", mockDeploymentID} + _, err := execDeploymentCmd(cmdArgs...) + assert.EqualError(t, err, "failed to list api tokens") + }) + t.Run("any context errors from api are returned and apiTokens are not listed", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.Initial) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseError, nil).Once() + astroCoreClient = mockClient + cmdArgs := []string{"api-token", "list", "--deployment-id", mockDeploymentID} + _, err := execDeploymentCmd(cmdArgs...) + assert.Error(t, err) + }) +} + +func TestDeploymentApiTokenUpdate(t *testing.T) { + testUtil.InitTestConfig(testUtil.LocalPlatform) + + t.Run("-h prints update help", func(t *testing.T) { + cmdArgs := []string{"api-token", "update", "-h"} + _, err := execDeploymentCmd(cmdArgs...) + assert.NoError(t, err) + }) + t.Run("will error if deployment id flag is not provided", func(t *testing.T) { + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + astroCoreClient = mockClient + cmdArgs := []string{"api-token", "update", apiToken1.Id, "--role", "DEPLOYMENT_ADMIN"} + _, err := execDeploymentCmd(cmdArgs...) + assert.EqualError(t, err, "flag --deployment-id is required") + }) + t.Run("valid id with valid role updates apiToken", func(t *testing.T) { + expectedOut := fmt.Sprintf("The deployment apiToken %s role was successfully updated to DEPLOYMENT_ADMIN", apiToken1.Id) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("GetDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentAPITokenWithResponseOK, nil).Twice() + mockClient.On("UpdateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentAPITokenRoleResponseOK, nil).Once() + astroCoreClient = mockClient + cmdArgs := []string{"api-token", "update", apiToken1.Id, "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} + resp, err := execDeploymentCmd(cmdArgs...) + assert.NoError(t, err) + assert.Contains(t, resp, expectedOut) + }) + t.Run("any errors from api are returned and role is not updated", func(t *testing.T) { + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("GetDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentAPITokenWithResponseOK, nil).Twice() + mockClient.On("UpdateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentAPITokenRoleResponseError, nil).Once() + astroCoreClient = mockClient + cmdArgs := []string{"api-token", "update", apiToken1.Id, "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} + _, err := execDeploymentCmd(cmdArgs...) + assert.EqualError(t, err, "failed to update api token") + }) + + t.Run("any context errors from api are returned and role is not updated", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.Initial) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("GetDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentAPITokenWithResponseOK, nil).Twice() + mockClient.On("UpdateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentAPITokenRoleResponseOK, nil).Once() + astroCoreClient = mockClient + cmdArgs := []string{"api-token", "update", apiToken1.Id, "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} + _, err := execDeploymentCmd(cmdArgs...) + assert.Error(t, err) + }) + t.Run("command asks for input when no api token name is passed in as an arg", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.LocalPlatform) + + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil).Twice() + // mock os.Stdin + expectedInput := []byte("1") + r, w, err := os.Pipe() + assert.NoError(t, err) + _, err = w.Write(expectedInput) + assert.NoError(t, err) + w.Close() + stdin := os.Stdin + // Restore stdin right after the test. + defer func() { os.Stdin = stdin }() + os.Stdin = r + + expectedOut := fmt.Sprintf("The deployment apiToken %s role was successfully updated to DEPLOYMENT_ADMIN", deploymentAPITokens[0].Id) + mockClient.On("UpdateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&MutateDeploymentAPITokenRoleResponseOK, nil).Once() + astroCoreClient = mockClient + + cmdArgs := []string{"api-token", "update", "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} + resp, err := execDeploymentCmd(cmdArgs...) + assert.NoError(t, err) + assert.Contains(t, resp, expectedOut) + }) +} + +func TestDeploymentApiTokenCreate(t *testing.T) { + testUtil.InitTestConfig(testUtil.LocalPlatform) + + t.Run("-h prints add help", func(t *testing.T) { + cmdArgs := []string{"api-token", "create", "-h"} + _, err := execDeploymentCmd(cmdArgs...) + assert.NoError(t, err) + }) + t.Run("valid id with valid role adds apiToken", func(t *testing.T) { + expectedOut := "The apiToken was successfully created with the role DEPLOYMENT_ADMIN\n" + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("GetDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentAPITokenWithResponseOK, nil).Twice() + mockClient.On("CreateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateDeploymentAPITokenRoleResponseOK, nil).Once() + astroCoreClient = mockClient + cmdArgs := []string{"api-token", "create", apiToken1.Id, "--role", "DEPLOYMENT_ADMIN", "--name", "mockName", "--deployment-id", mockDeploymentID} + resp, err := execDeploymentCmd(cmdArgs...) + assert.NoError(t, err) + assert.Contains(t, resp, expectedOut) + }) + + t.Run("will error if deployment id flag is not provided", func(t *testing.T) { + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + astroCoreClient = mockClient + cmdArgs := []string{"api-token", "create", apiToken1.Id} + _, err := execDeploymentCmd(cmdArgs...) + assert.EqualError(t, err, "flag --deployment-id is required") + }) + t.Run("any errors from api are returned and apiToken is not created", func(t *testing.T) { + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("GetDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentAPITokenWithResponseOK, nil).Twice() + mockClient.On("CreateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateDeploymentAPITokenRoleResponseError, nil).Once() + astroCoreClient = mockClient + cmdArgs := []string{"api-token", "create", apiToken1.Id, "--role", "DEPLOYMENT_ADMIN", "--name", "mockName", "--deployment-id", mockDeploymentID} + _, err := execDeploymentCmd(cmdArgs...) + assert.EqualError(t, err, "failed to create api token") + }) + + t.Run("any context errors from api are returned and role is not created", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.Initial) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("GetDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentAPITokenWithResponseOK, nil).Twice() + mockClient.On("CreateDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&CreateDeploymentAPITokenRoleResponseOK, nil).Once() + astroCoreClient = mockClient + cmdArgs := []string{"api-token", "create", apiToken1.Id, "--role", "DEPLOYMENT_ADMIN", "--deployment-id", mockDeploymentID} + _, err := execDeploymentCmd(cmdArgs...) + assert.Error(t, err) + }) +} + +func TestDeploymentApiTokenDelete(t *testing.T) { + expectedHelp := "Delete a apiToken from an Astro Deployment" + testUtil.InitTestConfig(testUtil.LocalPlatform) + + t.Run("-h prints delete help", func(t *testing.T) { + cmdArgs := []string{"api-token", "delete", "-h"} + resp, err := execDeploymentCmd(cmdArgs...) + assert.NoError(t, err) + assert.Contains(t, resp, expectedHelp) + }) + t.Run("will error if deployment id flag is not provided", func(t *testing.T) { + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + astroCoreClient = mockClient + cmdArgs := []string{"api-token", "delete", apiToken1.Id} + _, err := execDeploymentCmd(cmdArgs...) + assert.EqualError(t, err, "flag --deployment-id is required") + }) + t.Run("valid id deletes apiToken", func(t *testing.T) { + expectedOut := fmt.Sprintf("Astro ApiToken %s was successfully deleted from deployment %s\n", apiToken1.Id, mockDeploymentID) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("GetDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentAPITokenWithResponseOK, nil).Twice() + mockClient.On("DeleteDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentAPITokenResponseOK, nil).Once() + astroCoreClient = mockClient + cmdArgs := []string{"api-token", "delete", apiToken1.Id, "--deployment-id", mockDeploymentID} + resp, err := execDeploymentCmd(cmdArgs...) + assert.NoError(t, err) + assert.Contains(t, resp, expectedOut) + }) + t.Run("any errors from api are returned and apiToken is not deleted", func(t *testing.T) { + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("GetDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentAPITokenWithResponseOK, nil).Twice() + mockClient.On("DeleteDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentAPITokenResponseError, nil).Once() + astroCoreClient = mockClient + cmdArgs := []string{"api-token", "delete", apiToken1.Id, "--deployment-id", mockDeploymentID} + _, err := execDeploymentCmd(cmdArgs...) + assert.EqualError(t, err, "failed to delete api token") + }) + t.Run("any context errors from api are returned and the apiToken is not deleted", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.Initial) + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("GetDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&GetDeploymentAPITokenWithResponseOK, nil).Twice() + mockClient.On("DeleteDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentAPITokenResponseOK, nil).Once() + astroCoreClient = mockClient + cmdArgs := []string{"api-token", "delete", apiToken1.Id, "--deployment-id", mockDeploymentID} + _, err := execDeploymentCmd(cmdArgs...) + assert.Error(t, err) + }) + t.Run("command asks for input when no id is passed in as an arg", func(t *testing.T) { + testUtil.InitTestConfig(testUtil.LocalPlatform) + + mockClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockClient.On("ListDeploymentApiTokensWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&ListDeploymentAPITokensResponseOK, nil).Twice() + // mock os.Stdin + expectedInput := []byte("1") + r, w, err := os.Pipe() + assert.NoError(t, err) + _, err = w.Write(expectedInput) + assert.NoError(t, err) + w.Close() + stdin := os.Stdin + // Restore stdin right after the test. + defer func() { os.Stdin = stdin }() + os.Stdin = r + + expectedOut := fmt.Sprintf("Astro ApiToken %s was successfully deleted from deployment %s\n", deploymentAPITokens[0].Id, mockDeploymentID) + mockClient.On("DeleteDeploymentApiTokenWithResponse", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&DeleteDeploymentAPITokenResponseOK, nil).Once() + astroCoreClient = mockClient + + cmdArgs := []string{"api-token", "delete", "--deployment-id", mockDeploymentID} + resp, err := execDeploymentCmd(cmdArgs...) + assert.NoError(t, err) + assert.Contains(t, resp, expectedOut) + }) +}