I'm working through the IAM policy examples for the AWS Go SDK and trying to do the opposite of the Create Policy
example -- basically, get all of the IAM policy in the account, get the default policy versions, then unmarshal that json document into a struct so it is easily parsed.
I got this far but I'm stuck with how go handles a conditional struct type. In the AWS policy doc version response, the json data for StatementEntry
can be string
or []string
depending on the doc.
What would be a best practice? Add another struct and use retry logic in the error handling?
package main
import (
"encoding/json"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/iam"
"log"
"net/url"
)
type PolicyDocument struct {
Version string
Statement []StatementEntry
}
type StatementEntry struct {
Effect string
Action []string
Resource []string
}
func main() {
sess, _ := session.NewSession(&aws.Config{
Region: aws.String("us-west-2")},
)
svc := iam.New(sess)
fmt.Printf("%s - %s\n", arn, *result.Policy.Description)
results, _ := svc.ListPolicies(&iam.ListPoliciesInput{})
for _, policy := range results.Policies {
arn := policy.Arn
version := policy.DefaultVersionId
pv, _ := svc.GetPolicyVersion(&iam.GetPolicyVersionInput{
PolicyArn: arn,
VersionId: version,
})
decodedValue, err := url.QueryUnescape(aws.StringValue(pv.PolicyVersion.Document))
if err != nil {
log.Fatal(err)
return
}
//fmt.Println(decodedValue)
data := []byte(decodedValue)
var doc PolicyDocument
err1 := json.Unmarshal(data, &doc)
if err1 != nil {
log.Fatal(err1)
}
fmt.Printf("\n----\n%v\n---\n", doc)
}
}
Example PolicyDocuments
are this:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ssm:PutParameter",
"ssm:DeleteParameter",
"ssm:DescribeInstancePatchStates",
"elasticloadbalancing:RegisterTargets",
"elasticloadbalancing:DescribeTargetHealth",
"elasticloadbalancing:DescribeTargetGroups",
"elasticloadbalancing:DeregisterTargets",
"ssm:GetParameter"
],
"Resource": "*"
}
]
}
And this (for Resource []string
in the StatementEntry
):
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:ListBucket"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::SageMaker"
]
},
{
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::SageMaker/*"
]
}
]
}
You can achieve this by using a custom type for the slice with an Unmarshal method that first unmarshals to an empty interface and then determines if it is a slice or a single string:
package main
import (
"fmt"
"encoding/json"
"errors"
)
type container struct {
Field customSlice
}
type customSlice []string
func (c *customSlice) UnmarshalJSON(data []byte) error {
var tmp interface{}
err := json.Unmarshal(data, &tmp)
if err != nil {
return err
}
slice, ok := tmp.([]interface{})
if ok {
for _, item := range slice {
*c = append(*c, item.(string))
}
return nil
}
theString, ok := tmp.(string)
if ok {
*c = append(*c, theString)
return nil
}
return errors.New("Field neither slice or string")
}
func main() {
jsonInputSlice := `{"Field":["a"]}`
jsonInputString := `{"Field":"a"}`
var containerSlice container
var containerString container
err := json.Unmarshal([]byte(jsonInputSlice), &containerSlice)
if err != nil {
panic(err)
}
fmt.Println(containerSlice)
err = json.Unmarshal([]byte(jsonInputString), &containerString)
if err != nil {
panic(err)
}
fmt.Println(containerString)
}
https://play.golang.org/p/mAhJBNhE1yc
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With