Skip to content

Commit

Permalink
Add DigitalOcean datasource digitalocean_image (#13787)
Browse files Browse the repository at this point in the history
Add a new data source for Digital Ocean that finds snapshots by name.

Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu>
  • Loading branch information
roidelapluie authored and stack72 committed Apr 21, 2017
1 parent be0390e commit c2a1e68
Show file tree
Hide file tree
Showing 4 changed files with 277 additions and 0 deletions.
93 changes: 93 additions & 0 deletions builtin/providers/digitalocean/datasource_digitaloceal_image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package digitalocean

import (
"fmt"
"strconv"

"github.com/digitalocean/godo"
"github.com/hashicorp/terraform/helper/schema"
)

func dataSourceDigitalOceanImage() *schema.Resource {
return &schema.Resource{
Read: dataSourceDigitalOceanImageRead,
Schema: map[string]*schema.Schema{

"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "name of the image",
},
// computed attributes
"image": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: "slug or id of the image",
},
"min_disk_size": &schema.Schema{
Type: schema.TypeInt,
Computed: true,
Description: "minimum disk size required by the image",
},
"private": &schema.Schema{
Type: schema.TypeBool,
Computed: true,
Description: "Is the image private or non-private",
},
"regions": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Description: "list of the regions that the image is available in",
Elem: &schema.Schema{Type: schema.TypeString},
},
"type": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: "type of the image",
},
},
}
}

func dataSourceDigitalOceanImageRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)

opts := &godo.ListOptions{}

images, _, err := client.Images.ListUser(opts)
if err != nil {
d.SetId("")
return err
}
image, err := findImageByName(images, d.Get("name").(string))

if err != nil {
return err
}

d.SetId(image.Name)
d.Set("name", image.Name)
d.Set("image", strconv.Itoa(image.ID))
d.Set("min_disk_size", image.MinDiskSize)
d.Set("private", !image.Public)
d.Set("regions", image.Regions)
d.Set("type", image.Type)

return nil
}

func findImageByName(images []godo.Image, name string) (*godo.Image, error) {
results := make([]godo.Image, 0)
for _, v := range images {
if v.Name == name {
results = append(results, v)
}
}
if len(results) == 1 {
return &results[0], nil
}
if len(results) == 0 {
return nil, fmt.Errorf("no user image found with name %s", name)
}
return nil, fmt.Errorf("too many user images found with name %s (found %d, expected 1)", name, len(results))
}
122 changes: 122 additions & 0 deletions builtin/providers/digitalocean/datasource_digitaloceal_image_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package digitalocean

import (
"fmt"
"log"
"regexp"
"testing"

"github.com/digitalocean/godo"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccDigitalOceanImage_Basic(t *testing.T) {
var droplet godo.Droplet
var snapshotsId []int
rInt := acctest.RandInt()

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanDropletDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckDigitalOceanDropletConfig_basic(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckDigitalOceanDropletExists("digitalocean_droplet.foobar", &droplet),
takeSnapshotsOfDroplet(rInt, &droplet, &snapshotsId),
),
},
{
Config: testAccCheckDigitalOceanImageConfig_basic(rInt, 1),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"data.digitalocean_image.foobar", "name", fmt.Sprintf("snap-%d-1", rInt)),
resource.TestCheckResourceAttr(
"data.digitalocean_image.foobar", "min_disk_size", "20"),
resource.TestCheckResourceAttr(
"data.digitalocean_image.foobar", "private", "true"),
resource.TestCheckResourceAttr(
"data.digitalocean_image.foobar", "type", "snapshot"),
),
},
{
Config: testAccCheckDigitalOceanImageConfig_basic(rInt, 0),
ExpectError: regexp.MustCompile(`.*too many user images found with name snap-.*\ .found 2, expected 1.`),
},
{
Config: testAccCheckDigitalOceanImageConfig_nonexisting(rInt),
Destroy: false,
ExpectError: regexp.MustCompile(`.*no user image found with name snap-.*-nonexisting`),
},
{
Config: " ",
Check: resource.ComposeTestCheckFunc(
deleteSnapshots(&snapshotsId),
),
},
},
})
}

func takeSnapshotsOfDroplet(rInt int, droplet *godo.Droplet, snapshotsId *[]int) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*godo.Client)
for i := 0; i < 3; i++ {
err := takeSnapshotOfDroplet(rInt, i%2, droplet)
if err != nil {
return err
}
}
retrieveDroplet, _, err := client.Droplets.Get((*droplet).ID)
if err != nil {
return err
}
*snapshotsId = retrieveDroplet.SnapshotIDs
return nil
}
}

func takeSnapshotOfDroplet(rInt, sInt int, droplet *godo.Droplet) error {
client := testAccProvider.Meta().(*godo.Client)
action, _, err := client.DropletActions.Snapshot((*droplet).ID, fmt.Sprintf("snap-%d-%d", rInt, sInt))
if err != nil {
return err
}
waitForAction(client, action)
return nil
}

func deleteSnapshots(snapshotsId *[]int) resource.TestCheckFunc {
return func(s *terraform.State) error {
log.Printf("XXX Deleting snaps")
client := testAccProvider.Meta().(*godo.Client)
snapshots := *snapshotsId
for _, value := range snapshots {
log.Printf("XXX Deleting %d", value)
_, err := client.Images.Delete(value)
if err != nil {
return err
}
}
return nil
}
}

func testAccCheckDigitalOceanImageConfig_basic(rInt, sInt int) string {
return fmt.Sprintf(`
data "digitalocean_image" "foobar" {
name = "snap-%d-%d"
}
`, rInt, sInt)
}

func testAccCheckDigitalOceanImageConfig_nonexisting(rInt int) string {
return fmt.Sprintf(`
data "digitalocean_image" "foobar" {
name = "snap-%d-nonexisting"
}
`, rInt)
}
4 changes: 4 additions & 0 deletions builtin/providers/digitalocean/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ func Provider() terraform.ResourceProvider {
},
},

DataSourcesMap: map[string]*schema.Resource{
"digitalocean_image": dataSourceDigitalOceanImage(),
},

ResourcesMap: map[string]*schema.Resource{
"digitalocean_domain": resourceDigitalOceanDomain(),
"digitalocean_droplet": resourceDigitalOceanDroplet(),
Expand Down
58 changes: 58 additions & 0 deletions website/source/docs/providers/do/d/image.html.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
layout: "digitalocean"
page_title: "DigitalOcean: digitalocean_image"
sidebar_current: "docs-do-datasource-image"
description: |-
Get information on an snapshot.
---

# digitalocean_image

Get information on an snapshot images. The aim of this datasource is to enable
you to build droplets based on snapshot names.

An error is triggered if zero or more than one result is returned by the query.

## Example Usage

Get the data about a snapshot:

```hcl
data "digitalocean_image" "example1" {
name = "example-1.0.0"
}
```

Reuse the data about a snapshot to create a droplet:

```hcl
data "digitalocean_image" "example1" {
name = "example-1.0.0"
}
resource "digitalocean_droplet" "example1" {
image = "${data.digitalocean_image.example1.image}"
name = "example-1"
region = "nyc2"
size = "512mb"
}
```

## Argument Reference

The following arguments are supported:

* `name` - The name of the image.

## Attributes Reference

The following attributes are exported:

* `name` - See Argument Reference above.
* `image` - The id of the image.
* `min_disk_size`: The minimum 'disk' required for the image.
* `private` - Is image a public image or not. Public images represents
Linux distributions or Application, while non-public images represent
snapshots and backups and are only available within your account.
* `regions`: The regions that the image is available in.
* `size_gigabytes`: The size of the image in gigabytes.
* `type`: Type of the image. Can be "snapshot" or "backup".

0 comments on commit c2a1e68

Please sign in to comment.