Skip to content

Commit

Permalink
Merge pull request #38 from yeya24/add-federaterole
Browse files Browse the repository at this point in the history
Support updating federated role
  • Loading branch information
openshift-merge-robot authored Jul 16, 2020
2 parents c9c3ec0 + 1937de1 commit fc304c3
Show file tree
Hide file tree
Showing 11 changed files with 862 additions and 1 deletion.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,13 @@ aws_account_operator_total_accounts_crs_claimed{name="aws-account-operator"} =>
```bash
osdctl clusterdeployment list
```

### AWS Account Federated Role Apply

```bash
# apply via URL
osdctl federatedrole apply -u <URL>

# apply via local file
osdctl federatedrole apply -f <yaml file>
```
197 changes: 197 additions & 0 deletions cmd/federatedrole/apply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package federatedrole

import (
"context"
"fmt"
"io"
"net/http"
"os"
"path/filepath"

awsv1alpha1 "github.com/openshift/aws-account-operator/pkg/apis/aws/v1alpha1"
"github.com/pkg/errors"
"github.com/spf13/cobra"

"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/cli-runtime/pkg/genericclioptions"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

"github.com/openshift/osd-utils-cli/pkg/k8s"
awsprovider "github.com/openshift/osd-utils-cli/pkg/provider/aws"
)

const (
awsAccountIDLabel = "awsAccountID"
uidLabel = "uid"
)

// newCmdApply implements the apply command to apply federated role CR
func newCmdApply(streams genericclioptions.IOStreams, flags *genericclioptions.ConfigFlags) *cobra.Command {
ops := newApplyOptions(streams, flags)
applyCmd := &cobra.Command{
Use: "apply",
Short: "Apply federated role CR",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(ops.complete(cmd, args))
cmdutil.CheckErr(ops.run())
},
}

applyCmd.Flags().StringVarP(&ops.url, "url", "u", "", "The URL of federated role yaml file")
applyCmd.Flags().StringVarP(&ops.file, "file", "f", "", "The path of federated role yaml file")
applyCmd.Flags().BoolVarP(&ops.verbose, "verbose", "v", false, "Verbose output")

return applyCmd
}

// applyOptions defines the struct for running list account command
type applyOptions struct {
url string
file string

verbose bool

flags *genericclioptions.ConfigFlags
genericclioptions.IOStreams
kubeCli client.Client
}

func newApplyOptions(streams genericclioptions.IOStreams, flags *genericclioptions.ConfigFlags) *applyOptions {
return &applyOptions{
flags: flags,
IOStreams: streams,
}
}

func (o *applyOptions) complete(cmd *cobra.Command, _ []string) error {
if o.file == "" && o.url == "" {
return cmdutil.UsageErrorf(cmd, "Flags file and url cannot be empty at the same time")
}

if o.file != "" && o.url != "" {
return cmdutil.UsageErrorf(cmd, "Flags file and url cannot be set at the same time")
}

var err error
o.kubeCli, err = k8s.NewClient(o.flags)
if err != nil {
return err
}

return nil
}

func (o *applyOptions) run() error {
ctx := context.TODO()

var (
input io.Reader
accountID string
uid string
ok bool
)
if o.url != "" {
resp, err := http.Get(o.url)
if err != nil {
return err
}

if resp.StatusCode/100 != 2 {
return errors.New(fmt.Sprintf("Failed to GET %s, status code %d", o.url, resp.StatusCode))
}

defer resp.Body.Close()
input = resp.Body

} else {
path, err := filepath.Abs(o.file)
if err != nil {
return err
}

file, err := os.Open(path)
if err != nil {
return err
}
input = file
}

var federatedRole awsv1alpha1.AWSFederatedRole
d := yaml.NewYAMLOrJSONDecoder(input, 4096)
if err := d.Decode(&federatedRole); err != nil {
return err
}

// apply federated role CR yaml
desired := federatedRole.DeepCopy()
if _, err := controllerutil.CreateOrUpdate(ctx, o.kubeCli, &federatedRole, func() error {
federatedRole.Spec = desired.Spec
return nil
}); err != nil {
return err
}

var federatedAccountAccesses awsv1alpha1.AWSFederatedAccountAccessList
if err := o.kubeCli.List(ctx, &federatedAccountAccesses, &client.ListOptions{}); err != nil {
return err
}

if len(federatedAccountAccesses.Items) == 0 {
fmt.Fprintln(o.Out, "Cannot find associated AWS federated account accesses")
return nil
}

awsClients := make(map[string]awsprovider.Client, 0)

// find all associated federated account access CR and update them
for _, federatedAccount := range federatedAccountAccesses.Items {
if federatedAccount.Spec.AWSFederatedRole.Namespace == federatedRole.Namespace &&
federatedAccount.Spec.AWSFederatedRole.Name == federatedRole.Name {
if accountID, ok = federatedAccount.Labels[awsAccountIDLabel]; !ok {
return errors.New(fmt.Sprintf(
"Unable to get AWS AccountID label for AWS federated account access CR %s/%s",
federatedAccount.Namespace, federatedAccount.Name))
}

if uid, ok = federatedAccount.Labels[uidLabel]; !ok {
return errors.New(fmt.Sprintf(
"Unable to get UID label for AWS federated account access CR %s/%s",
federatedAccount.Namespace, federatedAccount.Name))
}

var awsClient awsprovider.Client
if awsClient, ok = awsClients[federatedAccount.Namespace]; !ok {
creds, err := k8s.GetAWSAccountCredentials(ctx, o.kubeCli,
federatedAccount.Spec.AWSCustomerCredentialSecret.Namespace,
federatedAccount.Spec.AWSCustomerCredentialSecret.Name)
if err != nil {
return errors.Wrap(err, fmt.Sprintf(
"Failed to get AWS credentials for AWS federated account access CR %s/%s",
federatedAccount.Namespace, federatedAccount.Name))
}

awsClient, err = awsprovider.NewAwsClientWithInput(creds)
if err != nil {
return errors.Wrap(err, fmt.Sprintf(
"Failed to create AWS Setup Client for AWS federated account access CR %s/%s",
federatedAccount.Namespace, federatedAccount.Name))
}
awsClients[federatedAccount.Namespace] = awsClient
}

fmt.Fprintln(o.Out, fmt.Sprintf("Updating IAM policy for AWS federated account access CR %s/%s",
federatedAccount.Namespace, federatedAccount.Name))

if err := awsprovider.RefreshIAMPolicy(awsClient, &federatedRole, accountID, uid); err != nil {
return errors.Wrap(err, fmt.Sprintf("Failed to apply IAM policy for AWS federated account access CR %s/%s",
federatedAccount.Namespace, federatedAccount.Name))
}
}
}

return nil
}
25 changes: 25 additions & 0 deletions cmd/federatedrole/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package federatedrole

import (
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
)

// NewCmdFederatedRole implements the basic federated role command
func NewCmdFederatedRole(streams genericclioptions.IOStreams, flags *genericclioptions.ConfigFlags) *cobra.Command {
getCmd := &cobra.Command{
Use: "federatedrole",
Short: "federated role related commands",
Args: cobra.NoArgs,
DisableAutoGenTag: true,
Run: help,
}

getCmd.AddCommand(newCmdApply(streams, flags))

return getCmd
}

func help(cmd *cobra.Command, _ []string) {
cmd.Help()
}
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

"github.com/openshift/osd-utils-cli/cmd/account"
"github.com/openshift/osd-utils-cli/cmd/clusterdeployment"
"github.com/openshift/osd-utils-cli/cmd/federatedrole"
)

// GitCommit is the short git commit hash from the environment
Expand Down Expand Up @@ -57,6 +58,7 @@ func NewCmdRoot(streams genericclioptions.IOStreams) *cobra.Command {
// add sub commands
rootCmd.AddCommand(account.NewCmdAccount(streams, kubeFlags))
rootCmd.AddCommand(clusterdeployment.NewCmdClusterDeployment(streams, kubeFlags))
rootCmd.AddCommand(federatedrole.NewCmdFederatedRole(streams, kubeFlags))
rootCmd.AddCommand(newCmdMetrics(streams, kubeFlags))

// add docs command
Expand Down
1 change: 1 addition & 0 deletions docs/command/osdctl.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ osdctl [flags]
* [osdctl account](osdctl_account.md) - AWS Account related utilities
* [osdctl clusterdeployment](osdctl_clusterdeployment.md) - cluster deployment related utilities
* [osdctl completion](osdctl_completion.md) - Output shell completion code for the specified shell (bash or zsh)
* [osdctl federatedrole](osdctl_federatedrole.md) - federated role related commands
* [osdctl metrics](osdctl_metrics.md) - Display metrics of aws-account-operator
* [osdctl options](osdctl_options.md) - Print the list of flags inherited by all commands

34 changes: 34 additions & 0 deletions docs/command/osdctl_federatedrole.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## osdctl federatedrole

federated role related commands

### Synopsis

federated role related commands

```
osdctl federatedrole [flags]
```

### Options

```
-h, --help help for federatedrole
```

### Options inherited from parent commands

```
--cluster string The name of the kubeconfig cluster to use
--context string The name of the kubeconfig context to use
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
--kubeconfig string Path to the kubeconfig file to use for CLI requests.
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
-s, --server string The address and port of the Kubernetes API server
```

### SEE ALSO

* [osdctl](osdctl.md) - OSD CLI
* [osdctl federatedrole apply](osdctl_federatedrole_apply.md) - Apply federated role CR

36 changes: 36 additions & 0 deletions docs/command/osdctl_federatedrole_apply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## osdctl federatedrole apply

Apply federated role CR

### Synopsis

Apply federated role CR

```
osdctl federatedrole apply [flags]
```

### Options

```
-f, --file string The path of federated role yaml file
-h, --help help for apply
-u, --url string The URL of federated role yaml file
-v, --verbose Verbose output
```

### Options inherited from parent commands

```
--cluster string The name of the kubeconfig cluster to use
--context string The name of the kubeconfig context to use
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
--kubeconfig string Path to the kubeconfig file to use for CLI requests.
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
-s, --server string The address and port of the Kubernetes API server
```

### SEE ALSO

* [osdctl federatedrole](osdctl_federatedrole.md) - federated role related commands

25 changes: 25 additions & 0 deletions pkg/provider/aws/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ type Client interface {
CreateUser(*iam.CreateUserInput) (*iam.CreateUserOutput, error)
ListUsers(*iam.ListUsersInput) (*iam.ListUsersOutput, error)
AttachUserPolicy(*iam.AttachUserPolicyInput) (*iam.AttachUserPolicyOutput, error)
CreatePolicy(*iam.CreatePolicyInput) (*iam.CreatePolicyOutput, error)
DeletePolicy(*iam.DeletePolicyInput) (*iam.DeletePolicyOutput, error)
AttachRolePolicy(*iam.AttachRolePolicyInput) (*iam.AttachRolePolicyOutput, error)
DetachRolePolicy(*iam.DetachRolePolicyInput) (*iam.DetachRolePolicyOutput, error)
ListAttachedRolePolicies(*iam.ListAttachedRolePoliciesInput) (*iam.ListAttachedRolePoliciesOutput, error)
}

type AwsClient struct {
Expand Down Expand Up @@ -168,3 +173,23 @@ func (c *AwsClient) ListUsers(input *iam.ListUsersInput) (*iam.ListUsersOutput,
func (c *AwsClient) AttachUserPolicy(input *iam.AttachUserPolicyInput) (*iam.AttachUserPolicyOutput, error) {
return c.iamClient.AttachUserPolicy(input)
}

func (c *AwsClient) CreatePolicy(input *iam.CreatePolicyInput) (*iam.CreatePolicyOutput, error) {
return c.iamClient.CreatePolicy(input)
}

func (c *AwsClient) DeletePolicy(input *iam.DeletePolicyInput) (*iam.DeletePolicyOutput, error) {
return c.iamClient.DeletePolicy(input)
}

func (c *AwsClient) AttachRolePolicy(input *iam.AttachRolePolicyInput) (*iam.AttachRolePolicyOutput, error) {
return c.iamClient.AttachRolePolicy(input)
}

func (c *AwsClient) DetachRolePolicy(input *iam.DetachRolePolicyInput) (*iam.DetachRolePolicyOutput, error) {
return c.iamClient.DetachRolePolicy(input)
}

func (c *AwsClient) ListAttachedRolePolicies(input *iam.ListAttachedRolePoliciesInput) (*iam.ListAttachedRolePoliciesOutput, error) {
return c.iamClient.ListAttachedRolePolicies(input)
}
Loading

0 comments on commit fc304c3

Please sign in to comment.