Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

aws/arn: Package for parsing and producing ARNs #1463

Merged
merged 1 commit into from
Aug 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions aws/arn/arn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Package arn provides a parser for interacting with Amazon Resource Names.
package arn

import (
"errors"
"strings"
)

const (
arnDelimiter = ":"
arnSections = 6
arnPrefix = "arn:"

// zero-indexed
sectionPartition = 1
sectionService = 2
sectionRegion = 3
sectionAccountID = 4
sectionResource = 5

// errors
invalidPrefix = "arn: invalid prefix"
invalidSections = "arn: not enough sections"
)

// ARN captures the individual fields of an Amazon Resource Name.
// See http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html for more information.
type ARN struct {
// The partition that the resource is in. For standard AWS regions, the partition is "aws". If you have resources in
// other partitions, the partition is "aws-partitionname". For example, the partition for resources in the China
// (Beijing) region is "aws-cn".
Partition string

// The service namespace that identifies the AWS product (for example, Amazon S3, IAM, or Amazon RDS). For a list of
// namespaces, see
// http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-aws-service-namespaces.
Service string

// The region the resource resides in. Note that the ARNs for some resources do not require a region, so this
// component might be omitted.
Region string

// The ID of the AWS account that owns the resource, without the hyphens. For example, 123456789012. Note that the
// ARNs for some resources don't require an account number, so this component might be omitted.
AccountID string

// The content of this part of the ARN varies by service. It often includes an indicator of the type of resource —
// for example, an IAM user or Amazon RDS database - followed by a slash (/) or a colon (:), followed by the
// resource name itself. Some services allows paths for resource names, as described in
// http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#arns-paths.
Resource string
}

// Parse parses an ARN into its constituent parts.
//
// Some example ARNs:
// arn:aws:elasticbeanstalk:us-east-1:123456789012:environment/My App/MyEnvironment
// arn:aws:iam::123456789012:user/David
// arn:aws:rds:eu-west-1:123456789012:db:mysql-db
// arn:aws:s3:::my_corporate_bucket/exampleobject.png
func Parse(arn string) (ARN, error) {
if !strings.HasPrefix(arn, arnPrefix) {
return ARN{}, errors.New(invalidPrefix)
}
sections := strings.SplitN(arn, arnDelimiter, arnSections)
if len(sections) != arnSections {
return ARN{}, errors.New(invalidSections)
}
return ARN{
Partition: sections[sectionPartition],
Service: sections[sectionService],
Region: sections[sectionRegion],
AccountID: sections[sectionAccountID],
Resource: sections[sectionResource],
}, nil
}

// String returns the canonical representation of the ARN
func (arn ARN) String() string {
return arnPrefix +
arn.Partition + arnDelimiter +
arn.Service + arnDelimiter +
arn.Region + arnDelimiter +
arn.AccountID + arnDelimiter +
arn.Resource
}
90 changes: 90 additions & 0 deletions aws/arn/arn_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// +build go1.7

package arn

import (
"errors"
"testing"
)

func TestParseARN(t *testing.T) {
cases := []struct {
input string
arn ARN
err error
}{
{
input: "invalid",
err: errors.New(invalidPrefix),
},
{
input: "arn:nope",
err: errors.New(invalidSections),
},
{
input: "arn:aws:ecr:us-west-2:123456789012:repository/foo/bar",
arn: ARN{
Partition: "aws",
Service: "ecr",
Region: "us-west-2",
AccountID: "123456789012",
Resource: "repository/foo/bar",
},
},
{
input: "arn:aws:elasticbeanstalk:us-east-1:123456789012:environment/My App/MyEnvironment",
arn: ARN{
Partition: "aws",
Service: "elasticbeanstalk",
Region: "us-east-1",
AccountID: "123456789012",
Resource: "environment/My App/MyEnvironment",
},
},
{
input: "arn:aws:iam::123456789012:user/David",
arn: ARN{
Partition: "aws",
Service: "iam",
Region: "",
AccountID: "123456789012",
Resource: "user/David",
},
},
{
input: "arn:aws:rds:eu-west-1:123456789012:db:mysql-db",
arn: ARN{
Partition: "aws",
Service: "rds",
Region: "eu-west-1",
AccountID: "123456789012",
Resource: "db:mysql-db",
},
},
{
input: "arn:aws:s3:::my_corporate_bucket/exampleobject.png",
arn: ARN{
Partition: "aws",
Service: "s3",
Region: "",
AccountID: "",
Resource: "my_corporate_bucket/exampleobject.png",
},
},
}
for _, tc := range cases {
t.Run(tc.input, func(t *testing.T) {
spec, err := Parse(tc.input)
if tc.arn != spec {
t.Errorf("Expected %q to parse as %v, but got %v", tc.input, tc.arn, spec)
}
if err == nil && tc.err != nil {
t.Errorf("Expected err to be %v, but got nil", tc.err)
} else if err != nil && tc.err == nil {
t.Errorf("Expected err to be nil, but got %v", err)
} else if err != nil && tc.err != nil && err.Error() != tc.err.Error() {
t.Errorf("Expected err to be %v, but got %v", tc.err, err)
}
})
}
}