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