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

data_source/aws_ecr_lifecycle_policy_document: adding new data source for ECR #6133

Closed
201 changes: 201 additions & 0 deletions aws/data_source_aws_ecr_lifecycle_policy_document.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package aws

import (
"encoding/json"
"strconv"

"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
)

type EcrLifecyclePolicyDoc struct {
Rules []*EcrLifecyclePolicyStatement `json:"rules"`
}

type EcrLifecyclePolicyStatement struct {
RulePriority int `json:"rulePriority,omitempty"`
Description string `json:"description,omitempty"`
Selection EcrLifecyclePolicyStatementSelectionSet `json:"selection,omitempty"`
Action EcrLifecyclePolicyAction `json:"action"`
}

type EcrLifecyclePolicySelection struct {
TagStatus string `json:"tagStatus,omitempty"`
TagPrefixList []interface{} `json:"tagPrefixList,omitempty"`
CountType string `json:"countType,omitempty"`
CountUnit string `json:"countUnit,omitempty"`
CountNumber int `json:"countNumber,omitempty"`
}

type EcrLifecyclePolicyAction struct {
Type string `json:"type"`
}

type EcrLifecyclePolicyStatementSelectionSet EcrLifecyclePolicySelection

func dataSourceAwsEcrLifecyclePolicyDocument() *schema.Resource {

return &schema.Resource{
Read: dataSourceAwsEcrLifecyclePolicyDocumentRead,

Schema: map[string]*schema.Schema{
"rule": {
Type: schema.TypeList,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"priority": {
Type: schema.TypeInt,
Required: true,
},
"description": {
Type: schema.TypeString,
Required: false,
slapula marked this conversation as resolved.
Show resolved Hide resolved
Optional: true,
},
"selection": {
Type: schema.TypeSet,
Required: false,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"tag_status": {
Type: schema.TypeString,
Required: false,
Optional: true,
Default: "any",
ValidateFunc: validation.StringInSlice([]string{"tagged", "untagged", "any"}, false),
},
"tag_prefix_list": {
Type: schema.TypeList,
Required: false,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"count_type": {
Type: schema.TypeString,
Required: false,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"imageCountMoreThan", "sinceImagePushed", "any"}, false),
},
"count_unit": {
Type: schema.TypeString,
Required: false,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"days"}, false),
Copy link
Contributor

Choose a reason for hiding this comment

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

Example B of Filtering All Images uses hour as a count unit: https://docs.aws.amazon.com/AmazonECR/latest/userguide/lifecycle_policy_examples.html#lp_example_allimages

        {
            "rulePriority": 2,
            "description": "Rule 2",
            "selection": {
                "tagStatus": "untagged",
                "countType": "sinceImagePushed",
                "countUnit": "hours",
                "countNumber": 1
            },
            "action": {
                "type": "expire"
            }
        },

},
"count_number": {
Type: schema.TypeInt,
Required: false,
Optional: true,
},
},
},
},
"action": {
Type: schema.TypeSet,
slapula marked this conversation as resolved.
Show resolved Hide resolved
Required: false,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Type: schema.TypeString,
Optional: true,
Default: "expire",
},
},
},
},
},
},
},
"json": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

func dataSourceAwsEcrLifecyclePolicyDocumentRead(d *schema.ResourceData, meta interface{}) error {
mergedDoc := &EcrLifecyclePolicyDoc{}

// process the current document
doc := &EcrLifecyclePolicyDoc{}

var cfgStmts = d.Get("rule").([]interface{})
stmts := make([]*EcrLifecyclePolicyStatement, len(cfgStmts))
for i, stmtI := range cfgStmts {
cfgStmt := stmtI.(map[string]interface{})

stmt := &EcrLifecyclePolicyStatement{
RulePriority: cfgStmt["priority"].(int),
}

if description, ok := cfgStmt["description"]; ok {
stmt.Description = description.(string)
}

if selection := cfgStmt["selection"].(*schema.Set).List(); len(selection) > 0 {
stmt.Selection = dataSourceAwsEcrLifecyclePolicyDocumentMakeSelection(selection)
}

stmt.Action = EcrLifecyclePolicyAction{
bflad marked this conversation as resolved.
Show resolved Hide resolved
Type: "expire",
}

stmts[i] = stmt
}

doc.Rules = stmts

// merge our current document into mergedDoc
mergedDoc.Merge(doc)

jsonDoc, err := json.MarshalIndent(mergedDoc, "", " ")
if err != nil {
// should never happen if the above code is correct
return err
}
jsonString := string(jsonDoc)

d.Set("json", jsonString)
d.SetId(strconv.Itoa(hashcode.String(jsonString)))

return nil
}

func dataSourceAwsEcrLifecyclePolicyDocumentMakeSelection(in []interface{}) EcrLifecyclePolicyStatementSelectionSet {
item := in[0].(map[string]interface{})
out := EcrLifecyclePolicySelection{
TagStatus: item["tag_status"].(string),
TagPrefixList: item["tag_prefix_list"].([]interface{}),
CountType: item["count_type"].(string),
CountUnit: item["count_unit"].(string),
CountNumber: item["count_number"].(int),
}
return EcrLifecyclePolicyStatementSelectionSet(out)
}

func (self *EcrLifecyclePolicyDoc) Merge(newDoc *EcrLifecyclePolicyDoc) {
// merge in newDoc's statements, overwriting any existing Sids
var seen bool
for _, newRule := range newDoc.Rules {
if newRule.RulePriority == 0 {
self.Rules = append(self.Rules, newRule)
continue
}
seen = false
for i, existingRule := range self.Rules {
if existingRule.RulePriority == newRule.RulePriority {
self.Rules[i] = newRule
seen = true
break
}
}
if !seen {
self.Rules = append(self.Rules, newRule)
}
}
}
84 changes: 84 additions & 0 deletions aws/data_source_aws_ecr_lifecycle_policy_document_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package aws

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSDataSourceEcrLifecyclePolicyDocument_basic(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be nice to see additional testing that covers:

  • Setting other attributes or omitting optional attributes
  • Multiple rules

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just added a couple more tests: one that omits an optional attribute (descriptions) and one that generates a two rule policy.

// This really ought to be able to be a unit test rather than an
// acceptance test, but just instantiating the AWS provider requires
// some AWS API calls, and so this needs valid AWS credentials to work.
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccAWSEcrLifecyclePolicyDocumentConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckEcrLifecyclePolicyValue("data.aws_ecr_lifecycle_policy_document.test", "json",
slapula marked this conversation as resolved.
Show resolved Hide resolved
testAccAWSEcrLifecyclePolicyDocumentExpectedJSON,
),
),
},
},
})
}

func testAccCheckEcrLifecyclePolicyValue(id, name, value string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[id]
if !ok {
return fmt.Errorf("Not found: %s", id)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}

v := rs.Primary.Attributes[name]
if v != value {
return fmt.Errorf(
"Value for %s is %s, not %s", name, v, value)
}

return nil
}
}

var testAccAWSEcrLifecyclePolicyDocumentConfig = `
data "aws_ecr_lifecycle_policy_document" "test" {
rule {
priority = 1
description = "This is a test."
slapula marked this conversation as resolved.
Show resolved Hide resolved
selection = {
tag_status = "tagged"
tag_prefix_list = ["prod"]
count_type = "imageCountMoreThan"
count_number = 100
}
}
}
`

var testAccAWSEcrLifecyclePolicyDocumentExpectedJSON = `{
"rules": [
{
"rulePriority": 1,
"description": "This is a test.",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": [
"prod"
],
"countType": "imageCountMoreThan",
"countNumber": 100
},
"action": {
"type": "expire"
}
}
]
}`
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ func Provider() terraform.ResourceProvider {
"aws_ebs_snapshot": dataSourceAwsEbsSnapshot(),
"aws_ebs_snapshot_ids": dataSourceAwsEbsSnapshotIds(),
"aws_ebs_volume": dataSourceAwsEbsVolume(),
"aws_ecr_lifecycle_policy_document": dataSourceAwsEcrLifecyclePolicyDocument(),
"aws_ecr_repository": dataSourceAwsEcrRepository(),
"aws_ecs_cluster": dataSourceAwsEcsCluster(),
"aws_ecs_container_definition": dataSourceAwsEcsContainerDefinition(),
Expand Down
56 changes: 56 additions & 0 deletions website/docs/d/ecr_lifecycle_policy_document.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
layout: "aws"
page_title: "AWS: aws_ecr_lifecycle_policy_document"
sidebar_current: "docs-aws-datasource-ecr-lifecycle-policy-document"
description: |-
PGenerates an ECR lifecycle policy document in JSON format.
slapula marked this conversation as resolved.
Show resolved Hide resolved
---

# Data Source: aws_ecr_lifecycle_policy_document

This is a data source which can be used to construct a JSON representation of an ECR lifecycle policy document, for use with resources which expect policy documents, such as the aws_ecr_lifecycle_policy resource.
slapula marked this conversation as resolved.
Show resolved Hide resolved

-> For more information about building AWS ECR lifecycle policy documents with Terraform, see the [AWS ECR Lifecycle Policy Document Guide](https://docs.aws.amazon.com/AmazonECR/latest/userguide/LifecyclePolicies.html).
slapula marked this conversation as resolved.
Show resolved Hide resolved

## Example Usage

```hcl
data "aws_ecr_lifecycle_policy_document" "test" {
rule {
priority = 1
description = "This is a test."
selection = {
tag_status = "tagged"
tag_prefix_list = ["prod"]
count_type = "imageCountMoreThan"
count_number = 100
}
}
}

resource "aws_ecr_lifecycle_policy" "foopolicy" {
repository = "${aws_ecr_repository.foo.name}"

policy = "${data.aws_ecr_lifecycle_policy_document.test.json}"
}
```

## Argument Reference

Each document configuration may have one or more `rule` blocks, which
each accept the following arguments:

* `priority` (Required) - Sets the order in which rules are evaluated, lowest to highest. When you add rules to a lifecycle policy, you must give them each a unique value for `priority`. Values do not need to be sequential across rules in a policy. A rule with a `tag_status` value of any must have the highest value for `priority` and be evaluated last.
* `description` (Optional) - Describes the purpose of a rule within a lifecycle policy.
* `selection` (Required) - Collects parameters describing the selection criteria for the ECR lifecycle policy:
* `tag_status` (Required) - Determines whether the lifecycle policy rule that you are adding specifies a tag for an image. Acceptable options are tagged, untagged, or any. If you specify any, then all images have the rule applied to them. If you specify tagged, then you must also specify a `tag_prefix_list` value. If you specify untagged, then you must omit `tag_prefix_list`.
* `tag_prefix_list` (Required if `tag_status` is set to tagged) - You must specify a comma-separated list of image tag prefixes on which to take action with your lifecycle policy. For example, if your images are tagged as prod, prod1, prod2, and so on, you would use the tag prefix prod to specify all of them. If you specify multiple tags, only images with all specified tags are selected.
* `count_type` (Required) - Specify a count type to apply to the images. If `count_type` is set to imageCountMoreThan, you also specify `count_number` to create a rule that sets a limit on the number of images that exist in your repository. If `count_type` is set to sinceImagePushed, you also specify `count_unit` and `count_number` to specify a time limit on the images that exist in your repository.
* `count_unit` (Required if `count_type` is set to sinceImagePushed) - Specify a count unit of days to indicate that as the unit of time, in addition to `count_number`, which is the number of days.
* `count_number` (Required) - Specify a count number. If the `count_type` used is imageCountMoreThan, then the value is the maximum number of images that you want to retain in your repository. If the `count_type` used is sinceImagePushed, then the value is the maximum age limit for your images.

## Attributes Reference

The following attribute is exported:

* `json` - The above arguments serialized as a standard JSON policy document.