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
202 changes: 202 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,202 @@
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,
Optional: true,
},
"selection": {
Type: schema.TypeSet,
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.TypeList,
Required: false,
Optional: true,
MaxItems: 1,
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)
}

if action := cfgStmt["action"].([]interface{}); len(action) == 0 {
stmt.Action = EcrLifecyclePolicyAction{
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)
}
}
}
186 changes: 186 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,186 @@
package aws

import (
"testing"

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

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.TestCheckResourceAttr(
"data.aws_ecr_lifecycle_policy_document.test", "json",
testAccAWSEcrLifecyclePolicyDocumentExpectedJSON,
),
},
},
})
}

func TestAccAWSDataSourceEcrLifecyclePolicyDocument_multipleRules(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccAWSEcrLifecyclePolicyDocumentConfig_multipleRules,
Check: resource.TestCheckResourceAttr(
"data.aws_ecr_lifecycle_policy_document.test", "json",
testAccAWSEcrLifecyclePolicyDocumentExpectedJSON_multipleRules,
),
},
},
})
}

func TestAccAWSDataSourceEcrLifecyclePolicyDocument_noDesc(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccAWSEcrLifecyclePolicyDocumentConfig_noDesc,
Check: resource.TestCheckResourceAttr(
"data.aws_ecr_lifecycle_policy_document.test", "json",
testAccAWSEcrLifecyclePolicyDocumentExpectedJSON_noDesc,
),
},
},
})
}

var testAccAWSEcrLifecyclePolicyDocumentConfig = `
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
}
}
}
`

var testAccAWSEcrLifecyclePolicyDocumentExpectedJSON = `{
"rules": [
{
"rulePriority": 1,
"description": "This is a test.",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": [
"prod"
],
"countType": "imageCountMoreThan",
"countNumber": 100
},
"action": {
"type": "expire"
}
}
]
}`

var testAccAWSEcrLifecyclePolicyDocumentConfig_multipleRules = `
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
}
}
rule {
priority = 2
description = "This is another test."
selection = {
tag_status = "tagged"
tag_prefix_list = ["dev"]
count_type = "imageCountMoreThan"
count_number = 25
}
}
}
`

var testAccAWSEcrLifecyclePolicyDocumentExpectedJSON_multipleRules = `{
"rules": [
{
"rulePriority": 1,
"description": "This is a test.",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": [
"prod"
],
"countType": "imageCountMoreThan",
"countNumber": 100
},
"action": {
"type": "expire"
}
},
{
"rulePriority": 2,
"description": "This is another test.",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": [
"dev"
],
"countType": "imageCountMoreThan",
"countNumber": 25
},
"action": {
"type": "expire"
}
}
]
}`

var testAccAWSEcrLifecyclePolicyDocumentConfig_noDesc = `
data "aws_ecr_lifecycle_policy_document" "test" {
rule {
priority = 1
selection = {
tag_status = "tagged"
tag_prefix_list = ["prod"]
count_type = "imageCountMoreThan"
count_number = 100
}
}
}
`

var testAccAWSEcrLifecyclePolicyDocumentExpectedJSON_noDesc = `{
"rules": [
{
"rulePriority": 1,
"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 @@ -191,6 +191,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
Loading