Skip to content

Commit 62cb1e1

Browse files
authored
add immutability exclusions (#57)
1 parent c5c8a22 commit 62cb1e1

File tree

8 files changed

+123
-24
lines changed

8 files changed

+123
-24
lines changed

README.md

Lines changed: 13 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.yaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ usage: |-
2828
typically provisioned via the stack for the "artifact" account (typically `auto`, `artifact`, or `corp`) in the primary
2929
region.
3030
31-
```yaml
3231
components:
3332
terraform:
3433
ecr:
@@ -39,7 +38,14 @@ usage: |-
3938
scan_images_on_push: true
4039
protected_tags:
4140
- prod
42-
image_tag_mutability: MUTABLE
41+
# Tag mutability supports: MUTABLE, IMMUTABLE, IMMUTABLE_WITH_EXCLUSION, MUTABLE_WITH_EXCLUSION
42+
image_tag_mutability: IMMUTABLE_WITH_EXCLUSION
43+
# When using *_WITH_EXCLUSION, specify exclusions to allow certain tags to be mutable
44+
image_tag_mutability_exclusion_filter:
45+
- filter: "latest"
46+
filter_type: "WILDCARD"
47+
- filter: "dev-"
48+
filter_type: "WILDCARD"
4349
4450
images:
4551
- infrastructure

src/main.tf

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,22 @@ locals {
2424

2525
module "ecr" {
2626
source = "cloudposse/ecr/aws"
27-
version = "0.44.0"
27+
version = "1.0.0"
2828

29-
protected_tags = var.protected_tags
30-
protected_tags_keep_count = var.protected_tags_keep_count
31-
enable_lifecycle_policy = var.enable_lifecycle_policy
32-
default_lifecycle_rules_settings = var.default_lifecycle_rules_settings
33-
image_names = var.images
34-
image_tag_mutability = var.image_tag_mutability
35-
max_image_count = var.max_image_count
36-
principals_full_access = compact(concat(module.full_access.principals, [local.ecr_user_arn]))
37-
principals_readonly_access = module.readonly_access.principals
38-
principals_lambda = var.principals_lambda
39-
scan_images_on_push = var.scan_images_on_push
40-
use_fullname = false
41-
replication_configurations = var.replication_configurations
29+
protected_tags = var.protected_tags
30+
protected_tags_keep_count = var.protected_tags_keep_count
31+
enable_lifecycle_policy = var.enable_lifecycle_policy
32+
default_lifecycle_rules_settings = var.default_lifecycle_rules_settings
33+
image_names = var.images
34+
image_tag_mutability = var.image_tag_mutability
35+
image_tag_mutability_exclusion_filter = var.image_tag_mutability_exclusion_filter
36+
max_image_count = var.max_image_count
37+
principals_full_access = compact(concat(module.full_access.principals, [local.ecr_user_arn]))
38+
principals_readonly_access = module.readonly_access.principals
39+
principals_lambda = var.principals_lambda
40+
scan_images_on_push = var.scan_images_on_push
41+
use_fullname = false
42+
replication_configurations = var.replication_configurations
4243

4344
custom_lifecycle_rules = var.custom_lifecycle_rules
4445

src/variables.tf

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,35 @@ variable "images" {
1010

1111
variable "image_tag_mutability" {
1212
type = string
13-
description = "The tag mutability setting for the repository. Must be one of: `MUTABLE` or `IMMUTABLE`"
13+
description = "The tag mutability setting for the repository. Must be one of: `MUTABLE`, `IMMUTABLE`, `IMMUTABLE_WITH_EXCLUSION`, or `MUTABLE_WITH_EXCLUSION`"
1414
default = "MUTABLE"
1515
}
1616

17+
variable "image_tag_mutability_exclusion_filter" {
18+
type = list(object({
19+
filter = string
20+
filter_type = optional(string, "WILDCARD")
21+
}))
22+
default = []
23+
description = "List of exclusion filters for image tag mutability. Each filter object must contain 'filter' and 'filter_type' attributes. Requires AWS provider >= 6.8.0"
24+
25+
validation {
26+
condition = alltrue([
27+
for filter in var.image_tag_mutability_exclusion_filter :
28+
contains(["WILDCARD"], filter.filter_type)
29+
])
30+
error_message = "filter_type must be `WILDCARD`"
31+
}
32+
33+
validation {
34+
condition = alltrue([
35+
for filter in var.image_tag_mutability_exclusion_filter :
36+
length(trimspace(filter.filter)) > 0
37+
])
38+
error_message = "filter value cannot be empty or contain only whitespace."
39+
}
40+
}
41+
1742
variable "max_image_count" {
1843
type = number
1944
description = "Max number of images to store. Old ones will be deleted to make room for new ones."

src/versions.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ terraform {
44
required_providers {
55
aws = {
66
source = "hashicorp/aws"
7-
version = ">= 4.9.0, < 6.0.0"
7+
version = ">= 6.8.0, < 7.0.0"
88
}
99
}
1010
}

test/component_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,40 @@ type LifecyclePolicyRuleSelection struct {
2020
CountNumber int `json:"countNumber"`
2121
}
2222

23+
func (s *ComponentSuite) TestImmutabilityExclusions() {
24+
const component = "ecr/immutability-exclusions"
25+
const stack = "default-test"
26+
const awsRegion = "us-east-2"
27+
28+
suffix := strings.ToLower(random.UniqueId())
29+
30+
inputs := map[string]interface{}{
31+
"images": []string{
32+
fmt.Sprintf("infrastructure-%s", suffix),
33+
fmt.Sprintf("microservice-a-%s", suffix),
34+
fmt.Sprintf("microservice-b-%s", suffix),
35+
fmt.Sprintf("microservice-c-%s", suffix),
36+
},
37+
}
38+
39+
defer s.DestroyAtmosComponent(s.T(), component, stack, &inputs)
40+
options, _ := s.DeployAtmosComponent(s.T(), component, stack, &inputs)
41+
assert.NotNil(s.T(), options)
42+
43+
arnMaps := map[string]string{}
44+
atmos.OutputStruct(s.T(), options, "ecr_repo_arn_map", &arnMaps)
45+
46+
for name := range arnMaps {
47+
repository := aws.GetECRRepo(s.T(), awsRegion, name)
48+
// Assert new image tag mutability mode is applied
49+
assert.EqualValues(s.T(), "IMMUTABLE_WITH_EXCLUSION", repository.ImageTagMutability)
50+
// Existing expectations remain
51+
assert.True(s.T(), repository.ImageScanningConfiguration.ScanOnPush)
52+
}
53+
54+
s.DriftTest(component, stack, &inputs)
55+
}
56+
2357
type LifecyclePolicyRule struct {
2458
RulePriority int `json:"rulePriority"`
2559
Description string `json:"description"`
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
components:
2+
terraform:
3+
ecr/immutability-exclusions:
4+
metadata:
5+
component: target
6+
vars:
7+
ecr_user_enabled: false
8+
enable_lifecycle_policy: true
9+
max_image_count: 500
10+
scan_images_on_push: true
11+
protected_tags:
12+
- prod
13+
image_tag_mutability: IMMUTABLE_WITH_EXCLUSION
14+
image_tag_mutability_exclusion_filter:
15+
- filter: "latest"
16+
filter_type: "WILDCARD"
17+
- filter: "dev-"
18+
filter_type: "WILDCARD"
19+
images:
20+
- infrastructure
21+
- microservice-a
22+
- microservice-b
23+
- microservice-c
24+
read_write_account_role_map: {}
25+
read_only_account_role_map: {}

test/fixtures/stacks/orgs/default/test/tests.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ import:
22
- orgs/default/test/_defaults
33
- catalog/usecase/basic
44
- catalog/usecase/disabled
5+
- catalog/usecase/immutability-exclusions

0 commit comments

Comments
 (0)