From b46487f6c0ddb67b8f66744764a7b02401a0973f Mon Sep 17 00:00:00 2001 From: Predrag Janosevic Date: Wed, 18 Oct 2023 09:12:28 +0200 Subject: [PATCH] Add iam org policy commands --- cmd/iam_org_policy.go | 14 + cmd/iam_org_policy_show.go | 127 ++++++++ cmd/iam_org_policy_update.go | 246 +++++++++++++++ go.mod | 2 +- go.sum | 4 +- .../exoscale/egoscale/v2/client_mock.go | 107 +++++++ vendor/github.com/exoscale/egoscale/v2/iam.go | 55 ++++ .../exoscale/egoscale/v2/iam_api_key.go | 106 +++++++ .../exoscale/egoscale/v2/iam_org_policy.go | 77 +++++ .../exoscale/egoscale/v2/iam_role.go | 297 ++++++++++++++++++ .../exoscale/egoscale/version/version.go | 2 +- vendor/modules.txt | 2 +- 12 files changed, 1034 insertions(+), 5 deletions(-) create mode 100644 cmd/iam_org_policy.go create mode 100644 cmd/iam_org_policy_show.go create mode 100644 cmd/iam_org_policy_update.go create mode 100644 vendor/github.com/exoscale/egoscale/v2/iam.go create mode 100644 vendor/github.com/exoscale/egoscale/v2/iam_api_key.go create mode 100644 vendor/github.com/exoscale/egoscale/v2/iam_org_policy.go create mode 100644 vendor/github.com/exoscale/egoscale/v2/iam_role.go diff --git a/cmd/iam_org_policy.go b/cmd/iam_org_policy.go new file mode 100644 index 000000000..993b421ed --- /dev/null +++ b/cmd/iam_org_policy.go @@ -0,0 +1,14 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +var iamOrgPolicyCmd = &cobra.Command{ + Use: "org-policy", + Short: "IAM Organization Policy management", +} + +func init() { + iamCmd.AddCommand(iamOrgPolicyCmd) +} diff --git a/cmd/iam_org_policy_show.go b/cmd/iam_org_policy_show.go new file mode 100644 index 000000000..65633ee3a --- /dev/null +++ b/cmd/iam_org_policy_show.go @@ -0,0 +1,127 @@ +package cmd + +import ( + "fmt" + "os" + "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/table" + "github.com/exoscale/cli/utils" + exoapi "github.com/exoscale/egoscale/v2/api" +) + +type iamOrgPolicyShowOutput struct { + DefaultServiceStrategy string `json:"default-service-strategy"` + Services map[string]iamOrgPolicyServiceShowOutput `json:"services"` +} + +type iamOrgPolicyServiceShowOutput struct { + Type string `json:"type"` + Rules []iamOrgPolicyServiceRuleShowOutput `json:"rules"` +} + +type iamOrgPolicyServiceRuleShowOutput struct { + Action string `json:"action"` + Expression string `json:"expression"` + Resources []string `json:"resources,omitempty"` +} + +func (o *iamOrgPolicyShowOutput) ToJSON() { output.JSON(o) } +func (o *iamOrgPolicyShowOutput) ToText() { output.Text(o) } +func (o *iamOrgPolicyShowOutput) ToTable() { + t := table.NewTable(os.Stdout) + t.SetAutoMergeCells(true) + + t.SetHeader([]string{ + "Service", + fmt.Sprintf("Type (default strategy \"%s\")", o.DefaultServiceStrategy), + "Rule Action", + "Rule Expression", + "Rule Resources", + }) + defer t.Render() + + for name, service := range o.Services { + if len(service.Rules) == 0 { + t.Append([]string{name, service.Type, "", "", ""}) + continue + } + + for _, rule := range service.Rules { + t.Append([]string{ + name, + service.Type, + rule.Action, + rule.Expression, + strings.Join(rule.Resources, ","), + }) + } + } +} + +type iamOrgPolicyShowCmd struct { + cliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"show"` +} + +func (c *iamOrgPolicyShowCmd) cmdAliases() []string { return gShowAlias } + +func (c *iamOrgPolicyShowCmd) cmdShort() string { + return "Show Org policy details" +} + +func (c *iamOrgPolicyShowCmd) cmdLong() string { + return fmt.Sprintf(`This command shows IAM Org Policy details. + +Supported output template annotations: %s`, + strings.Join(output.TemplateAnnotations(&iamOrgPolicyShowOutput{}), ", ")) +} + +func (c *iamOrgPolicyShowCmd) cmdPreRun(cmd *cobra.Command, args []string) error { + return cliCommandDefaultPreRun(c, cmd, args) +} + +func (c *iamOrgPolicyShowCmd) cmdRun(_ *cobra.Command, _ []string) error { + zone := account.CurrentAccount.DefaultZone + ctx := exoapi.WithEndpoint(gContext, exoapi.NewReqEndpoint(account.CurrentAccount.Environment, zone)) + + policy, err := globalstate.EgoscaleClient.GetIAMOrgPolicy(ctx, zone) + if err != nil { + return err + } + + out := iamOrgPolicyShowOutput{ + DefaultServiceStrategy: policy.DefaultServiceStrategy, + Services: map[string]iamOrgPolicyServiceShowOutput{}, + } + + for name, service := range policy.Services { + rules := []iamOrgPolicyServiceRuleShowOutput{} + for _, rule := range service.Rules { + rules = append(rules, iamOrgPolicyServiceRuleShowOutput{ + Action: utils.DefaultString(rule.Action, ""), + Expression: utils.DefaultString(rule.Expression, ""), + Resources: rule.Resources, + }) + } + + out.Services[name] = iamOrgPolicyServiceShowOutput{ + Type: utils.DefaultString(service.Type, ""), + Rules: rules, + } + } + + return c.outputFunc(&out, nil) +} + +func init() { + cobra.CheckErr(registerCLICommand(iamOrgPolicyCmd, &iamOrgPolicyShowCmd{ + cliCommandSettings: defaultCLICmdSettings(), + })) +} diff --git a/cmd/iam_org_policy_update.go b/cmd/iam_org_policy_update.go new file mode 100644 index 000000000..1e654acf9 --- /dev/null +++ b/cmd/iam_org_policy_update.go @@ -0,0 +1,246 @@ +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 iamOrgPolicyUpdate struct { + cliCommandSettings `cli-cmd:"-"` + + _ bool `cli-cmd:"update"` + + // Root settings + DefaultServiceStrategy string `cli-flag:"default-service-strategy" cli-usage:"The default service strategy applies to all service classes that have not been explicitly configured. Allowed values are 'allow' and 'deny'"` + + // Actions (mutually exclusive) + Clear bool `cli-flag:"clear" cli-usage:"Remove all existing service classes"` + ReplacePolicy string `cli-flag:"replace-policy" cli-usage:"Replace the whole policy. New policy must be provided in JSON format. If value '-' is used, policy is read from stdin"` + DeleteService string `cli-flag:"delete-service" cli-usage:"Delete service class"` + AddService string `cli-flag:"add-service" cli-usage:"Add service class"` + AddServiceRules string `cli-flag:"add-service-rules" cli-usage:"Update service class by adding more rules"` + + // Service level settings + ServiceType string `cli-flag:"service-type" cli-usage:"Default Strategy for service type. Allowed values: 'allow', 'deny' and 'rules'. Required for --add-service, optional for --update-service"` +} + +func (c *iamOrgPolicyUpdate) cmdAliases() []string { return nil } + +func (c *iamOrgPolicyUpdate) cmdShort() string { + return "Update IAM Org policy" +} + +func (c *iamOrgPolicyUpdate) cmdLong() string { + return fmt.Sprintf(`This command updates an IAM Organization Policy. + +Command requires exacly one flag to be set from the following: --clear, --replace-policy, --delete-service, --add-service, --update-service. + +Supported output template annotations: %s`, + strings.Join(output.TemplateAnnotations(&iamOrgPolicyShowOutput{}), ", ")) +} + +func (c *iamOrgPolicyUpdate) cmdUse() string { + return "exo iam org-policy update [flags] test" +} + +func (c *iamOrgPolicyUpdate) cmdPreRun(cmd *cobra.Command, args []string) error { + err := cliCommandDefaultPreRun(c, cmd, args) + if err != nil { + return err + } + + counter := 0 + if c.Clear { + counter++ + } + if c.ReplacePolicy != "" { + counter++ + } + if c.DeleteService != "" { + counter++ + } + if c.AddService != "" { + counter++ + } + if c.AddServiceRules != "" { + counter++ + } + + if counter == 0 || counter > 1 { + return errors.New("command requires exacly one flag to be set from the following: --clear, --replace-policy, --delete-service, --add-service, --update-service") + } + + return nil +} + +func (c *iamOrgPolicyUpdate) cmdRun(cmd *cobra.Command, args []string) error { + zone := account.CurrentAccount.DefaultZone + ctx := exoapi.WithEndpoint(gContext, exoapi.NewReqEndpoint(account.CurrentAccount.Environment, zone)) + + policy, err := globalstate.EgoscaleClient.GetIAMOrgPolicy(ctx, zone) + if err != nil { + return err + } + + switch { + case c.ReplacePolicy != "": + if c.ReplacePolicy == "-" { + inputReader := cmd.InOrStdin() + b, err := io.ReadAll(inputReader) + if err != nil { + return fmt.Errorf("failed to read policy from stdin: %w", err) + } + + c.ReplacePolicy = string(b) + } + + var obj iamOrgPolicyShowOutput + err := json.Unmarshal([]byte(c.ReplacePolicy), &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 + }() + } + + if len(rl.Resources) > 0 { + rule.Resources = rl.Resources + } + + service.Rules = append(service.Rules, rule) + } + } + + policy.Services[name] = service + } + } + case c.Clear: + policy.Services = map[string]exoscale.IAMPolicyService{} + case c.DeleteService != "": + if _, found := policy.Services[c.DeleteService]; !found { + return fmt.Errorf("service class %q not found", c.DeleteService) + } + + delete(policy.Services, c.DeleteService) + case c.AddService != "": + if _, found := policy.Services[c.AddService]; found { + return fmt.Errorf("service class %q already exists in policy", c.AddService) + } + + service := exoscale.IAMPolicyService{} + + if c.ServiceType == "" { + return errors.New("--service-type must be specified when --add-service is used") + } + if c.ServiceType != "allow" && c.ServiceType != "deny" && c.ServiceType != "rules" { + return errors.New("allowed values for --service-type are: 'allow', 'deny' and 'rules'") + } + + service.Type = &c.ServiceType + + if c.ServiceType == "rules" { + // For rules service type arguments will hold pairs of "action" and "expression" definitions. + if len(args) == 0 || len(args)%2 != 0 { + return errors.New("at least one rule must be specified when --service-type is 'rules'") + } + + service.Rules = []exoscale.IAMPolicyServiceRule{} + + for i := 0; i < len(args); i = i + 2 { + rule := exoscale.IAMPolicyServiceRule{ + Action: &args[i], + Expression: &args[i+1], + } + + service.Rules = append(service.Rules, rule) + } + } + + policy.Services[c.AddService] = service + case c.AddServiceRules != "": + if _, found := policy.Services[c.AddServiceRules]; !found { + return fmt.Errorf("service class %q not found", c.AddService) + } + + service := policy.Services[c.AddServiceRules] + + if *service.Type != "rules" { + return fmt.Errorf("cannot add rules to service class of type %q", *service.Type) + } + + // For rules service type arguments must hold pairs of "action" and "expression" definitions. + if len(args) == 0 || len(args)%2 != 0 { + return errors.New("at least one rule must be specified when --service-type is 'rules'") + } + + for i := 0; i < len(args); i = i + 2 { + rule := exoscale.IAMPolicyServiceRule{ + Action: &args[i], + Expression: &args[i+1], + } + + service.Rules = append(service.Rules, rule) + } + + policy.Services[c.AddServiceRules] = service + } + + err = globalstate.EgoscaleClient.UpdateIAMOrgPolicy(ctx, zone, policy) + if err != nil { + return err + } + + if !globalstate.Quiet { + return (&iamOrgPolicyShowCmd{ + cliCommandSettings: c.cliCommandSettings, + }).cmdRun(nil, nil) + } + + return nil +} + +func init() { + cobra.CheckErr(registerCLICommand(iamOrgPolicyCmd, &iamOrgPolicyUpdate{ + cliCommandSettings: defaultCLICmdSettings(), + })) +} diff --git a/go.mod b/go.mod index 383308fe2..82c28ebec 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.2.0 github.com/aws/smithy-go v1.1.0 github.com/dustin/go-humanize v1.0.1 - github.com/exoscale/egoscale v0.101.0 + github.com/exoscale/egoscale v0.102.0 github.com/exoscale/openapi-cli-generator v1.1.0 github.com/fatih/camelcase v1.0.0 github.com/hashicorp/go-multierror v1.1.0 diff --git a/go.sum b/go.sum index 32fd65173..17cdc1e6f 100644 --- a/go.sum +++ b/go.sum @@ -194,8 +194,8 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= -github.com/exoscale/egoscale v0.101.0 h1:BFCJsoAmHFN4kg+NgxzkfLD+ajs51VSSwdCDAycBNNk= -github.com/exoscale/egoscale v0.101.0/go.mod h1:szh4hWSVh+ylgfti4AFR4mkRaCfUyUXSKS3PihlcOco= +github.com/exoscale/egoscale v0.102.0 h1:kIAg2n9Fowk/OCEgDwFNDAMtE+ls2HnGRtqPL22STJI= +github.com/exoscale/egoscale v0.102.0/go.mod h1:szh4hWSVh+ylgfti4AFR4mkRaCfUyUXSKS3PihlcOco= github.com/exoscale/openapi-cli-generator v1.1.0 h1:fYjmPqHR5vxlOBrbvde7eo7bISNQIFxsGn4A5/acwKA= github.com/exoscale/openapi-cli-generator v1.1.0/go.mod h1:TZBnbT7f3hJ5ImyUphJwRM+X5xF/zCQZ6o8a42gQeTs= github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= diff --git a/vendor/github.com/exoscale/egoscale/v2/client_mock.go b/vendor/github.com/exoscale/egoscale/v2/client_mock.go index 25f54fdb2..48f66b65d 100644 --- a/vendor/github.com/exoscale/egoscale/v2/client_mock.go +++ b/vendor/github.com/exoscale/egoscale/v2/client_mock.go @@ -1184,3 +1184,110 @@ func (m *oapiClientMock) StopDbaasMysqlMigrationWithResponse( args := m.Called(ctx, name, reqEditors) return args.Get(0).(*oapi.StopDbaasMysqlMigrationResponse), args.Error(1) } + +func (m *oapiClientMock) GetIamOrganizationPolicyWithResponse( + ctx context.Context, + reqEditors ...oapi.RequestEditorFn, +) (*oapi.GetIamOrganizationPolicyResponse, error) { + args := m.Called(ctx, reqEditors) + return args.Get(0).(*oapi.GetIamOrganizationPolicyResponse), args.Error(1) +} + +func (m *oapiClientMock) UpdateIamOrganizationPolicyWithResponse( + ctx context.Context, + body oapi.UpdateIamOrganizationPolicyJSONRequestBody, + reqEditors ...oapi.RequestEditorFn, +) (*oapi.UpdateIamOrganizationPolicyResponse, error) { + args := m.Called(ctx, body, reqEditors) + return args.Get(0).(*oapi.UpdateIamOrganizationPolicyResponse), args.Error(1) +} + +func (m *oapiClientMock) GetIamRoleWithResponse( + ctx context.Context, + id string, + reqEditors ...oapi.RequestEditorFn, +) (*oapi.GetIamRoleResponse, error) { + args := m.Called(ctx, id, reqEditors) + return args.Get(0).(*oapi.GetIamRoleResponse), args.Error(1) +} + +func (m *oapiClientMock) ListIamRolesWithResponse( + ctx context.Context, + reqEditors ...oapi.RequestEditorFn, +) (*oapi.ListIamRolesResponse, error) { + args := m.Called(ctx, reqEditors) + return args.Get(0).(*oapi.ListIamRolesResponse), args.Error(1) +} + +func (m *oapiClientMock) CreateIamRoleWithResponse( + ctx context.Context, + body oapi.CreateIamRoleJSONRequestBody, + reqEditors ...oapi.RequestEditorFn, +) (*oapi.CreateIamRoleResponse, error) { + args := m.Called(ctx, body, reqEditors) + return args.Get(0).(*oapi.CreateIamRoleResponse), args.Error(1) +} + +func (m *oapiClientMock) DeleteIamRoleWithResponse( + ctx context.Context, + id string, + reqEditors ...oapi.RequestEditorFn, +) (*oapi.DeleteIamRoleResponse, error) { + args := m.Called(ctx, id, reqEditors) + return args.Get(0).(*oapi.DeleteIamRoleResponse), args.Error(1) +} + +func (m *oapiClientMock) UpdateIamRoleWithResponse( + ctx context.Context, + id string, + body oapi.UpdateIamRoleJSONRequestBody, + reqEditors ...oapi.RequestEditorFn, +) (*oapi.UpdateIamRoleResponse, error) { + args := m.Called(ctx, id, body, reqEditors) + return args.Get(0).(*oapi.UpdateIamRoleResponse), args.Error(1) +} + +func (m *oapiClientMock) UpdateIamRolePolicyWithResponse( + ctx context.Context, + id string, + body oapi.UpdateIamRolePolicyJSONRequestBody, + reqEditors ...oapi.RequestEditorFn, +) (*oapi.UpdateIamRolePolicyResponse, error) { + args := m.Called(ctx, id, body, reqEditors) + return args.Get(0).(*oapi.UpdateIamRolePolicyResponse), args.Error(1) +} + +func (m *oapiClientMock) GetApiKeyWithResponse( + ctx context.Context, + id string, + reqEditors ...oapi.RequestEditorFn, +) (*oapi.GetApiKeyResponse, error) { + args := m.Called(ctx, id, reqEditors) + return args.Get(0).(*oapi.GetApiKeyResponse), args.Error(1) +} + +func (m *oapiClientMock) ListApiKeysWithResponse( + ctx context.Context, + reqEditors ...oapi.RequestEditorFn, +) (*oapi.ListApiKeysResponse, error) { + args := m.Called(ctx, reqEditors) + return args.Get(0).(*oapi.ListApiKeysResponse), args.Error(1) +} + +func (m *oapiClientMock) CreateApiKeyWithResponse( + ctx context.Context, + body oapi.CreateApiKeyJSONRequestBody, + reqEditors ...oapi.RequestEditorFn, +) (*oapi.CreateApiKeyResponse, error) { + args := m.Called(ctx, body, reqEditors) + return args.Get(0).(*oapi.CreateApiKeyResponse), args.Error(1) +} + +func (m *oapiClientMock) DeleteApiKeyWithResponse( + ctx context.Context, + id string, + reqEditors ...oapi.RequestEditorFn, +) (*oapi.DeleteApiKeyResponse, error) { + args := m.Called(ctx, id, reqEditors) + return args.Get(0).(*oapi.DeleteApiKeyResponse), args.Error(1) +} diff --git a/vendor/github.com/exoscale/egoscale/v2/iam.go b/vendor/github.com/exoscale/egoscale/v2/iam.go new file mode 100644 index 000000000..beb5d688c --- /dev/null +++ b/vendor/github.com/exoscale/egoscale/v2/iam.go @@ -0,0 +1,55 @@ +package v2 + +import ( + "github.com/exoscale/egoscale/v2/oapi" +) + +// IAMPolicy represents an IAM policy resource. +type IAMPolicy struct { + DefaultServiceStrategy string + Services map[string]IAMPolicyService +} + +// IAMPolicyService represents a service of IAM policy. +type IAMPolicyService struct { + Type *string + Rules []IAMPolicyServiceRule +} + +// IamPolicyServiceRule represents service rule of IAM policy. +type IAMPolicyServiceRule struct { + Action *string + Expression *string + Resources []string +} + +func iamPolicyFromAPI(r *oapi.IamPolicy) *IAMPolicy { + services := make(map[string]IAMPolicyService, len(r.Services.AdditionalProperties)) + for name, service := range r.Services.AdditionalProperties { + rules := []IAMPolicyServiceRule{} + if service.Rules != nil && len(*service.Rules) > 0 { + for _, rule := range *service.Rules { + resources := []string{} + if rule.Resources != nil && len(*rule.Resources) > 0 { + resources = *rule.Resources + } + + rules = append(rules, IAMPolicyServiceRule{ + Action: (*string)(rule.Action), + Expression: rule.Expression, + Resources: resources, + }) + } + } + + services[name] = IAMPolicyService{ + Type: (*string)(service.Type), + Rules: rules, + } + } + + return &IAMPolicy{ + DefaultServiceStrategy: string(r.DefaultServiceStrategy), + Services: services, + } +} diff --git a/vendor/github.com/exoscale/egoscale/v2/iam_api_key.go b/vendor/github.com/exoscale/egoscale/v2/iam_api_key.go new file mode 100644 index 000000000..1c8c15223 --- /dev/null +++ b/vendor/github.com/exoscale/egoscale/v2/iam_api_key.go @@ -0,0 +1,106 @@ +package v2 + +import ( + "context" + + apiv2 "github.com/exoscale/egoscale/v2/api" + "github.com/exoscale/egoscale/v2/oapi" +) + +// APIKey represents an IAM API Key resource. +type APIKey struct { + Key *string `req-for:"delete"` + Name *string `req-for:"create"` + RoleID *string `req-for:"create"` +} + +func apiKeyFromAPI(r *oapi.IamApiKey) *APIKey { + return &APIKey{ + Key: r.Key, + Name: r.Name, + RoleID: r.RoleId, + } +} + +// GetAPIKey returns the IAM API Key. +func (c *Client) GetAPIKey(ctx context.Context, zone, key string) (*APIKey, error) { + resp, err := c.GetApiKeyWithResponse(apiv2.WithZone(ctx, zone), key) + if err != nil { + return nil, err + } + + return apiKeyFromAPI(resp.JSON200), nil +} + +// ListAPIKeys returns the list of existing IAM API Keys. +func (c *Client) ListAPIKeys(ctx context.Context, zone string) ([]*APIKey, error) { + list := make([]*APIKey, 0) + + resp, err := c.ListApiKeysWithResponse(apiv2.WithZone(ctx, zone)) + if err != nil { + return nil, err + } + + if resp.JSON200.ApiKeys != nil { + for i := range *resp.JSON200.ApiKeys { + list = append(list, apiKeyFromAPI(&(*resp.JSON200.ApiKeys)[i])) + } + } + + return list, nil +} + +// CreateAPIKey creates a IAM API Key. +func (c *Client) CreateAPIKey( + ctx context.Context, + zone string, + apiKey *APIKey, +) (key *APIKey, secret string, err error) { + if err = validateOperationParams(apiKey, "create"); err != nil { + return + } + + req := oapi.CreateApiKeyJSONRequestBody{ + Name: *apiKey.Name, + RoleId: *apiKey.RoleID, + } + + resp, err := c.CreateApiKeyWithResponse( + apiv2.WithZone(ctx, zone), + req, + ) + if err != nil { + return + } + + key = &APIKey{ + Key: resp.JSON200.Key, + Name: resp.JSON200.Name, + RoleID: resp.JSON200.RoleId, + } + secret = *resp.JSON200.Secret + + return +} + +// DeleteAPIKey deletes IAM API Key. +func (c *Client) DeleteAPIKey(ctx context.Context, zone string, apiKey *APIKey) error { + if err := validateOperationParams(apiKey, "delete"); err != nil { + return err + } + + resp, err := c.DeleteApiKeyWithResponse(apiv2.WithZone(ctx, zone), *apiKey.Key) + if err != nil { + return err + } + + _, err = oapi.NewPoller(). + WithTimeout(c.timeout). + WithInterval(c.pollInterval). + Poll(ctx, oapi.OperationPoller(c, zone, *resp.JSON200.Id)) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/exoscale/egoscale/v2/iam_org_policy.go b/vendor/github.com/exoscale/egoscale/v2/iam_org_policy.go new file mode 100644 index 000000000..52a15bc2f --- /dev/null +++ b/vendor/github.com/exoscale/egoscale/v2/iam_org_policy.go @@ -0,0 +1,77 @@ +package v2 + +import ( + "context" + + apiv2 "github.com/exoscale/egoscale/v2/api" + "github.com/exoscale/egoscale/v2/oapi" +) + +// GetIAMOrgPolicy returns the IAM Organization policy. +func (c *Client) GetIAMOrgPolicy(ctx context.Context, zone string) (*IAMPolicy, error) { + resp, err := c.GetIamOrganizationPolicyWithResponse(apiv2.WithZone(ctx, zone)) + if err != nil { + return nil, err + } + + return iamPolicyFromAPI(resp.JSON200), nil +} + +// UpdateIAMOrgPolicy updates existing IAM Organization policy. +func (c *Client) UpdateIAMOrgPolicy(ctx context.Context, zone string, policy *IAMPolicy) error { + + req := oapi.UpdateIamOrganizationPolicyJSONRequestBody{ + DefaultServiceStrategy: oapi.IamPolicyDefaultServiceStrategy(policy.DefaultServiceStrategy), + Services: oapi.IamPolicy_Services{ + AdditionalProperties: map[string]oapi.IamServicePolicy{}, + }, + } + + if len(policy.Services) > 0 { + for name, service := range policy.Services { + t := oapi.IamServicePolicy{ + Type: (*oapi.IamServicePolicyType)(service.Type), + } + + if service.Rules != nil { + rules := []oapi.IamServicePolicyRule{} + + for _, rule := range service.Rules { + r := oapi.IamServicePolicyRule{ + Action: (*oapi.IamServicePolicyRuleAction)(rule.Action), + Expression: rule.Expression, + } + + if rule.Resources != nil { + r.Resources = &rule.Resources + } + + rules = append(rules, r) + } + + t.Rules = &rules + } + + req.Services.AdditionalProperties[name] = t + + } + } + + resp, err := c.UpdateIamOrganizationPolicyWithResponse( + apiv2.WithZone(ctx, zone), + req, + ) + if err != nil { + return err + } + + _, err = oapi.NewPoller(). + WithTimeout(c.timeout). + WithInterval(c.pollInterval). + Poll(ctx, oapi.OperationPoller(c, zone, *resp.JSON200.Id)) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/exoscale/egoscale/v2/iam_role.go b/vendor/github.com/exoscale/egoscale/v2/iam_role.go new file mode 100644 index 000000000..4e23eca55 --- /dev/null +++ b/vendor/github.com/exoscale/egoscale/v2/iam_role.go @@ -0,0 +1,297 @@ +package v2 + +import ( + "context" + + apiv2 "github.com/exoscale/egoscale/v2/api" + "github.com/exoscale/egoscale/v2/oapi" +) + +// IAMRole represents an IAM Role resource. +type IAMRole struct { + ID *string `req-for:"delete,update"` + Description *string + Editable *bool + Labels map[string]string + Name *string `req-for:"create"` + Permissions []string + Policy *IAMPolicy +} + +func iamRoleFromAPI(r *oapi.IamRole) *IAMRole { + labels := map[string]string{} + if r.Labels != nil { + labels = r.Labels.AdditionalProperties + } + + permissions := []string{} + if r.Permissions != nil { + for _, p := range *r.Permissions { + permissions = append(permissions, string(p)) + } + } + + return &IAMRole{ + ID: r.Id, + Description: r.Description, + Editable: r.Editable, + Labels: labels, + Name: r.Name, + Permissions: permissions, + + Policy: iamPolicyFromAPI(r.Policy), + } +} + +// GetIAMRole returns the IAM Role. +func (c *Client) GetIAMRole(ctx context.Context, zone, id string) (*IAMRole, error) { + resp, err := c.GetIamRoleWithResponse(apiv2.WithZone(ctx, zone), id) + if err != nil { + return nil, err + } + + return iamRoleFromAPI(resp.JSON200), nil +} + +// ListIAMRoles returns the list of existing IAM Roles. +func (c *Client) ListIAMRoles(ctx context.Context, zone string) ([]*IAMRole, error) { + list := make([]*IAMRole, 0) + + resp, err := c.ListIamRolesWithResponse(apiv2.WithZone(ctx, zone)) + if err != nil { + return nil, err + } + + if resp.JSON200.IamRoles != nil { + for i := range *resp.JSON200.IamRoles { + list = append(list, iamRoleFromAPI(&(*resp.JSON200.IamRoles)[i])) + } + } + + return list, nil +} + +// CreateIAMRole creates a IAM Role. +func (c *Client) CreateIAMRole( + ctx context.Context, + zone string, + role *IAMRole, +) (*IAMRole, error) { + if err := validateOperationParams(role, "create"); err != nil { + return nil, err + } + + req := oapi.CreateIamRoleJSONRequestBody{ + Name: *role.Name, + Description: role.Description, + Editable: role.Editable, + } + + if role.Labels != nil { + req.Labels = &oapi.Labels{ + AdditionalProperties: role.Labels, + } + } + + if role.Permissions != nil { + t := []oapi.CreateIamRoleJSONBodyPermissions{} + for _, p := range role.Permissions { + t = append(t, oapi.CreateIamRoleJSONBodyPermissions(p)) + } + + req.Permissions = &t + } + + if role.Policy != nil { + t := oapi.IamPolicy{ + DefaultServiceStrategy: oapi.IamPolicyDefaultServiceStrategy(role.Policy.DefaultServiceStrategy), + Services: oapi.IamPolicy_Services{ + AdditionalProperties: map[string]oapi.IamServicePolicy{}, + }, + } + + if len(role.Policy.Services) > 0 { + for name, service := range role.Policy.Services { + s := oapi.IamServicePolicy{ + Type: (*oapi.IamServicePolicyType)(service.Type), + } + + if service.Rules != nil { + rules := []oapi.IamServicePolicyRule{} + + for _, rule := range service.Rules { + r := oapi.IamServicePolicyRule{ + Action: (*oapi.IamServicePolicyRuleAction)(rule.Action), + Expression: rule.Expression, + } + + if rule.Resources != nil { + r.Resources = &rule.Resources + } + + rules = append(rules, r) + } + + s.Rules = &rules + } + + t.Services.AdditionalProperties[name] = s + + } + } + + req.Policy = &t + } + + resp, err := c.CreateIamRoleWithResponse( + apiv2.WithZone(ctx, zone), + req, + ) + if err != nil { + return nil, err + } + + res, err := oapi.NewPoller(). + WithTimeout(c.timeout). + WithInterval(c.pollInterval). + Poll(ctx, oapi.OperationPoller(c, zone, *resp.JSON200.Id)) + if err != nil { + return nil, err + } + + return c.GetIAMRole(ctx, zone, *res.(*struct { + Command *string `json:"command,omitempty"` + Id *string `json:"id,omitempty"` // revive:disable-line + Link *string `json:"link,omitempty"` + }).Id) +} + +// DeleteIAMRole deletes IAM Role. +func (c *Client) DeleteIAMRole(ctx context.Context, zone string, role *IAMRole) error { + if err := validateOperationParams(role, "delete"); err != nil { + return err + } + + resp, err := c.DeleteIamRoleWithResponse(apiv2.WithZone(ctx, zone), *role.ID) + if err != nil { + return err + } + + _, err = oapi.NewPoller(). + WithTimeout(c.timeout). + WithInterval(c.pollInterval). + Poll(ctx, oapi.OperationPoller(c, zone, *resp.JSON200.Id)) + if err != nil { + return err + } + + return nil +} + +// UpdateIAMRole updates existing IAM Role. +func (c *Client) UpdateIAMRole(ctx context.Context, zone string, role *IAMRole) error { + if err := validateOperationParams(role, "update"); err != nil { + return err + } + + req := oapi.UpdateIamRoleJSONRequestBody{ + Description: role.Description, + } + + if role.Labels != nil { + req.Labels = &oapi.Labels{ + AdditionalProperties: role.Labels, + } + } + + if role.Permissions != nil { + t := []oapi.UpdateIamRoleJSONBodyPermissions{} + for _, p := range role.Permissions { + t = append(t, oapi.UpdateIamRoleJSONBodyPermissions(p)) + } + + req.Permissions = &t + } + + resp, err := c.UpdateIamRoleWithResponse( + apiv2.WithZone(ctx, zone), + *role.ID, + req, + ) + if err != nil { + return err + } + + _, err = oapi.NewPoller(). + WithTimeout(c.timeout). + WithInterval(c.pollInterval). + Poll(ctx, oapi.OperationPoller(c, zone, *resp.JSON200.Id)) + if err != nil { + return err + } + + return nil +} + +// UpdateIAMRolePolicy updates existing IAM Role policy. +func (c *Client) UpdateIAMRolePolicy(ctx context.Context, zone string, role *IAMRole) error { + if err := validateOperationParams(role, "update"); err != nil { + return err + } + + req := oapi.UpdateIamRolePolicyJSONRequestBody{ + DefaultServiceStrategy: oapi.IamPolicyDefaultServiceStrategy(role.Policy.DefaultServiceStrategy), + Services: oapi.IamPolicy_Services{ + AdditionalProperties: map[string]oapi.IamServicePolicy{}, + }, + } + + if len(role.Policy.Services) > 0 { + for name, service := range role.Policy.Services { + t := oapi.IamServicePolicy{ + Type: (*oapi.IamServicePolicyType)(service.Type), + } + + if service.Rules != nil { + rules := []oapi.IamServicePolicyRule{} + + for _, rule := range service.Rules { + r := oapi.IamServicePolicyRule{ + Action: (*oapi.IamServicePolicyRuleAction)(rule.Action), + Expression: rule.Expression, + } + + if rule.Resources != nil { + r.Resources = &rule.Resources + } + + rules = append(rules, r) + } + + t.Rules = &rules + } + + req.Services.AdditionalProperties[name] = t + + } + } + + resp, err := c.UpdateIamRolePolicyWithResponse( + apiv2.WithZone(ctx, zone), + *role.ID, + req, + ) + if err != nil { + return err + } + + _, err = oapi.NewPoller(). + WithTimeout(c.timeout). + WithInterval(c.pollInterval). + Poll(ctx, oapi.OperationPoller(c, zone, *resp.JSON200.Id)) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/exoscale/egoscale/version/version.go b/vendor/github.com/exoscale/egoscale/version/version.go index 5e37c5659..9678f6625 100644 --- a/vendor/github.com/exoscale/egoscale/version/version.go +++ b/vendor/github.com/exoscale/egoscale/version/version.go @@ -2,4 +2,4 @@ package version // Version represents the current egoscale version. -const Version = "0.101.0" +const Version = "0.102.0" diff --git a/vendor/modules.txt b/vendor/modules.txt index 1860a06b9..f2d87693b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -214,7 +214,7 @@ github.com/dlclark/regexp2/syntax # github.com/dustin/go-humanize v1.0.1 ## explicit; go 1.16 github.com/dustin/go-humanize -# github.com/exoscale/egoscale v0.101.0 +# github.com/exoscale/egoscale v0.102.0 ## explicit; go 1.17 github.com/exoscale/egoscale/v2 github.com/exoscale/egoscale/v2/api