Skip to content

Commit

Permalink
iam role create: fix description and policy when not set (#569)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
kobajagi authored Feb 12, 2024
1 parent 347f6d3 commit 9223f78
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 154 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

### 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
- completion: Adding fish support

### 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

Expand Down
53 changes: 53 additions & 0 deletions cmd/iam.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package cmd

import (
"encoding/json"
"fmt"
"os"

"github.com/spf13/cobra"

"github.com/exoscale/cli/pkg/output"
"github.com/exoscale/cli/table"

exoscale "github.com/exoscale/egoscale/v2"
)

var iamCmd = &cobra.Command{
Expand Down Expand Up @@ -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
}
67 changes: 13 additions & 54 deletions cmd/iam_org_policy_replace.go → cmd/iam_org_policy_update.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cmd

import (
"encoding/json"
"fmt"
"io"
"strings"
Expand All @@ -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.
Expand All @@ -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,
Expand All @@ -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)
Expand All @@ -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(),
}))
}
73 changes: 21 additions & 52 deletions cmd/iam_role_create.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cmd

import (
"encoding/json"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -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 <role-name> | exo iam role create <new-role-name> --description "new role description" --policy -
exo iam role show --policy --output-format json <role-name> | exo iam role create <new-role-name> --policy -
Supported output template annotations: %s`,
strings.Join(output.TemplateAnnotations(&iamRoleShowOutput{}), ", "))
Expand All @@ -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
Expand Down
53 changes: 5 additions & 48 deletions cmd/iam_role_update.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cmd

import (
"encoding/json"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -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)) {
Expand All @@ -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
}
Expand All @@ -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
Expand Down

0 comments on commit 9223f78

Please sign in to comment.