From 9223f786a460991b43692578e1db371a182fc410 Mon Sep 17 00:00:00 2001 From: Predrag Janosevic Date: Mon, 12 Feb 2024 15:06:25 +0000 Subject: [PATCH] iam role create: fix description and policy when not set (#569) Fixes issues with empty description and policy in `iam role create` command. Also renames `iam org-policy` command `replace` to `update` (to be inline with `role`). `replace` is now alias command so it is backward compatible change. --- CHANGELOG.md | 3 + cmd/iam.go | 53 ++++++++++++++ ...cy_replace.go => iam_org_policy_update.go} | 67 ++++------------- cmd/iam_role_create.go | 73 ++++++------------- cmd/iam_role_update.go | 53 ++------------ 5 files changed, 95 insertions(+), 154 deletions(-) rename cmd/{iam_org_policy_replace.go => iam_org_policy_update.go} (51%) diff --git a/CHANGELOG.md b/CHANGELOG.md index d13cc49a..a0babd90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Bug Fixes - SOS download: output warning when no objects exist at prefix #563 +- Fix the bug in `iam role create` description that made it required #569 +- Fix creating role with empty policy #569 ### Features - Updated 'exo x' list-block-storage-volumes #562 @@ -11,6 +13,7 @@ ### Improvements - Update `exo iam role create` pro tip #55 +- `exo iam org-policy` `replace` command renamed to `update` where `replace` is now alias #569 ## 1.75.0 diff --git a/cmd/iam.go b/cmd/iam.go index 2f193962..8cd9d425 100644 --- a/cmd/iam.go +++ b/cmd/iam.go @@ -1,6 +1,7 @@ package cmd import ( + "encoding/json" "fmt" "os" @@ -8,6 +9,8 @@ import ( "github.com/exoscale/cli/pkg/output" "github.com/exoscale/cli/table" + + exoscale "github.com/exoscale/egoscale/v2" ) var iamCmd = &cobra.Command{ @@ -67,3 +70,53 @@ func (o *iamPolicyOutput) ToTable() { } } } + +func iamPolicyFromJSON(data []byte) (*exoscale.IAMPolicy, error) { + var obj iamPolicyOutput + err := json.Unmarshal(data, &obj) + if err != nil { + return nil, 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 + } + } + + return &policy, nil +} diff --git a/cmd/iam_org_policy_replace.go b/cmd/iam_org_policy_update.go similarity index 51% rename from cmd/iam_org_policy_replace.go rename to cmd/iam_org_policy_update.go index b384c401..bddd9c09 100644 --- a/cmd/iam_org_policy_replace.go +++ b/cmd/iam_org_policy_update.go @@ -1,7 +1,6 @@ package cmd import ( - "encoding/json" "fmt" "io" "strings" @@ -11,25 +10,26 @@ import ( "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 iamOrgPolicyReplaceCmd struct { +type iamOrgPolicyUpdateCmd struct { cliCommandSettings `cli-cmd:"-"` Policy string `cli-arg:"#"` - _ bool `cli-cmd:"replace"` + _ bool `cli-cmd:"update"` } -func (c *iamOrgPolicyReplaceCmd) cmdAliases() []string { return nil } +func (c *iamOrgPolicyUpdateCmd) cmdAliases() []string { + return []string{"replace"} +} -func (c *iamOrgPolicyReplaceCmd) cmdShort() string { - return "Replace Org policy" +func (c *iamOrgPolicyUpdateCmd) cmdShort() string { + return "Update Org policy" } -func (c *iamOrgPolicyReplaceCmd) cmdLong() string { +func (c *iamOrgPolicyUpdateCmd) cmdLong() string { return fmt.Sprintf(`This command replaces the complete IAM Organization Policy with the new one provided in JSON format. To read the Policy from STDIN provide '-' as an argument. @@ -41,11 +41,11 @@ Supported output template annotations: %s`, strings.Join(output.TemplateAnnotations(&iamPolicyOutput{}), ", ")) } -func (c *iamOrgPolicyReplaceCmd) cmdPreRun(cmd *cobra.Command, args []string) error { +func (c *iamOrgPolicyUpdateCmd) cmdPreRun(cmd *cobra.Command, args []string) error { return cliCommandDefaultPreRun(c, cmd, args) } -func (c *iamOrgPolicyReplaceCmd) cmdRun(cmd *cobra.Command, _ []string) error { +func (c *iamOrgPolicyUpdateCmd) cmdRun(cmd *cobra.Command, _ []string) error { zone := account.CurrentAccount.DefaultZone ctx := exoapi.WithEndpoint( gContext, @@ -62,50 +62,9 @@ func (c *iamOrgPolicyReplaceCmd) cmdRun(cmd *cobra.Command, _ []string) error { c.Policy = string(b) } - var obj iamPolicyOutput - err := json.Unmarshal([]byte(c.Policy), &obj) + policy, err := iamPolicyFromJSON([]byte(c.Policy)) 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 - } + return fmt.Errorf("failed to parse IAM policy: %w", err) } err = globalstate.EgoscaleClient.UpdateIAMOrgPolicy(ctx, zone, policy) @@ -123,7 +82,7 @@ func (c *iamOrgPolicyReplaceCmd) cmdRun(cmd *cobra.Command, _ []string) error { } func init() { - cobra.CheckErr(registerCLICommand(iamOrgPolicyCmd, &iamOrgPolicyReplaceCmd{ + cobra.CheckErr(registerCLICommand(iamOrgPolicyCmd, &iamOrgPolicyUpdateCmd{ cliCommandSettings: defaultCLICmdSettings(), })) } diff --git a/cmd/iam_role_create.go b/cmd/iam_role_create.go index af1cbeca..3a5802f8 100644 --- a/cmd/iam_role_create.go +++ b/cmd/iam_role_create.go @@ -1,7 +1,6 @@ package cmd import ( - "encoding/json" "errors" "fmt" "io" @@ -41,7 +40,7 @@ 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 | exo iam role create --description "new role description" --policy - + exo iam role show --policy --output-format json | exo iam role create --policy - Supported output template annotations: %s`, strings.Join(output.TemplateAnnotations(&iamRoleShowOutput{}), ", ")) @@ -62,71 +61,41 @@ func (c *iamRoleCreateCmd) cmdRun(cmd *cobra.Command, _ []string) error { 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 policy *exoscale.IAMPolicy - 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 - }(), + // Policy is optional, if not set API will default to `allow all` + if c.Policy != "" { + // If Policy value is `-` read from STDIN + 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) } - 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) - } - } + c.Policy = string(b) + } + + var err error - policy.Services[name] = service + policy, err = iamPolicyFromJSON([]byte(c.Policy)) + if err != nil { + return fmt.Errorf("failed to parse IAM policy: %w", err) } } role := &exoscale.IAMRole{ Name: &c.Name, - Description: &c.Description, Editable: &c.Editable, Labels: c.Labels, Permissions: c.Permissions, Policy: policy, } + if c.Description != "" { + role.Description = &c.Description + } + r, err := globalstate.EgoscaleClient.CreateIAMRole(ctx, zone, role) if err != nil { return err diff --git a/cmd/iam_role_update.go b/cmd/iam_role_update.go index f6403dbd..6ecdfaa7 100644 --- a/cmd/iam_role_update.go +++ b/cmd/iam_role_update.go @@ -1,7 +1,6 @@ package cmd import ( - "encoding/json" "errors" "fmt" "io" @@ -73,9 +72,8 @@ func (c *iamRoleUpdateCmd) cmdRun(cmd *cobra.Command, _ []string) error { } } - role, err := globalstate.EgoscaleClient.GetIAMRole(ctx, zone, c.Role) - if err != nil { - return err + role := &exoscale.IAMRole{ + ID: &c.Role, } if cmd.Flags().Changed(mustCLICommandFlagName(c, &c.Description)) { @@ -88,7 +86,7 @@ func (c *iamRoleUpdateCmd) cmdRun(cmd *cobra.Command, _ []string) error { role.Permissions = c.Permissions } - err = globalstate.EgoscaleClient.UpdateIAMRole(ctx, zone, role) + err := globalstate.EgoscaleClient.UpdateIAMRole(ctx, zone, role) if err != nil { return err } @@ -115,50 +113,9 @@ func (c *iamRoleUpdateCmd) cmdRun(cmd *cobra.Command, _ []string) error { c.Policy = string(b) } - var obj iamPolicyOutput - err = json.Unmarshal([]byte(c.Policy), &obj) + policy, err := iamPolicyFromJSON([]byte(c.Policy)) 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 - } + return fmt.Errorf("failed to parse IAM policy: %w", err) } role.Policy = policy