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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'aws_ecr_image' datasource #8403

Merged
merged 1 commit into from
May 13, 2019
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
124 changes: 124 additions & 0 deletions aws/data_source_aws_ecr_image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package aws

import (
"fmt"
"log"
"time"

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

func dataSourceAwsEcrImage() *schema.Resource {
return &schema.Resource{
Read: dataSourceAwsEcrImageRead,
Schema: map[string]*schema.Schema{
"registry_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.NoZeroValues,
Copy link
Contributor

Choose a reason for hiding this comment

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

In a future update of this data source: we should likely remove this validation since in Terraform 0.11 it may be helpful to define this data source as part of a Terraform module that uses an optional variable to override image lookup and the below d.GetOk() usage will skip "" automatically.

variable "registry_id" {
  default = ""
}

data "aws_ecr_image" "example" {
  registry_id     = "${var.registry_id}"
  repostiory_name = "..."
}

},
"repository_name": {
Type: schema.TypeString,
Required: true,
},
"image_digest": {
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"image_tag": {
Type: schema.TypeString,
Optional: true,
},
"image_pushed_at": {
Type: schema.TypeInt,
Computed: true,
},
"image_size_in_bytes": {
Type: schema.TypeInt,
Computed: true,
},
"image_tags": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}
}

func dataSourceAwsEcrImageRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).ecrconn

params := &ecr.DescribeImagesInput{
RepositoryName: aws.String(d.Get("repository_name").(string)),
}

regId, ok := d.GetOk("registry_id")
if ok {
params.RegistryId = aws.String(regId.(string))
}

imgId := ecr.ImageIdentifier{}
digest, ok := d.GetOk("image_digest")
if ok {
imgId.ImageDigest = aws.String(digest.(string))
}
tag, ok := d.GetOk("image_tag")
if ok {
imgId.ImageTag = aws.String(tag.(string))
}

if imgId.ImageDigest == nil && imgId.ImageTag == nil {
return fmt.Errorf("At least one of either image_digest or image_tag must be defined")
Copy link
Contributor

Choose a reason for hiding this comment

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

In a future update of this data souce: instead of returning an error in this situation, we should likely just perform a latest image tag lookup instead, which matches the behavior of other tooling. The code and documentation should be updated to reflect this change. 馃憤

}

params.ImageIds = []*ecr.ImageIdentifier{&imgId}

var imageDetails []*ecr.ImageDetail
log.Printf("[DEBUG] Reading ECR Images: %s", params)
err := conn.DescribeImagesPages(params, func(page *ecr.DescribeImagesOutput, lastPage bool) bool {
imageDetails = append(imageDetails, page.ImageDetails...)
return true
})
if err != nil {
return fmt.Errorf("Error describing ECR images: %q", err)
}

if len(imageDetails) == 0 {
return fmt.Errorf("No matching image found")
}
if len(imageDetails) > 1 {
return fmt.Errorf("More than one image found for tag/digest combination")
}

image := imageDetails[0]

d.SetId(time.Now().UTC().String())
if err = d.Set("registry_id", *image.RegistryId); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

To prevent potential panics, we should prefer to skip direct * dereferences with d.Set(), which handles pointers automatically, or use the AWS Go SDK provided conversion functions (e.g. aws.StringValue(image.RegistryId)) here and below

Suggested change
if err = d.Set("registry_id", *image.RegistryId); err != nil {
if err = d.Set("registry_id", image.RegistryId); err != nil {

return fmt.Errorf("failed to set registry_id: %s", err)
}
if err = d.Set("image_digest", *image.ImageDigest); err != nil {
return fmt.Errorf("failed to set image_digest: %s", err)
}
if err = d.Set("image_pushed_at", image.ImagePushedAt.Unix()); err != nil {
return fmt.Errorf("failed to set image_pushed_at: %s", err)
}
if err = d.Set("image_size_in_bytes", *image.ImageSizeInBytes); err != nil {
return fmt.Errorf("failed to set image_size_in_bytes: %s", err)
}

tags := []string{}
for _, t := range image.ImageTags {
tags = append(tags, *t)
}
if err := d.Set("image_tags", tags); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be simplified with the AWS Go SDK provided conversion function, aws.StringValueSlice()

Suggested change
if err := d.Set("image_tags", tags); err != nil {
if err := d.Set("image_tags", aws.StringValueSlice(image.ImageTags)); err != nil {

return fmt.Errorf("failed to set image_tags: %s", err)
}

return nil
}
75 changes: 75 additions & 0 deletions aws/data_source_aws_ecr_image_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package aws

import (
"fmt"
"strconv"
"testing"

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

func TestAccAWSEcrDataSource_ecrImage(t *testing.T) {
registry, repo, tag := "137112412989", "amazonlinux", "latest"
resourceByTag := "data.aws_ecr_image.by_tag"
resourceByDigest := "data.aws_ecr_image.by_digest"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccCheckAwsEcrImageDataSourceConfig(registry, repo, tag),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(resourceByTag, "image_digest"),
resource.TestCheckResourceAttrSet(resourceByTag, "image_pushed_at"),
resource.TestCheckResourceAttrSet(resourceByTag, "image_size_in_bytes"),
testCheckTagInImageTags(resourceByTag, tag),
resource.TestCheckResourceAttrSet(resourceByDigest, "image_pushed_at"),
resource.TestCheckResourceAttrSet(resourceByDigest, "image_size_in_bytes"),
testCheckTagInImageTags(resourceByDigest, tag),
),
},
},
})
}

func testAccCheckAwsEcrImageDataSourceConfig(reg, repo, tag string) string {
return fmt.Sprintf(`
data "aws_ecr_image" "by_tag" {
registry_id = "%s"
repository_name = "%s"
image_tag = "%s"
}

data "aws_ecr_image" "by_digest" {
registry_id = "${data.aws_ecr_image.by_tag.registry_id}"
repository_name = "${data.aws_ecr_image.by_tag.repository_name}"
image_digest = "${data.aws_ecr_image.by_tag.image_digest}"
}
`, reg, repo, tag)
}

func testCheckTagInImageTags(name, expectedTag string) resource.TestCheckFunc {
return func(s *terraform.State) error {
// Ensure we have enough information in state to look up in API
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Resource not found: %s", name)
}

tagsLenStr, ok := rs.Primary.Attributes["image_tags.#"]
if !ok {
return fmt.Errorf("No attribute 'image_tags' in resource: %s", name)
}
tagsLen, _ := strconv.Atoi(tagsLenStr)

for i := 0; i < tagsLen; i++ {
tag := rs.Primary.Attributes[fmt.Sprintf("image_tags.%d", i)]
if tag == expectedTag {
return nil
}
}
return fmt.Errorf("No tag '%s' in images_tags of resource %s", expectedTag, name)
}
}
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ func Provider() terraform.ResourceProvider {
"aws_ec2_transit_gateway_route_table": dataSourceAwsEc2TransitGatewayRouteTable(),
"aws_ec2_transit_gateway_vpc_attachment": dataSourceAwsEc2TransitGatewayVpcAttachment(),
"aws_ec2_transit_gateway_vpn_attachment": dataSourceAwsEc2TransitGatewayVpnAttachment(),
"aws_ecr_image": dataSourceAwsEcrImage(),
"aws_ecr_repository": dataSourceAwsEcrRepository(),
"aws_ecs_cluster": dataSourceAwsEcsCluster(),
"aws_ecs_container_definition": dataSourceAwsEcsContainerDefinition(),
Expand Down
3 changes: 3 additions & 0 deletions website/aws.erb
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@
<li>
<a href="/docs/providers/aws/d/ec2_transit_gateway_vpn_attachment.html">aws_ec2_transit_gateway_vpn_attachment</a>
</li>
<li>
<a href="/docs/providers/aws/d/ecr_image.html">aws_ecr_image</a>
</li>
<li>
<a href="/docs/providers/aws/d/ecr_repository.html">aws_ecr_repository</a>
</li>
Expand Down
37 changes: 37 additions & 0 deletions website/docs/d/ecr_image.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
layout: "aws"
page_title: "AWS: aws_ecr_image"
sidebar_current: "docs-aws-datasource-ecr-image"
description: |-
Provides details about an ECR Image
---

# Data Source: aws_ecr_image

The ECR Image data source allows the details of an image with a particular tag or digest to be retrieved.

## Example Usage

```hcl
data "aws_ecr_image" "service_image" {
repository_name = "my/service"
image_tag = "latest"
}
```

## Argument Reference

The following arguments are supported:

* `registry_id` - (Optional) The ID of the Registry where the repository resides.
* `repository_name` - (Required) The name of the ECR Repository.
* `image_digest` - (Optional) The sha256 digest of the image manifest. At least one of `image_digest` or `image_tag` must be specified.
* `image_tag` - (Optional) The tag associated with this image. At least one of `image_digest` or `image_tag` must be specified.

## Attributes Reference

In addition to all arguments above, the following attributes are exported:

* `image_pushed_at` - The date and time, expressed as a unix timestamp, at which the current image was pushed to the repository.
* `image_size_in_bytes` - The size, in bytes, of the image in the repository.
* `image_tags` - The list of tags associated with this image.