diff --git a/builtin/providers/atlas/data_source_artifact.go b/builtin/providers/atlas/data_source_artifact.go new file mode 100644 index 000000000..7dcfdbe81 --- /dev/null +++ b/builtin/providers/atlas/data_source_artifact.go @@ -0,0 +1,149 @@ +package atlas + +import ( + "fmt" + + "github.com/hashicorp/atlas-go/v1" + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSourceAtlasArtifact() *schema.Resource { + return &schema.Resource{ + Read: dataSourceArtifactRead, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "build": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "version": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "metadata_keys": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "metadata": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + + "file_url": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "metadata_full": &schema.Schema{ + Type: schema.TypeMap, + Computed: true, + }, + + "slug": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "version_real": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceArtifactRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*atlas.Client) + + // Parse the slug from the name given of the artifact since the API + // expects these to be split. + user, name, err := atlas.ParseSlug(d.Get("name").(string)) + if err != nil { + return err + } + + // Filter by version or build if given + var build, version string + if v, ok := d.GetOk("version"); ok { + version = v.(string) + } else if b, ok := d.GetOk("build"); ok { + build = b.(string) + } + + // If we have neither, default to latest version + if build == "" && version == "" { + version = "latest" + } + + // Compile the metadata search params + md := make(map[string]string) + for _, v := range d.Get("metadata_keys").(*schema.Set).List() { + md[v.(string)] = atlas.MetadataAnyValue + } + for k, v := range d.Get("metadata").(map[string]interface{}) { + md[k] = v.(string) + } + + // Do the search! + vs, err := client.ArtifactSearch(&atlas.ArtifactSearchOpts{ + User: user, + Name: name, + Type: d.Get("type").(string), + Build: build, + Version: version, + Metadata: md, + }) + if err != nil { + return fmt.Errorf( + "Error searching for artifact '%s/%s': %s", + user, name, err) + } + + if len(vs) == 0 { + return fmt.Errorf("No matching artifact for '%s/%s'", user, name) + } else if len(vs) > 1 { + return fmt.Errorf( + "Got %d results for '%s/%s', only one is allowed", + len(vs), user, name) + } + v := vs[0] + + d.SetId(v.ID) + if v.ID == "" { + d.SetId(fmt.Sprintf("%s %d", v.Tag, v.Version)) + } + d.Set("version_real", v.Version) + d.Set("metadata_full", cleanMetadata(v.Metadata)) + d.Set("slug", v.Slug) + + d.Set("file_url", "") + if u, err := client.ArtifactFileURL(v); err != nil { + return fmt.Errorf( + "Error reading file URL: %s", err) + } else if u != nil { + d.Set("file_url", u.String()) + } + + return nil +} diff --git a/builtin/providers/atlas/data_source_artifact_test.go b/builtin/providers/atlas/data_source_artifact_test.go new file mode 100644 index 000000000..c6d503b3b --- /dev/null +++ b/builtin/providers/atlas/data_source_artifact_test.go @@ -0,0 +1,150 @@ +package atlas + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataSourceArtifact_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataArtifact_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckDataArtifactState("name", "hashicorp/tf-provider-test"), + ), + }, + }, + }) +} + +func TestAccDataSourceArtifact_metadata(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataArtifact_metadata, + Check: resource.ComposeTestCheckFunc( + testAccCheckDataArtifactState("name", "hashicorp/tf-provider-test"), + testAccCheckDataArtifactState("id", "x86"), + testAccCheckDataArtifactState("metadata_full.arch", "x86"), + ), + }, + }, + }) +} + +func TestAccDataSourceArtifact_metadataSet(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataArtifact_metadataSet, + Check: resource.ComposeTestCheckFunc( + testAccCheckDataArtifactState("name", "hashicorp/tf-provider-test"), + testAccCheckDataArtifactState("id", "x64"), + testAccCheckDataArtifactState("metadata_full.arch", "x64"), + ), + }, + }, + }) +} + +func TestAccDataSourceArtifact_buildLatest(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataArtifact_buildLatest, + Check: resource.ComposeTestCheckFunc( + testAccCheckDataArtifactState("name", "hashicorp/tf-provider-test"), + ), + }, + }, + }) +} + +func TestAccDataSourceArtifact_versionAny(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataArtifact_versionAny, + Check: resource.ComposeTestCheckFunc( + testAccCheckDataArtifactState("name", "hashicorp/tf-provider-test"), + ), + }, + }, + }) +} + +func testAccCheckDataArtifactState(key, value string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources["data.atlas_artifact.foobar"] + if !ok { + return fmt.Errorf("Not found: %s", "data.atlas_artifact.foobar") + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + p := rs.Primary + if p.Attributes[key] != value { + return fmt.Errorf( + "%s != %s (actual: %s)", key, value, p.Attributes[key]) + } + + return nil + } +} + +const testAccDataArtifact_basic = ` +data "atlas_artifact" "foobar" { + name = "hashicorp/tf-provider-test" + type = "foo" +}` + +const testAccDataArtifact_metadata = ` +data "atlas_artifact" "foobar" { + name = "hashicorp/tf-provider-test" + type = "foo" + metadata { + arch = "x86" + } + version = "any" +}` + +const testAccDataArtifact_metadataSet = ` +data "atlas_artifact" "foobar" { + name = "hashicorp/tf-provider-test" + type = "foo" + metadata_keys = ["arch"] + version = "any" +}` + +const testAccDataArtifact_buildLatest = ` +data "atlas_artifact" "foobar" { + name = "hashicorp/tf-provider-test" + type = "foo" + build = "latest" + metadata { + arch = "x86" + } +}` + +const testAccDataArtifact_versionAny = ` +data "atlas_artifact" "foobar" { + name = "hashicorp/tf-provider-test" + type = "foo" + version = "any" +}` diff --git a/builtin/providers/atlas/provider.go b/builtin/providers/atlas/provider.go index 96eaf7ecb..3d14edca8 100644 --- a/builtin/providers/atlas/provider.go +++ b/builtin/providers/atlas/provider.go @@ -31,6 +31,10 @@ func Provider() terraform.ResourceProvider { }, }, + DataSourcesMap: map[string]*schema.Resource{ + "atlas_artifact": dataSourceAtlasArtifact(), + }, + ResourcesMap: map[string]*schema.Resource{ "atlas_artifact": resourceArtifact(), }, diff --git a/builtin/providers/atlas/resource_artifact.go b/builtin/providers/atlas/resource_artifact.go index 447b0b35a..1ac7bd38a 100644 --- a/builtin/providers/atlas/resource_artifact.go +++ b/builtin/providers/atlas/resource_artifact.go @@ -22,9 +22,10 @@ func resourceArtifact() *schema.Resource { Schema: map[string]*schema.Schema{ "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + Deprecated: `atlas_artifact is now deprecated. Use the Atlas Artifact Data Source instead. See https://terraform.io/docs/providers/atlas/d/artifact.html`, }, "type": &schema.Schema{ diff --git a/builtin/providers/atlas/resource_artifact_test.go b/builtin/providers/atlas/resource_artifact_test.go index 19f6680d8..440b954e4 100644 --- a/builtin/providers/atlas/resource_artifact_test.go +++ b/builtin/providers/atlas/resource_artifact_test.go @@ -2,7 +2,6 @@ package atlas import ( "fmt" - "reflect" "testing" "github.com/hashicorp/terraform/helper/resource" @@ -109,21 +108,6 @@ func testAccCheckArtifactState(key, value string) resource.TestCheckFunc { } } -func TestCleanMetadata(t *testing.T) { - in := map[string]string{ - "region.us-east-1": "in", - "what is this?": "out", - } - exp := map[string]string{ - "region-us-east-1": "in", - "what-is-this-": "out", - } - out := cleanMetadata(in) - if !reflect.DeepEqual(out, exp) { - t.Fatalf("bad: %#v", out) - } -} - const testAccArtifact_basic = ` resource "atlas_artifact" "foobar" { name = "hashicorp/tf-provider-test" diff --git a/website/source/docs/providers/atlas/d/artifact.html.markdown b/website/source/docs/providers/atlas/d/artifact.html.markdown new file mode 100644 index 000000000..3939fa74a --- /dev/null +++ b/website/source/docs/providers/atlas/d/artifact.html.markdown @@ -0,0 +1,86 @@ +--- +layout: "atlas" +page_title: "Atlas: atlas_artifact" +sidebar_current: "docs-atlas-data-artifact" +description: |- + Provides a data source to deployment artifacts managed by Atlas. This can + be used to dynamically configure instantiation and provisioning + of resources. +--- + +# atlas\_artifact + +Provides a [Data Source](/docs/configuration/data-sources.html) to access to deployment +artifacts managed by Atlas. This can be used to dynamically configure instantiation +and provisioning of resources. + +## Example Usage + +An artifact can be created that has metadata representing +an AMI in AWS. This AMI can be used to configure an instance. Any changes +to this artifact will trigger a change to that instance. + +``` +# Read the AMI +data "atlas_artifact" "web" { + name = "hashicorp/web" + type = "amazon.image" + build = "latest" + metadata { + arch = "386" + } +} + +# Start our instance with the dynamic ami value +# Remember to include the AWS region as it is part of the full ID +resource "aws_instance" "app" { + ami = "${data.atlas_artifact.web.metadata_full.region-us-east-1}" + ... +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) Name of the artifact in Atlas. This is given + in slug format like "organization/artifact". + +* `type` - (Required) The type of artifact to query for. + +* `build` - (Optional) The build number responsible for creating + the version of the artifact to filter on. This can be "latest", + to find a matching artifact in the latest build, "any" to find a + matching artifact in any build, or a specific number to pin to that + build. If `build` and `version` are unspecified, `version` will default + to "latest". Cannot be specified with `version`. Note: `build` is only + present if Atlas builds the image. + +* `version` - (Optional) The version of the artifact to filter on. This can + be "latest", to match against the latest version, "any" to find a matching artifact + in any version, or a specific number to pin to that version. Defaults to + "latest" if neither `build` or `version` is specified. Cannot be specified + with `build`. + +* `metadata_keys` - (Optional) If given, only an artifact containing + the given keys will be returned. This is used to disambiguate when + multiple potential artifacts match. An example is "aws" to filter + on an AMI. + +* `metadata` - (Optional) If given, only an artifact matching the + metadata filters will be returned. This is used to disambiguate when + multiple potential artifacts match. An example is "arch" = "386" to + filter on architecture. + + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the artifact. This could be an AMI ID, GCE Image ID, etc. +* `file_url` - For artifacts that are binaries, this is a download path. +* `metadata_full` - Contains the full metadata of the artifact. The keys are sanitized + to replace any characters that are invalid in a resource name with a hyphen. + For example, the "region.us-east-1" key will become "region-us-east-1". +* `version_real` - The matching version of the artifact +* `slug` - The artifact slug in Atlas diff --git a/website/source/docs/providers/atlas/r/artifact.html.markdown b/website/source/docs/providers/atlas/r/artifact.html.markdown index 41c0fe296..14992d942 100644 --- a/website/source/docs/providers/atlas/r/artifact.html.markdown +++ b/website/source/docs/providers/atlas/r/artifact.html.markdown @@ -14,6 +14,9 @@ Provides access to deployment artifacts managed by Atlas. This can be used to dynamically configure instantiation and provisioning of resources. +~> **NOTE: This resource is deprecated.** +Please use the [Atlas Artifact Data Source](/docs/providers/atlas/d/artifact.html) + ## Example Usage An artifact can be created that has metadata representing diff --git a/website/source/docs/providers/aws/d/iam_policy_document.html.markdown b/website/source/docs/providers/aws/d/iam_policy_document.html.markdown index f055834ab..036765202 100644 --- a/website/source/docs/providers/aws/d/iam_policy_document.html.markdown +++ b/website/source/docs/providers/aws/d/iam_policy_document.html.markdown @@ -1,7 +1,7 @@ --- layout: "aws" page_title: "AWS: aws_iam_policy_document" -sidebar_current: "docs-aws-resource-iam-policy-document" +sidebar_current: "docs-aws-datasource-iam-policy-document" description: |- Generates an IAM policy document in JSON format --- diff --git a/website/source/layouts/atlas.erb b/website/source/layouts/atlas.erb index 8df8365e3..72ef8d888 100644 --- a/website/source/layouts/atlas.erb +++ b/website/source/layouts/atlas.erb @@ -3,24 +3,31 @@ <% end %> <%= yield %> - <% end %> +<% end %> +