-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
# Description This PR adds IAM Role management commands: - `exo iam role list` - `exo iam role show` - `exo iam role create` - `exo iam delete` - `exo iam role update`
- Loading branch information
Showing
8 changed files
with
642 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package cmd | ||
|
||
import ( | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var iamRoleCmd = &cobra.Command{ | ||
Use: "role", | ||
Short: "IAM Role management", | ||
} | ||
|
||
func init() { | ||
iamCmd.AddCommand(iamRoleCmd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package cmd | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"strings" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
"github.com/exoscale/cli/pkg/account" | ||
"github.com/exoscale/cli/pkg/globalstate" | ||
"github.com/exoscale/cli/pkg/output" | ||
exoscale "github.com/exoscale/egoscale/v2" | ||
exoapi "github.com/exoscale/egoscale/v2/api" | ||
) | ||
|
||
type iamRoleCreateCmd struct { | ||
cliCommandSettings `cli-cmd:"-"` | ||
|
||
_ bool `cli-cmd:"create"` | ||
|
||
Name string `cli-arg:"#" cli-usage:"NAME"` | ||
Description string `cli-flag:"description" cli-usage:"Role description"` | ||
Permissions []string `cli-flag:"permissions" cli-usage:"Role permissions"` | ||
Editable bool `cli-flag:"editable" cli-usage:"Set --editable=false do prevent editing Policy after creation"` | ||
Labels map[string]string `cli-flag:"label" cli-usage:"Role labels (format: key=value)"` | ||
Policy string `cli-flag:"policy" cli-usage:"Role policy (use '-' to read from STDIN)"` | ||
} | ||
|
||
func (c *iamRoleCreateCmd) cmdAliases() []string { return nil } | ||
|
||
func (c *iamRoleCreateCmd) cmdShort() string { | ||
return "Create IAM Role" | ||
} | ||
|
||
func (c *iamRoleCreateCmd) cmdLong() string { | ||
return fmt.Sprintf(`This command creates a new IAM Role. | ||
To read the Policy from STDIN, append '-' to the '--policy' flag. | ||
Pro Tip: you can reuse an existing role policy by providing the output of the show command as input: | ||
exo iam role show --policy --output-format json <role-name> | exo iam role create --name <new-role-name> --policy - | ||
Supported output template annotations: %s`, | ||
strings.Join(output.TemplateAnnotations(&iamRoleShowOutput{}), ", ")) | ||
} | ||
|
||
func (c *iamRoleCreateCmd) cmdPreRun(cmd *cobra.Command, args []string) error { | ||
return cliCommandDefaultPreRun(c, cmd, args) | ||
} | ||
|
||
func (c *iamRoleCreateCmd) cmdRun(cmd *cobra.Command, _ []string) error { | ||
if c.Name == "" { | ||
return errors.New("NAME not provided") | ||
} | ||
|
||
zone := account.CurrentAccount.DefaultZone | ||
ctx := exoapi.WithEndpoint( | ||
gContext, | ||
exoapi.NewReqEndpoint(account.CurrentAccount.Environment, zone), | ||
) | ||
|
||
if c.Policy == "-" { | ||
inputReader := cmd.InOrStdin() | ||
b, err := io.ReadAll(inputReader) | ||
if err != nil { | ||
return fmt.Errorf("failed to read policy from stdin: %w", err) | ||
} | ||
|
||
c.Policy = string(b) | ||
} | ||
|
||
var obj iamPolicyOutput | ||
err := json.Unmarshal([]byte(c.Policy), &obj) | ||
if err != nil { | ||
return fmt.Errorf("failed to parse policy: %w", err) | ||
} | ||
|
||
policy := &exoscale.IAMPolicy{ | ||
DefaultServiceStrategy: obj.DefaultServiceStrategy, | ||
Services: map[string]exoscale.IAMPolicyService{}, | ||
} | ||
|
||
if len(obj.Services) > 0 { | ||
for name, sv := range obj.Services { | ||
service := exoscale.IAMPolicyService{ | ||
Type: func() *string { | ||
t := sv.Type | ||
return &t | ||
}(), | ||
} | ||
|
||
if len(sv.Rules) > 0 { | ||
service.Rules = []exoscale.IAMPolicyServiceRule{} | ||
for _, rl := range sv.Rules { | ||
|
||
rule := exoscale.IAMPolicyServiceRule{ | ||
Action: func() *string { | ||
t := rl.Action | ||
return &t | ||
}(), | ||
} | ||
|
||
if rl.Expression != "" { | ||
rule.Expression = func() *string { | ||
t := rl.Expression | ||
return &t | ||
}() | ||
} | ||
|
||
service.Rules = append(service.Rules, rule) | ||
} | ||
} | ||
|
||
policy.Services[name] = service | ||
} | ||
} | ||
|
||
role := &exoscale.IAMRole{ | ||
Name: &c.Name, | ||
Description: &c.Description, | ||
Editable: &c.Editable, | ||
Labels: c.Labels, | ||
Permissions: c.Permissions, | ||
Policy: policy, | ||
} | ||
|
||
r, err := globalstate.EgoscaleClient.CreateIAMRole(ctx, zone, role) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if !globalstate.Quiet { | ||
return (&iamRoleShowCmd{ | ||
cliCommandSettings: c.cliCommandSettings, | ||
Role: *r.ID, | ||
}).cmdRun(nil, nil) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func init() { | ||
cobra.CheckErr(registerCLICommand(iamRoleCmd, &iamRoleCreateCmd{ | ||
cliCommandSettings: defaultCLICmdSettings(), | ||
Editable: true, | ||
})) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/google/uuid" | ||
"github.com/spf13/cobra" | ||
|
||
"github.com/exoscale/cli/pkg/account" | ||
"github.com/exoscale/cli/pkg/globalstate" | ||
egoscale "github.com/exoscale/egoscale/v2" | ||
exoapi "github.com/exoscale/egoscale/v2/api" | ||
) | ||
|
||
type iamRoleDeleteCmd struct { | ||
cliCommandSettings `cli-cmd:"-"` | ||
|
||
_ bool `cli-cmd:"delete"` | ||
|
||
Role string `cli-arg:"#" cli-usage:"ID|NAME"` | ||
|
||
Force bool `cli-short:"f" cli-usage:"don't prompt for confirmation"` | ||
} | ||
|
||
func (c *iamRoleDeleteCmd) cmdAliases() []string { return gDeleteAlias } | ||
|
||
func (c *iamRoleDeleteCmd) cmdShort() string { | ||
return "Delete IAM Role" | ||
} | ||
|
||
func (c *iamRoleDeleteCmd) cmdLong() string { | ||
return `This command deletes an existing IAM Role. | ||
It will fail if the Role is attached to an IAM Key.` | ||
} | ||
|
||
func (c *iamRoleDeleteCmd) cmdPreRun(cmd *cobra.Command, args []string) error { | ||
return cliCommandDefaultPreRun(c, cmd, args) | ||
} | ||
|
||
func (c *iamRoleDeleteCmd) cmdRun(_ *cobra.Command, _ []string) error { | ||
zone := account.CurrentAccount.DefaultZone | ||
ctx := exoapi.WithEndpoint(gContext, exoapi.NewReqEndpoint(account.CurrentAccount.Environment, zone)) | ||
|
||
if _, err := uuid.Parse(c.Role); err != nil { | ||
roles, err := globalstate.EgoscaleClient.ListIAMRoles(ctx, zone) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
found := false | ||
for _, role := range roles { | ||
if role.Name != nil && *role.Name == c.Role { | ||
c.Role = *role.ID | ||
found = true | ||
break | ||
} | ||
} | ||
|
||
if !found { | ||
return fmt.Errorf("role with name %q not found", c.Role) | ||
} | ||
} | ||
|
||
if !c.Force { | ||
if !askQuestion(fmt.Sprintf("Are you sure you want to delete IAM Role %s?", c.Role)) { | ||
return nil | ||
} | ||
} | ||
|
||
var err error | ||
decorateAsyncOperation(fmt.Sprintf("Deleting IAM role %s...", c.Role), func() { | ||
|
||
err = globalstate.EgoscaleClient.DeleteIAMRole(ctx, zone, &egoscale.IAMRole{ID: &c.Role}) | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func init() { | ||
cobra.CheckErr(registerCLICommand(iamRoleCmd, &iamRoleDeleteCmd{ | ||
cliCommandSettings: defaultCLICmdSettings(), | ||
})) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
"github.com/exoscale/cli/pkg/account" | ||
"github.com/exoscale/cli/pkg/globalstate" | ||
"github.com/exoscale/cli/pkg/output" | ||
"github.com/exoscale/cli/utils" | ||
exoapi "github.com/exoscale/egoscale/v2/api" | ||
) | ||
|
||
type iamRoleListItemOutput struct { | ||
ID string `json:"key"` | ||
Name string `json:"name"` | ||
Editable bool `json:"type"` | ||
} | ||
|
||
type iamRoleListOutput []iamRoleListItemOutput | ||
|
||
func (o *iamRoleListOutput) ToJSON() { output.JSON(o) } | ||
func (o *iamRoleListOutput) ToText() { output.Text(o) } | ||
func (o *iamRoleListOutput) ToTable() { output.Table(o) } | ||
|
||
type iamRoleListCmd struct { | ||
cliCommandSettings `cli-cmd:"-"` | ||
|
||
_ bool `cli-cmd:"list"` | ||
} | ||
|
||
func (c *iamRoleListCmd) cmdAliases() []string { return gListAlias } | ||
|
||
func (c *iamRoleListCmd) cmdShort() string { return "List IAM Roles" } | ||
|
||
func (c *iamRoleListCmd) cmdLong() string { | ||
return fmt.Sprintf(`This command lists existing IAM Roles. | ||
Supported output template annotations: %s`, | ||
strings.Join(output.TemplateAnnotations(&iamRoleListOutput{}), ", ")) | ||
} | ||
|
||
func (c *iamRoleListCmd) cmdPreRun(cmd *cobra.Command, args []string) error { | ||
return cliCommandDefaultPreRun(c, cmd, args) | ||
} | ||
|
||
func (c *iamRoleListCmd) cmdRun(_ *cobra.Command, _ []string) error { | ||
zone := account.CurrentAccount.DefaultZone | ||
|
||
ctx := exoapi.WithEndpoint(gContext, exoapi.NewReqEndpoint(account.CurrentAccount.Environment, zone)) | ||
|
||
iamRoles, err := globalstate.EgoscaleClient.ListIAMRoles(ctx, zone) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
out := make(iamRoleListOutput, 0) | ||
|
||
for _, role := range iamRoles { | ||
out = append(out, iamRoleListItemOutput{ | ||
ID: utils.DefaultString(role.ID, ""), | ||
Name: utils.DefaultString(role.Name, ""), | ||
Editable: utils.DefaultBool(role.Editable, false), | ||
}) | ||
} | ||
|
||
return c.outputFunc(&out, err) | ||
} | ||
|
||
func init() { | ||
cobra.CheckErr(registerCLICommand(iamRoleCmd, &iamRoleListCmd{ | ||
cliCommandSettings: defaultCLICmdSettings(), | ||
})) | ||
} |
Oops, something went wrong.