feat: ListPolicyVersions

This commit is contained in:
Julian Sommer
2026-06-11 12:25:12 +10:00
parent db8d5a7265
commit 05e66bd6a0
11 changed files with 99 additions and 0 deletions
+1
View File
@@ -489,6 +489,7 @@ bypasses policy evaluation entirely.
| `create-policy` | `--policy-name`, `--policy-document`, `--path`, `--description` | `--tags` | **DONE** |
| `get-policy` | `--policy-arn` | — | **DONE** |
| `get-policy-version` | `--policy-arn`, `--version-id` | — | **DONE** |
| `list-policy-versions` | `--policy-arn` | `--max-items`, `--marker` | **DONE** |
| `list-policies` | — | `--scope`, `--only-attached`, `--path-prefix`, `--max-items`, `--marker` | **DONE** |
| `delete-policy` | `--policy-arn` | — | **DONE** |
| `attach-user-policy` | `--user-name`, `--policy-arn` | — | **DONE** |
+3
View File
@@ -82,6 +82,9 @@ func (m *mockIAMService) GetPolicy(_ string, _ *iam.GetPolicyInput) (*iam.GetPol
func (m *mockIAMService) GetPolicyVersion(_ string, _ *iam.GetPolicyVersionInput) (*iam.GetPolicyVersionOutput, error) {
return nil, nil
}
func (m *mockIAMService) ListPolicyVersions(_ string, _ *iam.ListPolicyVersionsInput) (*iam.ListPolicyVersionsOutput, error) {
return nil, nil
}
func (m *mockIAMService) ListPolicies(_ string, _ *iam.ListPoliciesInput) (*iam.ListPoliciesOutput, error) {
return nil, nil
}
@@ -79,6 +79,9 @@ func (f *fakeIAMService) GetPolicy(string, *iam.GetPolicyInput) (*iam.GetPolicyO
func (f *fakeIAMService) GetPolicyVersion(string, *iam.GetPolicyVersionInput) (*iam.GetPolicyVersionOutput, error) {
return &iam.GetPolicyVersionOutput{}, nil
}
func (f *fakeIAMService) ListPolicyVersions(string, *iam.ListPolicyVersionsInput) (*iam.ListPolicyVersionsOutput, error) {
return &iam.ListPolicyVersionsOutput{}, nil
}
func (f *fakeIAMService) ListPolicies(string, *iam.ListPoliciesInput) (*iam.ListPoliciesOutput, error) {
return &iam.ListPoliciesOutput{}, nil
}
+3
View File
@@ -78,6 +78,9 @@ var iamActions = map[string]IAMHandler{
"GetPolicyVersion": iamHandler(func(accountID string, input *iam.GetPolicyVersionInput, gw *GatewayConfig) (any, error) {
return gateway_iam.GetPolicyVersion(accountID, input, gw.IAMService)
}),
"ListPolicyVersions": iamHandler(func(accountID string, input *iam.ListPolicyVersionsInput, gw *GatewayConfig) (any, error) {
return gateway_iam.ListPolicyVersions(accountID, input, gw.IAMService)
}),
"ListPolicies": iamHandler(func(accountID string, input *iam.ListPoliciesInput, gw *GatewayConfig) (any, error) {
return gateway_iam.ListPolicies(accountID, input, gw.IAMService)
}),
+28
View File
@@ -62,6 +62,10 @@ func (s *stubIAMService) GetPolicyVersion(_ string, _ *iam.GetPolicyVersionInput
return &iam.GetPolicyVersionOutput{}, nil
}
func (s *stubIAMService) ListPolicyVersions(_ string, _ *iam.ListPolicyVersionsInput) (*iam.ListPolicyVersionsOutput, error) {
return &iam.ListPolicyVersionsOutput{}, nil
}
func (s *stubIAMService) ListPolicies(_ string, _ *iam.ListPoliciesInput) (*iam.ListPoliciesOutput, error) {
return &iam.ListPoliciesOutput{}, nil
}
@@ -413,6 +417,30 @@ func TestGetPolicyVersion(t *testing.T) {
}
}
func TestListPolicyVersions(t *testing.T) {
svc := &stubIAMService{}
tests := []struct {
name string
input *iam.ListPolicyVersionsInput
wantErr string
}{
{"nil PolicyArn", &iam.ListPolicyVersionsInput{}, awserrors.ErrorMissingParameter},
{"empty PolicyArn", &iam.ListPolicyVersionsInput{PolicyArn: aws.String("")}, awserrors.ErrorMissingParameter},
{"valid", &iam.ListPolicyVersionsInput{PolicyArn: aws.String("arn:aws:iam::000000000000:policy/mypolicy")}, ""},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
_, err := ListPolicyVersions(testAccountID, tc.input, svc)
if tc.wantErr != "" {
require.Error(t, err)
assert.Equal(t, tc.wantErr, err.Error())
} else {
require.NoError(t, err)
}
})
}
}
func TestListPolicies(t *testing.T) {
svc := &stubIAMService{}
_, err := ListPolicies(testAccountID, &iam.ListPoliciesInput{}, svc)
+7
View File
@@ -35,6 +35,13 @@ func GetPolicyVersion(accountID string, input *iam.GetPolicyVersionInput, svc ha
return svc.GetPolicyVersion(accountID, input)
}
func ListPolicyVersions(accountID string, input *iam.ListPolicyVersionsInput, svc handlers_iam.IAMService) (*iam.ListPolicyVersionsOutput, error) {
if input.PolicyArn == nil || *input.PolicyArn == "" {
return nil, errors.New(awserrors.ErrorMissingParameter)
}
return svc.ListPolicyVersions(accountID, input)
}
func ListPolicies(accountID string, input *iam.ListPoliciesInput, svc handlers_iam.IAMService) (*iam.ListPoliciesOutput, error) {
return svc.ListPolicies(accountID, input)
}
+4
View File
@@ -96,6 +96,10 @@ func (m *flexMockIAMService) GetPolicyVersion(_ string, _ *iam.GetPolicyVersionI
return &iam.GetPolicyVersionOutput{}, nil
}
func (m *flexMockIAMService) ListPolicyVersions(_ string, _ *iam.ListPolicyVersionsInput) (*iam.ListPolicyVersionsOutput, error) {
return &iam.ListPolicyVersionsOutput{}, nil
}
func (m *flexMockIAMService) ListPolicies(_ string, _ *iam.ListPoliciesInput) (*iam.ListPoliciesOutput, error) {
return &iam.ListPoliciesOutput{}, nil
}
+3
View File
@@ -124,6 +124,9 @@ func (s *stubIAMService) GetPolicy(string, *iam.GetPolicyInput) (*iam.GetPolicyO
func (s *stubIAMService) GetPolicyVersion(string, *iam.GetPolicyVersionInput) (*iam.GetPolicyVersionOutput, error) {
panic("unexpected GetPolicyVersion call")
}
func (s *stubIAMService) ListPolicyVersions(string, *iam.ListPolicyVersionsInput) (*iam.ListPolicyVersionsOutput, error) {
panic("unexpected ListPolicyVersions call")
}
func (s *stubIAMService) ListPolicies(string, *iam.ListPoliciesInput) (*iam.ListPoliciesOutput, error) {
panic("unexpected ListPolicies call")
}
+1
View File
@@ -22,6 +22,7 @@ type IAMService interface {
CreatePolicy(accountID string, input *iam.CreatePolicyInput) (*iam.CreatePolicyOutput, error)
GetPolicy(accountID string, input *iam.GetPolicyInput) (*iam.GetPolicyOutput, error)
GetPolicyVersion(accountID string, input *iam.GetPolicyVersionInput) (*iam.GetPolicyVersionOutput, error)
ListPolicyVersions(accountID string, input *iam.ListPolicyVersionsInput) (*iam.ListPolicyVersionsOutput, error)
ListPolicies(accountID string, input *iam.ListPoliciesInput) (*iam.ListPoliciesOutput, error)
DeletePolicy(accountID string, input *iam.DeletePolicyInput) (*iam.DeletePolicyOutput, error)
+20
View File
@@ -1089,6 +1089,26 @@ func (s *IAMServiceImpl) GetPolicyVersion(accountID string, input *iam.GetPolicy
}, nil
}
func (s *IAMServiceImpl) ListPolicyVersions(accountID string, input *iam.ListPolicyVersionsInput) (*iam.ListPolicyVersionsOutput, error) {
policy, err := s.getPolicyByARN(accountID, *input.PolicyArn)
if err != nil {
return nil, err
}
// CreatePolicyVersion is unrouted, so every policy has exactly one immutable
// default version (v1). Real IAM omits the document from list entries —
// callers fetch it via GetPolicyVersion — so Document is left nil here.
createdAt := parseCreatedAt(policy.CreatedAt)
return &iam.ListPolicyVersionsOutput{
Versions: []*iam.PolicyVersion{{
VersionId: aws.String(policy.DefaultVersion),
IsDefaultVersion: aws.Bool(true),
CreateDate: aws.Time(createdAt),
}},
IsTruncated: aws.Bool(false),
}, nil
}
func (s *IAMServiceImpl) ListPolicies(accountID string, input *iam.ListPoliciesInput) (*iam.ListPoliciesOutput, error) {
keys, err := s.policiesBucket.Keys()
if err != nil {
@@ -1149,6 +1149,32 @@ func TestGetPolicyVersion_PolicyNotFound(t *testing.T) {
assert.Contains(t, err.Error(), awserrors.ErrorIAMNoSuchEntity)
}
func TestListPolicyVersions(t *testing.T) {
svc := setupTestIAMService(t)
created := createTestPolicy(t, svc, "ListVersionPolicy")
out, err := svc.ListPolicyVersions(testAccountID, &iam.ListPolicyVersionsInput{
PolicyArn: created.Arn,
})
require.NoError(t, err)
assert.False(t, *out.IsTruncated)
require.Len(t, out.Versions, 1)
assert.Equal(t, "v1", *out.Versions[0].VersionId)
assert.True(t, *out.Versions[0].IsDefaultVersion)
// AWS omits the document from list entries — callers fetch it via GetPolicyVersion.
assert.Nil(t, out.Versions[0].Document)
}
func TestListPolicyVersions_PolicyNotFound(t *testing.T) {
svc := setupTestIAMService(t)
_, err := svc.ListPolicyVersions(testAccountID, &iam.ListPolicyVersionsInput{
PolicyArn: aws.String("arn:aws:iam::000000000000:policy/Ghost"),
})
assert.Error(t, err)
assert.Contains(t, err.Error(), awserrors.ErrorIAMNoSuchEntity)
}
func TestListPolicies(t *testing.T) {
svc := setupTestIAMService(t)