-
Notifications
You must be signed in to change notification settings - Fork 194
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
OCM-6543 | feat: detach cmd to detach policy
Signed-off-by: marcolan018 <llan@redhat.com>
- Loading branch information
1 parent
93a653f
commit 85834eb
Showing
9 changed files
with
462 additions
and
42 deletions.
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,34 @@ | ||
/* | ||
Copyright (c) 2024 Red Hat, Inc. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package detach | ||
|
||
import ( | ||
"github.com/spf13/cobra" | ||
|
||
policy "github.com/openshift/rosa/cmd/detach/policy" | ||
) | ||
|
||
func NewRosaDetachCommand() *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "detach", | ||
Short: "Detach AWS resource", | ||
Long: "Detach AWS resource", | ||
Args: cobra.NoArgs, | ||
} | ||
cmd.AddCommand(policy.NewDetachPolicyCommand()) | ||
return cmd | ||
} |
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,133 @@ | ||
/* | ||
Copyright (c) 2024 Red Hat, Inc. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package policy | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
"github.com/openshift/rosa/pkg/interactive" | ||
"github.com/openshift/rosa/pkg/policy" | ||
"github.com/openshift/rosa/pkg/rosa" | ||
) | ||
|
||
const ( | ||
use = "policy" | ||
short = "Detach AWS IAM Policies from an AWS IAM Role" | ||
long = "Detach AWS IAM Policies from an AWS IAM Role in the authenticated AWS Account" | ||
example = ` # Detach policy <policy_arn_1> and <policy_arn_2> from role <role_name> | ||
rosa detach policy --role-arn=<role_name> --policy-arns=<policy_arn_1>,<policy_arn_2>` | ||
) | ||
|
||
type RosaDetachPolicyOptions struct { | ||
policyArns string | ||
roleName string | ||
} | ||
|
||
func NewRosaDetachPolicyOptions() RosaDetachPolicyOptions { | ||
return RosaDetachPolicyOptions{} | ||
} | ||
|
||
func NewDetachPolicyCommand() *cobra.Command { | ||
options := NewRosaDetachPolicyOptions() | ||
cmd := &cobra.Command{ | ||
Use: use, | ||
Short: short, | ||
Long: long, | ||
Example: example, | ||
Args: cobra.NoArgs, | ||
Run: rosa.DefaultRunner(rosa.RuntimeWithOCMAndAWS(), DetachPolicyRunner(&options)), | ||
} | ||
|
||
flags := cmd.Flags() | ||
flags.StringVarP( | ||
&options.policyArns, | ||
"policy-arns", | ||
"p", | ||
"", | ||
"Policy arn of the policies to be detached from the specified role."+ | ||
" Format should be a comma-separated list. (required).", | ||
) | ||
flags.StringVarP( | ||
&options.roleName, | ||
"role-name", | ||
"r", | ||
"", | ||
"Role name of the role to detach the specified policy (required).", | ||
) | ||
cmd.MarkFlagRequired("policy-arns") | ||
cmd.MarkFlagRequired("role-name") | ||
interactive.AddModeFlag(cmd) | ||
return cmd | ||
} | ||
|
||
func DetachPolicyRunner(userOptions *RosaDetachPolicyOptions) rosa.CommandRunner { | ||
return func(_ context.Context, r *rosa.Runtime, cmd *cobra.Command, _ []string) error { | ||
options := NewRosaDetachPolicyOptions() | ||
options.BindAndValidate(*userOptions) | ||
policySvc := policy.NewPolicyService(r.OCMClient, r.AWSClient) | ||
policyArns := strings.Split(options.policyArns, ",") | ||
err := policySvc.ValidateDetachOptions(options.roleName, policyArns) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
mode, err := interactive.GetMode() | ||
if err != nil { | ||
return err | ||
} | ||
// Determine if interactive mode is needed | ||
if !interactive.Enabled() && !cmd.Flags().Changed("mode") { | ||
interactive.Enable() | ||
} | ||
if interactive.Enabled() { | ||
mode, err = interactive.GetOptionMode(cmd, mode, "Detach policy mode") | ||
if err != nil { | ||
return fmt.Errorf("Expected a valid detach policy mode: %s", err) | ||
} | ||
} | ||
|
||
orgID, _, err := r.OCMClient.GetCurrentOrganization() | ||
if err != nil { | ||
return fmt.Errorf("Failed to get current organization: %s", err) | ||
} | ||
switch mode { | ||
case interactive.ModeAuto: | ||
output, err := policySvc.AutoDetachArbitraryPolicy(options.roleName, policyArns, | ||
r.Creator.AccountID, orgID) | ||
r.Reporter.Infof(output) | ||
if err != nil { | ||
return err | ||
} | ||
case interactive.ModeManual: | ||
r.Reporter.Infof("Run the following command to detach the policy:") | ||
fmt.Print(policySvc.ManualDetachArbitraryPolicy(options.roleName, policyArns, | ||
r.Creator.AccountID, orgID)) | ||
default: | ||
return fmt.Errorf("Invalid mode. Allowed values are %s", interactive.Modes) | ||
} | ||
return nil | ||
} | ||
} | ||
|
||
func (o *RosaDetachPolicyOptions) BindAndValidate(options RosaDetachPolicyOptions) { | ||
o.policyArns = options.policyArns | ||
o.roleName = options.roleName | ||
} |
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,151 @@ | ||
/* | ||
Copyright (c) 2024 Red Hat, Inc. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
package policy | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"testing" | ||
|
||
"go.uber.org/mock/gomock" | ||
|
||
"github.com/aws/aws-sdk-go-v2/aws" | ||
iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types" | ||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
amsv1 "github.com/openshift-online/ocm-sdk-go/accountsmgmt/v1" | ||
. "github.com/openshift-online/ocm-sdk-go/testing" | ||
"github.com/spf13/cobra" | ||
|
||
mock "github.com/openshift/rosa/pkg/aws" | ||
"github.com/openshift/rosa/pkg/aws/tags" | ||
. "github.com/openshift/rosa/pkg/test" | ||
) | ||
|
||
func TestDetachPolicy(t *testing.T) { | ||
RegisterFailHandler(Fail) | ||
RunSpecs(t, "rosa detach policy") | ||
} | ||
|
||
var _ = Describe("rosa detach policy", func() { | ||
Context("Create Command", func() { | ||
It("Returns Command", func() { | ||
|
||
cmd := NewDetachPolicyCommand() | ||
Expect(cmd).NotTo(BeNil()) | ||
|
||
Expect(cmd.Use).To(Equal(use)) | ||
Expect(cmd.Example).To(Equal(example)) | ||
Expect(cmd.Short).To(Equal(short)) | ||
Expect(cmd.Long).To(Equal(long)) | ||
Expect(cmd.Args).NotTo(BeNil()) | ||
Expect(cmd.Run).NotTo(BeNil()) | ||
|
||
flag := cmd.Flags().Lookup("role-name") | ||
Expect(flag).NotTo(BeNil()) | ||
|
||
flag = cmd.Flags().Lookup("policy-arns") | ||
Expect(flag).NotTo(BeNil()) | ||
}) | ||
}) | ||
|
||
Context("Execute command", func() { | ||
|
||
const ( | ||
roleName = "sample-role" | ||
policyArn1 = "sample-policy-arn-1" | ||
policyArn2 = "sample-policy-arn-2" | ||
|
||
roleNotFoundMsg = "roleNotFoundMsg" | ||
policyNotFoundMsg = "policyNotFoundMsg" | ||
) | ||
|
||
var ( | ||
t *TestingRuntime | ||
c *cobra.Command | ||
mockClient *mock.MockClient | ||
options *RosaDetachPolicyOptions | ||
role *iamtypes.Role | ||
) | ||
|
||
BeforeEach(func() { | ||
c = NewDetachPolicyCommand() | ||
options = &RosaDetachPolicyOptions{ | ||
roleName: roleName, | ||
policyArns: policyArn1 + "," + policyArn2, | ||
} | ||
c.Flags().Set("mode", "auto") | ||
role = &iamtypes.Role{ | ||
Tags: []iamtypes.Tag{ | ||
{ | ||
Key: aws.String(tags.RedHatManaged), | ||
Value: aws.String("true"), | ||
}, | ||
}, | ||
} | ||
|
||
t = NewTestRuntime() | ||
mockCtrl := gomock.NewController(GinkgoT()) | ||
mockClient = mock.NewMockClient(mockCtrl) | ||
t.RosaRuntime.AWSClient = mockClient | ||
|
||
account, _ := amsv1.NewAccount().Organization(amsv1.NewOrganization(). | ||
ID("123").ExternalID("456")).Build() | ||
t.ApiServer.AppendHandlers(RespondWithJSON(http.StatusOK, FormatResource(account))) | ||
|
||
}) | ||
|
||
It("Returns an error if the role does not exist", func() { | ||
mockClient.EXPECT().GetRoleByName(roleName).Return(iamtypes.Role{}, fmt.Errorf(roleNotFoundMsg)) | ||
runner := DetachPolicyRunner(options) | ||
err := runner(context.Background(), t.RosaRuntime, c, nil) | ||
Expect(err).To(HaveOccurred()) | ||
Expect(err.Error()).To(Equal(fmt.Sprintf( | ||
"Failed to find the role '%s': %s", roleName, roleNotFoundMsg))) | ||
}) | ||
|
||
It("Returns an error if the role does not has tag 'red-hat-managed'", func() { | ||
mockClient.EXPECT().GetRoleByName(roleName).Return(iamtypes.Role{}, nil) | ||
runner := DetachPolicyRunner(options) | ||
err := runner(context.Background(), t.RosaRuntime, c, nil) | ||
Expect(err).To(HaveOccurred()) | ||
Expect(err.Error()).To(Equal("Cannot attach/detach policies to non-ROSA roles")) | ||
}) | ||
|
||
It("Returns an error if one policy does not exist", func() { | ||
mockClient.EXPECT().GetRoleByName(roleName).Return(*role, nil) | ||
mockClient.EXPECT().IsPolicyExists(policyArn1).Return(nil, fmt.Errorf(policyNotFoundMsg)) | ||
runner := DetachPolicyRunner(options) | ||
err := runner(context.Background(), t.RosaRuntime, c, nil) | ||
Expect(err).To(HaveOccurred()) | ||
Expect(err.Error()).To(Equal(fmt.Sprintf( | ||
"Failed to find the policy '%s': %s", policyArn1, policyNotFoundMsg))) | ||
}) | ||
|
||
It("Detach policy from role", func() { | ||
mockClient.EXPECT().GetRoleByName(roleName).Return(*role, nil) | ||
mockClient.EXPECT().IsPolicyExists(policyArn1).Return(nil, nil) | ||
mockClient.EXPECT().IsPolicyExists(policyArn2).Return(nil, nil) | ||
mockClient.EXPECT().DetachRolePolicy(policyArn1, roleName).Return(nil) | ||
mockClient.EXPECT().DetachRolePolicy(policyArn2, roleName).Return(nil) | ||
runner := DetachPolicyRunner(options) | ||
err := runner(context.Background(), t.RosaRuntime, c, nil) | ||
Expect(err).NotTo(HaveOccurred()) | ||
}) | ||
|
||
}) | ||
}) |
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
Oops, something went wrong.