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

New Resource: aws_ecr_repository_policy_statement #985

Closed
Closed
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
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ func Provider() terraform.ResourceProvider {
"aws_ebs_snapshot": resourceAwsEbsSnapshot(),
"aws_ebs_volume": resourceAwsEbsVolume(),
"aws_ecr_repository": resourceAwsEcrRepository(),
"aws_ecr_repository_policy_statement": resourceAwsEcrRepositoryPolicyStatement(),
"aws_ecr_repository_policy": resourceAwsEcrRepositoryPolicy(),
"aws_ecs_cluster": resourceAwsEcsCluster(),
"aws_ecs_service": resourceAwsEcsService(),
Expand Down
294 changes: 294 additions & 0 deletions aws/resource_aws_ecr_repository_policy_statement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
package aws

import (
"encoding/json"
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/ecr"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

type ecrPolicy struct {
Version string `json:",omitempty"`
ID string `json:",omitempty"`
Statements []*ecrPolicyStatement `json:"Statement"`
}

type ecrPolicyStatement struct {
Sid string
Effect string `json:",omitempty"`
Actions interface{} `json:"Action,omitempty"`
NotActions interface{} `json:"NotAction,omitempty"`
Resources interface{} `json:"Resource,omitempty"`
NotResources interface{} `json:"NotResource,omitempty"`
Principals interface{} `json:"Principal,omitempty"`
NotPrincipals interface{} `json:"NotPrincipal,omitempty"`
Conditions interface{} `json:"Condition,omitempty"`
}

func resourceAwsEcrRepositoryPolicyStatement() *schema.Resource {
return &schema.Resource{
Create: resourceAwsEcrRepositoryPolicyStatementCreate,
Read: resourceAwsEcrRepositoryPolicyStatementRead,
Update: resourceAwsEcrRepositoryPolicyStatementUpdate,
Delete: resourceAwsEcrRepositoryPolicyStatementDelete,

Schema: map[string]*schema.Schema{
"sid": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"sid_prefix"},
},

"sid_prefix": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},

"repository": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"statement": &schema.Schema{
Type: schema.TypeString,
Required: true,
},

"registry_id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}

func resourceAwsEcrRepositoryPolicyStatementCreateOrUpdate(d *schema.ResourceData, meta interface{}) error {
var sid string

conn := meta.(*AWSClient).ecrconn
registryID := d.Get("registry_id").(string)
repository := d.Get("repository").(string)
stmtText := d.Get("statement").(string)

if v, ok := d.GetOk("sid"); ok {
sid = v.(string)
} else if v, ok := d.GetOk("sid_prefix"); ok {
sid = resource.PrefixedUniqueId(v.(string))
} else {
sid = resource.UniqueId()
}

awsMutexKV.Lock(repository)
defer awsMutexKV.Unlock(repository)

_, policy, err := readOrBuildEcrPolicy(conn, registryID, repository)
if err != nil {
return err
}

index, found := findEcrPolicyStatement(policy, sid)

statement := &ecrPolicyStatement{}
if found {
statement = policy.Statements[index]
} else {
policy.Statements = append(policy.Statements, statement)
}

if err := json.Unmarshal([]byte(stmtText), statement); err != nil {
return err
}

// Force Sid to be the one defined in the resource
statement.Sid = sid

repositoryPolicy, err := resourceAwsEcrRepositoryPolicyStatementUpdateWith(d, meta, policy)
if err != nil {
return err
}

d.SetId(sid)
d.Set("sid", sid)
d.Set("repository", *repositoryPolicy.RepositoryName)
d.Set("registry_id", repositoryPolicy.RegistryId)

return nil
}

func resourceAwsEcrRepositoryPolicyStatementCreate(d *schema.ResourceData, meta interface{}) error {
return resourceAwsEcrRepositoryPolicyStatementCreateOrUpdate(d, meta)
}

func resourceAwsEcrRepositoryPolicyStatementRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ecrconn
sid := d.Get("sid").(string)
registryID := d.Get("registry_id").(string)
repository := d.Get("repository").(string)

awsMutexKV.Lock(repository)
defer awsMutexKV.Unlock(repository)

log.Printf("[DEBUG] Reading repository policy statement %s", d.Id())

repositoryPolicy, policy, err := readOrBuildEcrPolicy(conn, registryID, repository)
if err != nil {
return err
}

index, found := findEcrPolicyStatement(policy, sid)
if !found {
d.SetId("")
return nil
}

statement := policy.Statements[index]

d.SetId(statement.Sid)
d.Set("sid", statement.Sid)
d.Set("registry_id", repositoryPolicy.RegistryId)
d.Set("repository", *repositoryPolicy.RepositoryName)

return nil
}

func resourceAwsEcrRepositoryPolicyStatementUpdate(d *schema.ResourceData, meta interface{}) error {
return resourceAwsEcrRepositoryPolicyStatementCreateOrUpdate(d, meta)
}

func resourceAwsEcrRepositoryPolicyStatementDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ecrconn
sid := d.Get("sid").(string)
registryID := d.Get("registry_id").(string)
repository := d.Get("repository").(string)

awsMutexKV.Lock(repository)
defer awsMutexKV.Unlock(repository)

log.Printf("[DEBUG] Reading repository policy statement %s", d.Id())

_, policy, err := readOrBuildEcrPolicy(conn, registryID, repository)
if err != nil {
return err
}

index, found := findEcrPolicyStatement(policy, sid)
if !found {
d.SetId("")
return nil
}

policy.Statements = append(policy.Statements[:index], policy.Statements[index+1:]...)

_, err = resourceAwsEcrRepositoryPolicyStatementUpdateWith(d, meta, policy)
if err != nil {
return err
}

log.Printf("[DEBUG] repository policy statement %s deleted.", d.Id())

return nil
}

func resourceAwsEcrRepositoryPolicyStatementUpdateWith(d *schema.ResourceData, meta interface{}, policy *ecrPolicy) (*ecr.SetRepositoryPolicyOutput, error) {
conn := meta.(*AWSClient).ecrconn
registryID := d.Get("registry_id").(string)
repository := d.Get("repository").(string)

if len(policy.Statements) == 0 {
input := ecr.DeleteRepositoryPolicyInput{
RepositoryName: aws.String(repository),
}

if registryID != "" {
input.RegistryId = aws.String(registryID)
}

_, err := conn.DeleteRepositoryPolicy(&input)

if err != nil {
return nil, err
}

return nil, nil
}

policyText, err := json.Marshal(policy)
if err != nil {
return nil, err
}

input := ecr.SetRepositoryPolicyInput{
RepositoryName: aws.String(repository),
PolicyText: aws.String(string(policyText)),
}

if registryID != "" {
input.RegistryId = aws.String(registryID)
}

out, err := conn.SetRepositoryPolicy(&input)

if err != nil {
return nil, err
}

return out, nil
}

func readOrBuildEcrPolicy(conn *ecr.ECR, registryID string, repositoryName string) (*ecr.GetRepositoryPolicyOutput, *ecrPolicy, error) {
input := &ecr.GetRepositoryPolicyInput{
RepositoryName: aws.String(repositoryName),
}

if registryID != "" {
input.RegistryId = aws.String(registryID)
}

out, err := conn.GetRepositoryPolicy(input)

if err != nil {
if ecrerr, ok := err.(awserr.Error); ok {
switch ecrerr.Code() {
case "RepositoryNotFoundException", "RepositoryPolicyNotFoundException":
// Do nothing
default:
return nil, nil, err
}
} else {
return nil, nil, err
}
}

policy := &ecrPolicy{}
if out != nil && out.PolicyText != nil {
if err := json.Unmarshal([]byte(*out.PolicyText), policy); err != nil {
return nil, nil, err
}
} else {
policy.Version = "2008-10-17"
}

if policy.Statements == nil {
policy.Statements = make([]*ecrPolicyStatement, 0)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@greenboxal Do you think you could take some time to create a data source called aws_iam_policy_document which would have a source_policy document, that would allow to merge your policy into the source one?

Your use case seems totally valid and it would be a great addition to have it! 👍
I am just wondering that maybe we could make this part reusable so that we can handle this case in several situations. What do you think? :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ninir this sounds almost exactly like what I did with #2890


return out, policy, nil
}

func findEcrPolicyStatement(policy *ecrPolicy, sid string) (int, bool) {
for i, statement := range policy.Statements {
if statement.Sid == sid {
return i, true
}
}

return 0, false
}
Loading