Rename StringOrSlice to StringOrSet, sort lists

This commit is contained in:
Peter Rifel 2024-02-12 21:37:27 -06:00
parent 21804bf631
commit f098401c49
No known key found for this signature in database
5 changed files with 62 additions and 60 deletions

View File

@ -60,19 +60,19 @@ func addCertManagerPermissions(b *iam.PolicyBuilder, p *iam.Policy) {
Action: stringorslice.Of("route53:ChangeResourceRecordSets",
"route53:ListResourceRecordSets",
),
Resource: stringorslice.Slice(zoneResources),
Resource: stringorslice.Set(zoneResources),
})
p.Statement = append(p.Statement, &iam.Statement{
Effect: iam.StatementEffectAllow,
Action: stringorslice.Slice([]string{"route53:GetChange"}),
Resource: stringorslice.Slice([]string{fmt.Sprintf("arn:%v:route53:::change/*", b.Partition)}),
Action: stringorslice.Set([]string{"route53:GetChange"}),
Resource: stringorslice.Set([]string{fmt.Sprintf("arn:%v:route53:::change/*", b.Partition)}),
})
wildcard := stringorslice.Slice([]string{"*"})
wildcard := stringorslice.Set([]string{"*"})
p.Statement = append(p.Statement, &iam.Statement{
Effect: iam.StatementEffectAllow,
Action: stringorslice.Slice([]string{"route53:ListHostedZonesByName"}),
Action: stringorslice.Set([]string{"route53:ListHostedZonesByName"}),
Resource: wildcard,
})
}

View File

@ -78,7 +78,7 @@ func (p *Policy) AddEC2CreateAction(actions, resources []string) {
&Statement{
Effect: StatementEffectAllow,
Action: stringorslice.String("ec2:CreateTags"),
Resource: stringorslice.Slice(actualResources),
Resource: stringorslice.Set(actualResources),
Condition: Condition{
"StringEquals": map[string]interface{}{
"aws:RequestTag/KubernetesCluster": p.clusterName,
@ -89,11 +89,11 @@ func (p *Policy) AddEC2CreateAction(actions, resources []string) {
&Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{
Action: stringorslice.Set([]string{
"ec2:CreateTags",
"ec2:DeleteTags", // aws.go, tag.go
}),
Resource: stringorslice.Slice(actualResources),
Resource: stringorslice.Set(actualResources),
Condition: Condition{
"Null": map[string]string{
"aws:RequestTag/KubernetesCluster": "true",
@ -176,8 +176,8 @@ type Condition map[string]interface{}
type Statement struct {
Effect StatementEffect
Principal Principal
Action stringorslice.StringOrSlice
Resource stringorslice.StringOrSlice
Action stringorslice.StringOrSet
Resource stringorslice.StringOrSet
Condition Condition
}
@ -600,7 +600,7 @@ func (b *PolicyBuilder) AddS3Permissions(p *Policy) (*Policy, error) {
"s3:ListBucket",
"s3:ListBucketVersions",
),
Resource: stringorslice.Slice([]string{
Resource: stringorslice.Set([]string{
fmt.Sprintf("arn:%v:s3:::%v", p.partition, s3Bucket),
}),
})
@ -612,7 +612,7 @@ func (b *PolicyBuilder) AddS3Permissions(p *Policy) (*Policy, error) {
func (b *PolicyBuilder) buildS3WriteStatements(p *Policy, iamS3Path string) {
p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{
Action: stringorslice.Set([]string{
"s3:GetObject",
"s3:DeleteObject",
"s3:DeleteObjectVersion",
@ -640,7 +640,7 @@ func (b *PolicyBuilder) buildS3GetStatements(p *Policy, iamS3Path string) error
p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{"s3:Get*"}),
Action: stringorslice.Set([]string{"s3:Get*"}),
Resource: stringorslice.Of(resources...),
})
}
@ -803,7 +803,7 @@ func addEtcdManagerPermissions(p *Policy) {
Action: stringorslice.Of(
"ec2:AttachVolume",
),
Resource: stringorslice.Slice([]string{"*"}),
Resource: stringorslice.Set([]string{"*"}),
Condition: Condition{
"StringEquals": map[string]string{
"aws:ResourceTag/k8s.io/role/master": "1",
@ -1064,19 +1064,19 @@ func AddDNSControllerPermissions(b *PolicyBuilder, p *Policy) {
Action: stringorslice.Of("route53:ChangeResourceRecordSets",
"route53:ListResourceRecordSets",
"route53:GetHostedZone"),
Resource: stringorslice.Slice([]string{fmt.Sprintf("arn:%v:route53:::hostedzone/%v", b.Partition, hostedZoneID)}),
Resource: stringorslice.Set([]string{fmt.Sprintf("arn:%v:route53:::hostedzone/%v", b.Partition, hostedZoneID)}),
})
p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{"route53:GetChange"}),
Resource: stringorslice.Slice([]string{fmt.Sprintf("arn:%v:route53:::change/*", b.Partition)}),
Action: stringorslice.Set([]string{"route53:GetChange"}),
Resource: stringorslice.Set([]string{fmt.Sprintf("arn:%v:route53:::change/*", b.Partition)}),
})
wildcard := stringorslice.Slice([]string{"*"})
wildcard := stringorslice.Set([]string{"*"})
p.Statement = append(p.Statement, &Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{"route53:ListHostedZones", "route53:ListTagsForResource"}),
Action: stringorslice.Set([]string{"route53:ListHostedZones", "route53:ListTagsForResource"}),
Resource: wildcard,
})
}
@ -1169,10 +1169,10 @@ func addAmazonVPCCNIPermissions(p *Policy) {
p.Statement = append(p.Statement,
&Statement{
Effect: StatementEffectAllow,
Action: stringorslice.Slice([]string{
Action: stringorslice.Set([]string{
"ec2:CreateTags",
}),
Resource: stringorslice.Slice([]string{
Resource: stringorslice.Set([]string{
strings.Join([]string{"arn:", p.partition, ":ec2:*:*:network-interface/*"}, ""),
}),
},

View File

@ -49,7 +49,7 @@ func TestRoundTrip(t *testing.T) {
Action: stringorslice.Of("ec2:DescribeRegions", "ec2:DescribeInstances"),
Resource: stringorslice.Of("a", "b"),
},
JSON: "{\"Action\":[\"ec2:DescribeRegions\",\"ec2:DescribeInstances\"],\"Effect\":\"Deny\",\"Resource\":[\"a\",\"b\"]}",
JSON: "{\"Action\":[\"ec2:DescribeInstances\",\"ec2:DescribeRegions\"],\"Effect\":\"Deny\",\"Resource\":[\"a\",\"b\"]}",
},
{
IAM: &Statement{

View File

@ -18,45 +18,54 @@ package stringorslice
import (
"encoding/json"
"sort"
"strings"
"k8s.io/apimachinery/pkg/util/sets"
)
// StringOrSlice is a type that holds a []string, but marshals to a []string or a string.
type StringOrSlice struct {
values []string
// StringOrSet is a type that holds a []string, but marshals to a []string or a string.
type StringOrSet struct {
values sets.Set[string]
forceEncodeAsArray bool
}
func (s *StringOrSlice) IsEmpty() bool {
func (s *StringOrSet) IsEmpty() bool {
return len(s.values) == 0
}
// Slice will build a value that marshals to a JSON array
func Slice(v []string) StringOrSlice {
return StringOrSlice{values: v, forceEncodeAsArray: true}
// Set will build a value that marshals to a JSON array
func Set(v []string) StringOrSet {
values := sets.Set[string]{}
values.Insert(v...)
return StringOrSet{values: values, forceEncodeAsArray: true}
}
// Of will build a value that marshals to a JSON array if len(v) > 1,
// otherwise to a single string
func Of(v ...string) StringOrSlice {
func Of(v ...string) StringOrSet {
if v == nil {
v = []string{}
}
return StringOrSlice{values: v}
values := sets.Set[string]{}
values.Insert(v...)
return StringOrSet{values: values}
}
// String will build a value that marshals to a single string
func String(v string) StringOrSlice {
return StringOrSlice{values: []string{v}, forceEncodeAsArray: false}
func String(v string) StringOrSet {
return StringOrSet{values: sets.New(v), forceEncodeAsArray: false}
}
// UnmarshalJSON implements the json.Unmarshaller interface.
func (s *StringOrSlice) UnmarshalJSON(value []byte) error {
func (s *StringOrSet) UnmarshalJSON(value []byte) error {
if value[0] == '[' {
s.forceEncodeAsArray = true
if err := json.Unmarshal(value, &s.values); err != nil {
var vals []string
if err := json.Unmarshal(value, &vals); err != nil {
return nil
}
s.values = sets.New(vals...)
return nil
}
s.forceEncodeAsArray = false
@ -64,46 +73,39 @@ func (s *StringOrSlice) UnmarshalJSON(value []byte) error {
if err := json.Unmarshal(value, &stringValue); err != nil {
return err
}
s.values = []string{stringValue}
s.values = sets.New(stringValue)
return nil
}
// String returns the string value, or the Itoa of the int value.
func (s StringOrSlice) String() string {
return strings.Join(s.values, ",")
func (s StringOrSet) String() string {
return strings.Join(sets.List[string](s.values), ",")
}
func (v *StringOrSlice) Value() []string {
return v.values
func (v *StringOrSet) Value() []string {
vals := sets.List[string](v.values)
sort.Strings(vals)
return vals
}
func (l StringOrSlice) Equal(r StringOrSlice) bool {
if len(l.values) != len(r.values) {
return false
}
for i := 0; i < len(l.values); i++ {
if l.values[i] != r.values[i] {
return false
}
}
return true
func (l StringOrSet) Equal(r StringOrSet) bool {
return l.values.Equal(r.values)
}
// MarshalJSON implements the json.Marshaller interface.
func (v StringOrSlice) MarshalJSON() ([]byte, error) {
func (v StringOrSet) MarshalJSON() ([]byte, error) {
encodeAsJSONArray := v.forceEncodeAsArray
if len(v.values) > 1 {
encodeAsJSONArray = true
}
values := v.values
values := v.Value()
if values == nil {
values = []string{}
}
if encodeAsJSONArray {
return json.Marshal(values)
} else if len(v.values) == 1 {
s := v.values[0]
return json.Marshal(&s)
} else if len(values) == 1 {
return json.Marshal(&values[0])
} else {
return json.Marshal(values)
}

View File

@ -25,7 +25,7 @@ import (
func TestRoundTrip(t *testing.T) {
grid := []struct {
Value StringOrSlice
Value StringOrSet
JSON string
}{
{
@ -37,7 +37,7 @@ func TestRoundTrip(t *testing.T) {
JSON: "\"a\"",
},
{
Value: Slice([]string{"a"}),
Value: Set([]string{"a"}),
JSON: "[\"a\"]",
},
{
@ -45,7 +45,7 @@ func TestRoundTrip(t *testing.T) {
JSON: "[\"a\",\"b\"]",
},
{
Value: Slice([]string{"a", "b"}),
Value: Set([]string{"a", "b"}),
JSON: "[\"a\",\"b\"]",
},
{
@ -53,7 +53,7 @@ func TestRoundTrip(t *testing.T) {
JSON: "[]",
},
{
Value: Slice(nil),
Value: Set(nil),
JSON: "[]",
},
}
@ -69,7 +69,7 @@ func TestRoundTrip(t *testing.T) {
t.Errorf("Unexpected JSON encoding. Actual=%q, Expected=%q", string(actualJSON), g.JSON)
}
parsed := &StringOrSlice{}
parsed := &StringOrSet{}
err = json.Unmarshal([]byte(g.JSON), parsed)
if err != nil {
t.Errorf("error decoding StringOrSlice %s to json: %v", g.JSON, err)