Skip to content

Commit

Permalink
Container File Support
Browse files Browse the repository at this point in the history
This commit adds the file block to the lxd_container resource.
One or more individual files may be defined in the resource and
they will be uploaded/pushed to the container via the LXC/LXD API.

Updates to the files will cause all old files to be deleted and
new copies to be uploaded.
  • Loading branch information
jtopjian authored and sl1pm4t committed Apr 6, 2017
1 parent 797489d commit 260414a
Show file tree
Hide file tree
Showing 3 changed files with 242 additions and 18 deletions.
46 changes: 28 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,26 +250,36 @@ The following resources are currently available:

##### Parameters

* `name` - *Required* -Name of the container.
* `image` - *Required* -Base image from which the container will be created.
* `profiles` - *Optional* -Array of LXD config profiles to apply to the new container.
* `ephemeral` - *Optional* -Boolean indicating if this container is ephemeral. Default = false.
* `privileged`- *Optional* -Boolean indicating if this container will run in privileged mode. Default = false.
* `config` - *Optional* -Map of key/value pairs of [container config settings](https://github.com/lxc/lxd/blob/master/doc/configuration.md#container-configuration).
* `device` - *Optional* -Device definition. See reference below.
* `name` - *Required* - Name of the container.
* `image` - *Required* - Base image from which the container will be created.
* `profiles` - *Optional* - Array of LXD config profiles to apply to the new container.
* `ephemeral` - *Optional* - Boolean indicating if this container is ephemeral. Default = false.
* `privileged`- *Optional* - Boolean indicating if this container will run in privileged mode. Default = false.
* `config` - *Optional* - Map of key/value pairs of [container config settings](https://github.com/lxc/lxd/blob/master/doc/configuration.md#container-configuration).
* `device` - *Optional* - Device definition. See reference below.
* `file` - *Optional* - File to upload to the container. See reference below.

##### Device Block

* `name` - *Required* -Name of the device.
* `type` - *Required* -Type of the device Must be one of none, disk, nic, unix-char, unix-block, usb, gpu.
* `properties`- *Required* -Map of key/value pairs of [device properties](https://github.com/lxc/lxd/blob/master/doc/configuration.md#devices-configuration).
* `name` - *Required* - Name of the device.
* `type` - *Required* - Type of the device Must be one of none, disk, nic, unix-char, unix-block, usb, gpu.
* `properties`- *Required* - Map of key/value pairs of [device properties](https://github.com/lxc/lxd/blob/master/doc/configuration.md#devices-configuration).

##### File Block

* `content` - *Required* - The _contents_ of the file. Use the `file()` function to read in the content of a file from disk.
* `target_file` - *Required* - The absolute path of the file on the container, including the filename.
* `uid` - *Optional* - The UID of the file. Must be an unquoted integer.
* `gid` - *Optional* - The GID of the file. Must be an unquoted integer.
* `mode` - *Optional* - The octal permissions of the file, must be quoted.
* `create_directories` - *Optional* - Whether to create the directories leading to the target if they do not exist.

#### lxd_network

##### Parameters

* `name` - *Required* -Name of the network. This is usually the device the network will appear as to containers.
* `config` - *Optional* -Map of key/value pairs of [network config settings](https://github.com/lxc/lxd/blob/master/doc/configuration.md#network-configuration).
* `name` - *Required* - Name of the network. This is usually the device the network will appear as to containers.
* `config` - *Optional* - Map of key/value pairs of [network config settings](https://github.com/lxc/lxd/blob/master/doc/configuration.md#network-configuration).

##### Exported Attributes

Expand All @@ -280,15 +290,15 @@ The following resources are currently available:

##### Parameters

* `name` - *Required* -Name of the container.
* `config` - *Optional* -Map of key/value pairs of [container config settings](https://github.com/lxc/lxd/blob/master/doc/configuration.md#container-configuration).
* `device` - *Optional* -Device definition. See reference below.
* `name` - *Required* - Name of the container.
* `config` - *Optional* - Map of key/value pairs of [container config settings](https://github.com/lxc/lxd/blob/master/doc/configuration.md#container-configuration).
* `device` - *Optional* - Device definition. See reference below.

##### Device Block

* `name` - *Required* -Name of the device.
* `type` - *Required* -Type of the device Must be one of none, disk, nic, unix-char, unix-block, usb, gpu.
* `properties`- *Required* -Map of key/value pairs of [device properties](https://github.com/lxc/lxd/blob/master/doc/configuration.md#devices-configuration).
* `name` - *Required* - Name of the device.
* `type` - *Required* - Type of the device Must be one of none, disk, nic, unix-char, unix-block, usb, gpu.
* `properties`- *Required* - Map of key/value pairs of [device properties](https://github.com/lxc/lxd/blob/master/doc/configuration.md#devices-configuration).

## Known Limitations

Expand Down
156 changes: 156 additions & 0 deletions lxd/resource_lxd_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ package lxd
import (
"fmt"
"log"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/hashicorp/terraform/helper/resource"
Expand Down Expand Up @@ -85,6 +90,44 @@ func resourceLxdContainer() *schema.Resource {
ForceNew: false,
},

"file": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"content": &schema.Schema{
Type: schema.TypeString,
Required: true,
},

"target_file": &schema.Schema{
Type: schema.TypeString,
Required: true,
},

"uid": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},

"gid": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},

"mode": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},

"create_directories": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
},
},
},

"ip_address": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -155,6 +198,16 @@ func resourceLxdContainerCreate(d *schema.ResourceData, meta interface{}) error
}

d.SetId(name)

// Upload any files, if specified
if v, ok := d.GetOk("file"); ok {
for _, v := range v.([]interface{}) {
if err := resourceLxdContainerUploadFile(client, name, v); err != nil {
return err
}
}
}

return resourceLxdContainerRead(d, meta)
}

Expand Down Expand Up @@ -261,6 +314,21 @@ func resourceLxdContainerUpdate(d *schema.ResourceData, meta interface{}) error
}
}

if d.HasChange("file") {
oldFiles, newFiles := d.GetChange("file")
for _, v := range oldFiles.([]interface{}) {
if err := resourceLxdContainerDeleteFile(client, name, v); err != nil {
return err
}
}

for _, v := range newFiles.([]interface{}) {
if err := resourceLxdContainerUploadFile(client, name, v); err != nil {
return err
}
}
}

return nil
}

Expand Down Expand Up @@ -329,3 +397,91 @@ func resourceLxdContainerRefresh(client *lxd.Client, name string) resource.State
return ct, ct.Status, nil
}
}

func resourceLxdContainerUploadFile(client *lxd.Client, container string, file interface{}) error {
var uid, gid int
var createDirectories bool
fileInfo := file.(map[string]interface{})

fileContent := fileInfo["content"].(string)
fileTarget := fileInfo["target_file"].(string)

if v, ok := fileInfo["uid"]; ok {
uid = v.(int)
}

if v, ok := fileInfo["gid"]; ok {
gid = v.(int)
}

if v, ok := fileInfo["create_directories"]; ok {
createDirectories = v.(bool)
}

fileTarget, err := filepath.Abs(fileTarget)
if err != nil {
return fmt.Errorf("Could not santize destination target %s", fileTarget)
}

targetIsDir := strings.HasSuffix(fileTarget, "/")
if targetIsDir {
return fmt.Errorf("Target must be an absolute path with filename")
}

mode := os.FileMode(0755)
if v, ok := fileInfo["mode"].(string); ok && v != "" {
if len(v) != 3 {
v = "0" + v
}

m, err := strconv.ParseInt(v, 0, 0)
if err != nil {
return fmt.Errorf("Could not determine file mode %s", v)
}

mode = os.FileMode(m)
}

log.Printf("[DEBUG] Attempting to upload file to %s with uid %d, gid %d, and mode %s",
fileTarget, uid, gid, fmt.Sprintf("%04o", mode.Perm()))

if createDirectories {
if err := client.MkdirP(container, path.Dir(fileTarget), mode, uid, gid); err != nil {
return fmt.Errorf("Could not create path %s", path.Dir(fileTarget))
}
}

f := strings.NewReader(fileContent)
if err := client.PushFile(
container, fileTarget, gid, uid, fmt.Sprintf("%04o", mode.Perm()), f); err != nil {
return fmt.Errorf("Could not upload file %s: %s", fileTarget, err)
}

log.Printf("[DEBUG] Successfully uploaded file %s", fileTarget)

return nil
}

func resourceLxdContainerDeleteFile(client *lxd.Client, container string, file interface{}) error {
fileInfo := file.(map[string]interface{})
fileTarget := fileInfo["target_file"].(string)
fileTarget, err := filepath.Abs(fileTarget)
if err != nil {
return fmt.Errorf("Could not santize destination target %s", fileTarget)
}

targetIsDir := strings.HasSuffix(fileTarget, "/")
if targetIsDir {
return fmt.Errorf("Target must be an absolute path with filename")
}

log.Printf("[DEBUG] Attempting to delete file %s", fileTarget)

if err := client.DeleteFile(container, fileTarget); err != nil {
return fmt.Errorf("Could not delete file %s: %s", fileTarget, err)
}

log.Printf("[DEBUG] Successfully deleted file %s", fileTarget)

return nil
}
58 changes: 58 additions & 0 deletions lxd/resource_lxd_container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,30 @@ func TestAccContainer_removeDevice(t *testing.T) {
})
}

func TestAccContainer_fileUpload(t *testing.T) {
var container api.Container
containerName := strings.ToLower(petname.Generate(2, "-"))

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccContainer_fileUpload_1(containerName),
Check: resource.ComposeTestCheckFunc(
testAccContainerRunning(t, "lxd_container.container1", &container),
),
},
resource.TestStep{
Config: testAccContainer_fileUpload_2(containerName),
Check: resource.ComposeTestCheckFunc(
testAccContainerRunning(t, "lxd_container.container1", &container),
),
},
},
})
}

func testAccContainerRunning(t *testing.T, n string, container *api.Container) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
Expand Down Expand Up @@ -562,3 +586,37 @@ resource "lxd_container" "container1" {
}
`, name)
}

func testAccContainer_fileUpload_1(name string) string {
return fmt.Sprintf(`
resource "lxd_container" "container1" {
name = "%s"
image = "ubuntu"
profiles = ["default"]
file {
content = "Hello, World!\n"
target_file = "/tmp/foo/bar.txt"
mode = "0644"
create_directories = true
}
}
`, name)
}

func testAccContainer_fileUpload_2(name string) string {
return fmt.Sprintf(`
resource "lxd_container" "container1" {
name = "%s"
image = "ubuntu"
profiles = ["default"]
file {
content = "Goodbye, World!\n"
target_file = "/tmp/foo/bar.txt"
mode = "0644"
create_directories = true
}
}
`, name)
}

0 comments on commit 260414a

Please sign in to comment.