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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dep api token crud #1564

Merged
merged 23 commits into from
Feb 29, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
257 changes: 257 additions & 0 deletions cloud/apitoken/apitoken.go
Original file line number Diff line number Diff line change
@@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should save the api token from response to ASTRO_API_TOKEN variable. Right now, the token gets created, but user cannot use the token value

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will get a pr up for your comments

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think instead of setting it we really want to print it to screen so they can use it where they want to.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worried about the token leaking in ci cd logs...

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

Check warning on line 69 in cloud/apitoken/apitoken.go

View check run for this annotation

Codecov / codecov/patch

cloud/apitoken/apitoken.go#L69

Added line #L69 was not covered by tests
}
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

Check warning on line 84 in cloud/apitoken/apitoken.go

View check run for this annotation

Codecov / codecov/patch

cloud/apitoken/apitoken.go#L84

Added line #L84 was not covered by tests
}
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

Check warning on line 113 in cloud/apitoken/apitoken.go

View check run for this annotation

Codecov / codecov/patch

cloud/apitoken/apitoken.go#L113

Added line #L113 was not covered by tests
}

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

Check warning on line 134 in cloud/apitoken/apitoken.go

View check run for this annotation

Codecov / codecov/patch

cloud/apitoken/apitoken.go#L134

Added line #L134 was not covered by tests
}
}

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

Check warning on line 155 in cloud/apitoken/apitoken.go

View check run for this annotation

Codecov / codecov/patch

cloud/apitoken/apitoken.go#L155

Added line #L155 was not covered by tests
}
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

Check warning on line 173 in cloud/apitoken/apitoken.go

View check run for this annotation

Codecov / codecov/patch

cloud/apitoken/apitoken.go#L173

Added line #L173 was not covered by tests
}
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

Check warning on line 218 in cloud/apitoken/apitoken.go

View check run for this annotation

Codecov / codecov/patch

cloud/apitoken/apitoken.go#L218

Added line #L218 was not covered by tests
}

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if there are no api tokens, we should display an error message, rather than an empty table with only headers

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

Check warning on line 242 in cloud/apitoken/apitoken.go

View check run for this annotation

Codecov / codecov/patch

cloud/apitoken/apitoken.go#L242

Added line #L242 was not covered by tests
}
}
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
}