├── lxc ├── config.go ├── provider_test.go ├── provider.go ├── resource_lxc_container_test.go ├── resource_lxc_clone_test.go ├── resource_lxc_bridge_test.go ├── resource_lxc_bridge.go ├── utils.go ├── resource_lxc_clone.go └── resource_lxc_container.go ├── Godeps ├── Readme └── Godeps.json ├── main.go ├── .gitignore └── README.md /lxc/config.go: -------------------------------------------------------------------------------- 1 | package lxc 2 | 3 | type Config struct { 4 | LXCPath string 5 | LXCLogPath string 6 | } 7 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/hashicorp/terraform/plugin" 5 | "github.com/jtopjian/terraform-provider-lxc/lxc" 6 | ) 7 | 8 | func main() { 9 | plugin.Serve(&plugin.ServeOpts{ 10 | ProviderFunc: lxc.Provider, 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | **/*.swp 27 | Godeps/_workspace 28 | -------------------------------------------------------------------------------- /lxc/provider_test.go: -------------------------------------------------------------------------------- 1 | package lxc 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform/helper/schema" 8 | "github.com/hashicorp/terraform/terraform" 9 | ) 10 | 11 | var testAccProviders map[string]terraform.ResourceProvider 12 | var testAccProvider *schema.Provider 13 | 14 | func init() { 15 | testAccProvider = Provider().(*schema.Provider) 16 | testAccProviders = map[string]terraform.ResourceProvider{ 17 | "lxc": testAccProvider, 18 | } 19 | } 20 | 21 | func TestProvider(t *testing.T) { 22 | if err := Provider().(*schema.Provider).InternalValidate(); err != nil { 23 | t.Fatalf("err: %s", err) 24 | } 25 | } 26 | 27 | func TestProvider_impl(t *testing.T) { 28 | var _ terraform.ResourceProvider = Provider() 29 | } 30 | 31 | func testAccPreCheck(t *testing.T) { 32 | // nothing to do yet 33 | } 34 | 35 | func testProviderConfig() (*Config, error) { 36 | config := testAccProvider.Meta().(*Config) 37 | if config == nil { 38 | return nil, fmt.Errorf("Unable to obtain provider config\n") 39 | } 40 | 41 | return config, nil 42 | } 43 | -------------------------------------------------------------------------------- /lxc/provider.go: -------------------------------------------------------------------------------- 1 | package lxc 2 | 3 | import ( 4 | "github.com/hashicorp/terraform/helper/schema" 5 | "github.com/hashicorp/terraform/terraform" 6 | ) 7 | 8 | func Provider() terraform.ResourceProvider { 9 | return &schema.Provider{ 10 | Schema: map[string]*schema.Schema{ 11 | "lxc_path": &schema.Schema{ 12 | Type: schema.TypeString, 13 | Optional: true, 14 | Default: "/var/lib/lxc", 15 | }, 16 | "lxc_log_path": &schema.Schema{ 17 | Type: schema.TypeString, 18 | Optional: true, 19 | Default: "/var/log/lxc", 20 | }, 21 | }, 22 | 23 | ResourcesMap: map[string]*schema.Resource{ 24 | "lxc_bridge": resourceLXCBridge(), 25 | "lxc_clone": resourceLXCClone(), 26 | "lxc_container": resourceLXCContainer(), 27 | }, 28 | 29 | ConfigureFunc: configureProvider, 30 | } 31 | } 32 | 33 | func configureProvider(d *schema.ResourceData) (interface{}, error) { 34 | 35 | config := Config{ 36 | LXCPath: d.Get("lxc_path").(string), 37 | LXCLogPath: d.Get("lxc_log_path").(string), 38 | } 39 | 40 | return &config, nil 41 | } 42 | -------------------------------------------------------------------------------- /lxc/resource_lxc_container_test.go: -------------------------------------------------------------------------------- 1 | package lxc 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform/helper/resource" 8 | "github.com/hashicorp/terraform/terraform" 9 | 10 | "gopkg.in/lxc/go-lxc.v2" 11 | ) 12 | 13 | func TestLXCContainer(t *testing.T) { 14 | var container lxc.Container 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | Providers: testAccProviders, 18 | CheckDestroy: testAccCheckLXCContainerDestroy, 19 | Steps: []resource.TestStep{ 20 | resource.TestStep{ 21 | Config: testAccLXCContainer, 22 | Check: resource.ComposeTestCheckFunc( 23 | testAccCheckLXCContainerExists( 24 | t, "lxc_container.accept_test", &container), 25 | resource.TestCheckResourceAttr( 26 | "lxc_container.accept_test", "name", "accept_test"), 27 | ), 28 | }, 29 | }, 30 | }) 31 | } 32 | 33 | func testAccCheckLXCContainerExists(t *testing.T, n string, container *lxc.Container) resource.TestCheckFunc { 34 | return func(s *terraform.State) error { 35 | rs, ok := s.RootModule().Resources[n] 36 | if !ok { 37 | return fmt.Errorf("Not found: %v", n) 38 | } 39 | 40 | if rs.Primary.ID == "" { 41 | return fmt.Errorf("No ID is set") 42 | } 43 | 44 | config, err := testProviderConfig() 45 | if err != nil { 46 | return err 47 | } 48 | 49 | c := lxc.ActiveContainers(config.LXCPath) 50 | for i := range c { 51 | if c[i].Name() == rs.Primary.ID { 52 | *container = c[i] 53 | return nil 54 | } 55 | } 56 | 57 | return fmt.Errorf("Unable to find running container.") 58 | } 59 | } 60 | 61 | func testAccCheckLXCContainerDestroy(s *terraform.State) error { 62 | config, err := testProviderConfig() 63 | if err != nil { 64 | return err 65 | } 66 | 67 | for _, rs := range s.RootModule().Resources { 68 | if rs.Type != "lxc_container" { 69 | continue 70 | } 71 | 72 | c := lxc.ActiveContainers(config.LXCPath) 73 | for i := range c { 74 | if c[i].Name() == rs.Primary.ID { 75 | return fmt.Errorf("Container still exists.") 76 | } 77 | } 78 | } 79 | 80 | return nil 81 | } 82 | 83 | var testAccLXCContainer = ` 84 | resource "lxc_container" "accept_test" { 85 | name = "accept_test" 86 | }` 87 | -------------------------------------------------------------------------------- /lxc/resource_lxc_clone_test.go: -------------------------------------------------------------------------------- 1 | package lxc 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform/helper/resource" 8 | "github.com/hashicorp/terraform/terraform" 9 | 10 | "gopkg.in/lxc/go-lxc.v2" 11 | ) 12 | 13 | func TestLXCClone(t *testing.T) { 14 | var container lxc.Container 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | Providers: testAccProviders, 18 | CheckDestroy: testAccCheckLXCCloneDestroy, 19 | Steps: []resource.TestStep{ 20 | resource.TestStep{ 21 | Config: testAccLXCClone, 22 | Check: resource.ComposeTestCheckFunc( 23 | testAccCheckLXCCloneExists( 24 | t, "lxc_clone.accept_clone", &container), 25 | resource.TestCheckResourceAttr( 26 | "lxc_clone.accept_clone", "name", "accept_clone"), 27 | ), 28 | }, 29 | }, 30 | }) 31 | } 32 | 33 | func testAccCheckLXCCloneExists(t *testing.T, n string, container *lxc.Container) resource.TestCheckFunc { 34 | return func(s *terraform.State) error { 35 | rs, ok := s.RootModule().Resources[n] 36 | if !ok { 37 | return fmt.Errorf("Not found: %v", n) 38 | } 39 | 40 | if rs.Primary.ID == "" { 41 | return fmt.Errorf("No ID is set") 42 | } 43 | 44 | config, err := testProviderConfig() 45 | if err != nil { 46 | return err 47 | } 48 | 49 | c := lxc.ActiveContainers(config.LXCPath) 50 | for i := range c { 51 | if c[i].Name() == rs.Primary.ID { 52 | *container = c[i] 53 | return nil 54 | } 55 | } 56 | 57 | return fmt.Errorf("Unable to find running container.") 58 | } 59 | } 60 | 61 | func testAccCheckLXCCloneDestroy(s *terraform.State) error { 62 | config, err := testProviderConfig() 63 | if err != nil { 64 | return err 65 | } 66 | 67 | for _, rs := range s.RootModule().Resources { 68 | if rs.Type != "lxc_container" { 69 | continue 70 | } 71 | 72 | c := lxc.ActiveContainers(config.LXCPath) 73 | for i := range c { 74 | if c[i].Name() == rs.Primary.ID { 75 | return fmt.Errorf("Container still exists.") 76 | } 77 | } 78 | } 79 | 80 | return nil 81 | } 82 | 83 | var testAccLXCClone = ` 84 | resource "lxc_container" "accept_test" { 85 | name = "accept_test" 86 | } 87 | 88 | resource "lxc_clone" "accept_clone" { 89 | name = "accept_clone" 90 | source = "${lxc_container.accept_test.name}" 91 | }` 92 | -------------------------------------------------------------------------------- /lxc/resource_lxc_bridge_test.go: -------------------------------------------------------------------------------- 1 | package lxc 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "testing" 7 | 8 | "github.com/hashicorp/terraform/helper/resource" 9 | "github.com/hashicorp/terraform/terraform" 10 | "github.com/vishvananda/netlink" 11 | ) 12 | 13 | func TestLXCBridge(t *testing.T) { 14 | var bridge netlink.Link 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | Providers: testAccProviders, 18 | CheckDestroy: testAccCheckLXCBridgeDestroy, 19 | Steps: []resource.TestStep{ 20 | resource.TestStep{ 21 | Config: testAccLXCBridge, 22 | Check: resource.ComposeTestCheckFunc( 23 | testAccCheckLXCBridgeExists( 24 | t, "lxc_bridge.accept_test", &bridge), 25 | resource.TestCheckResourceAttr( 26 | "lxc_bridge.accept_test", "name", "accept_test"), 27 | ), 28 | }, 29 | resource.TestStep{ 30 | Config: testAccLXCBridgeWithIface, 31 | Check: resource.ComposeTestCheckFunc( 32 | testAccCheckLXCBridgeExists( 33 | t, "lxc_bridge.accept_test_iface", &bridge), 34 | resource.TestCheckResourceAttr( 35 | "lxc_bridge.accept_test_iface", "name", "accept_test_iface"), 36 | resource.TestCheckResourceAttr( 37 | "lxc_bridge.accept_test_iface", "hostInterface", "accept_test"), 38 | ), 39 | }, 40 | }, 41 | }) 42 | } 43 | 44 | func testAccCheckLXCBridgeExists(t *testing.T, n string, bridge *netlink.Link) resource.TestCheckFunc { 45 | return func(s *terraform.State) error { 46 | rs, ok := s.RootModule().Resources[n] 47 | if !ok { 48 | return fmt.Errorf("Not found: %v", n) 49 | } 50 | 51 | if rs.Primary.ID == "" { 52 | return fmt.Errorf("No ID is set") 53 | } 54 | 55 | bridgeIndex, err := strconv.Atoi(rs.Primary.ID) 56 | if err != nil { 57 | return fmt.Errorf("Internal error reading resource ID.") 58 | } 59 | br, err := netlink.LinkByIndex(bridgeIndex) 60 | if err != nil { 61 | return fmt.Errorf("Error searching for bridge.") 62 | } else { 63 | *bridge = br 64 | return nil 65 | } 66 | 67 | return fmt.Errorf("Unable to find bridge.") 68 | } 69 | } 70 | 71 | func testAccCheckLXCBridgeDestroy(s *terraform.State) error { 72 | for _, rs := range s.RootModule().Resources { 73 | if rs.Type != "lxc_bridge" { 74 | continue 75 | } 76 | 77 | bridgeIndex, err := strconv.Atoi(rs.Primary.ID) 78 | if err != nil { 79 | return fmt.Errorf("Internal error reading resource ID.") 80 | } 81 | _, err = netlink.LinkByIndex(bridgeIndex) 82 | if err == nil { 83 | return fmt.Errorf("Bridge still exists.") 84 | } 85 | 86 | } 87 | 88 | return nil 89 | } 90 | 91 | var testAccLXCBridge = ` 92 | resource "lxc_bridge" "accept_test" { 93 | name = "accept_test" 94 | }` 95 | 96 | var testAccLXCBridgeWithIface = ` 97 | resource "lxc_bridge" "accept_test" { 98 | name = "accept_test_ip" 99 | hostInterface = "accept_test" 100 | }` 101 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/jtopjian/terraform-provider-lxc", 3 | "GoVersion": "go1.4.2", 4 | "Deps": [ 5 | { 6 | "ImportPath": "github.com/google/shlex", 7 | "Rev": "6f45313302b9c56850fc17f99e40caebce98c716" 8 | }, 9 | { 10 | "ImportPath": "github.com/hashicorp/errwrap", 11 | "Rev": "7554cd9344cec97297fa6649b055a8c98c2a1e55" 12 | }, 13 | { 14 | "ImportPath": "github.com/hashicorp/go-multierror", 15 | "Rev": "fcdddc395df1ddf4247c69bd436e84cfa0733f7e" 16 | }, 17 | { 18 | "ImportPath": "github.com/hashicorp/hcl", 19 | "Rev": "513e04c400ee2e81e97f5e011c08fb42c6f69b84" 20 | }, 21 | { 22 | "ImportPath": "github.com/hashicorp/terraform/config", 23 | "Comment": "v0.5.3-199-gf1a88fc", 24 | "Rev": "f1a88fca5bdaabeda5ef12399da4599d75f11b5b" 25 | }, 26 | { 27 | "ImportPath": "github.com/hashicorp/terraform/dag", 28 | "Comment": "v0.5.3-199-gf1a88fc", 29 | "Rev": "f1a88fca5bdaabeda5ef12399da4599d75f11b5b" 30 | }, 31 | { 32 | "ImportPath": "github.com/hashicorp/terraform/flatmap", 33 | "Comment": "v0.5.3-199-gf1a88fc", 34 | "Rev": "f1a88fca5bdaabeda5ef12399da4599d75f11b5b" 35 | }, 36 | { 37 | "ImportPath": "github.com/hashicorp/terraform/helper/config", 38 | "Comment": "v0.5.3-199-gf1a88fc", 39 | "Rev": "f1a88fca5bdaabeda5ef12399da4599d75f11b5b" 40 | }, 41 | { 42 | "ImportPath": "github.com/hashicorp/terraform/helper/multierror", 43 | "Comment": "v0.5.3-199-gf1a88fc", 44 | "Rev": "f1a88fca5bdaabeda5ef12399da4599d75f11b5b" 45 | }, 46 | { 47 | "ImportPath": "github.com/hashicorp/terraform/helper/resource", 48 | "Comment": "v0.5.3-199-gf1a88fc", 49 | "Rev": "f1a88fca5bdaabeda5ef12399da4599d75f11b5b" 50 | }, 51 | { 52 | "ImportPath": "github.com/hashicorp/terraform/helper/schema", 53 | "Comment": "v0.5.3-199-gf1a88fc", 54 | "Rev": "f1a88fca5bdaabeda5ef12399da4599d75f11b5b" 55 | }, 56 | { 57 | "ImportPath": "github.com/hashicorp/terraform/helper/url", 58 | "Comment": "v0.5.3-199-gf1a88fc", 59 | "Rev": "f1a88fca5bdaabeda5ef12399da4599d75f11b5b" 60 | }, 61 | { 62 | "ImportPath": "github.com/hashicorp/terraform/plugin", 63 | "Comment": "v0.5.3-199-gf1a88fc", 64 | "Rev": "f1a88fca5bdaabeda5ef12399da4599d75f11b5b" 65 | }, 66 | { 67 | "ImportPath": "github.com/hashicorp/terraform/rpc", 68 | "Comment": "v0.5.3-199-gf1a88fc", 69 | "Rev": "f1a88fca5bdaabeda5ef12399da4599d75f11b5b" 70 | }, 71 | { 72 | "ImportPath": "github.com/hashicorp/terraform/terraform", 73 | "Comment": "v0.5.3-199-gf1a88fc", 74 | "Rev": "f1a88fca5bdaabeda5ef12399da4599d75f11b5b" 75 | }, 76 | { 77 | "ImportPath": "github.com/hashicorp/yamux", 78 | "Rev": "b4f943b3f25da97dec8e26bee1c3269019de070d" 79 | }, 80 | { 81 | "ImportPath": "github.com/mitchellh/copystructure", 82 | "Rev": "c101d94abf8cd5c6213c8300d0aed6368f2d6ede" 83 | }, 84 | { 85 | "ImportPath": "github.com/mitchellh/mapstructure", 86 | "Rev": "442e588f213303bec7936deba67901f8fc8f18b1" 87 | }, 88 | { 89 | "ImportPath": "github.com/mitchellh/reflectwalk", 90 | "Rev": "9cdd861463675960a0a0083a7e2023e7b0c994d7" 91 | }, 92 | { 93 | "ImportPath": "github.com/vishvananda/netlink", 94 | "Rev": "1fd3169564abccd94f0e2786160ab294832a498b" 95 | }, 96 | { 97 | "ImportPath": "gopkg.in/lxc/go-lxc.v2", 98 | "Rev": "a0fa4019e64b385dfa2fb8abcabcdd2f66871639" 99 | } 100 | ] 101 | } 102 | -------------------------------------------------------------------------------- /lxc/resource_lxc_bridge.go: -------------------------------------------------------------------------------- 1 | package lxc 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strconv" 7 | 8 | "github.com/hashicorp/terraform/helper/schema" 9 | "github.com/vishvananda/netlink" 10 | ) 11 | 12 | func resourceLXCBridge() *schema.Resource { 13 | return &schema.Resource{ 14 | Create: resourceLXCBridgeCreate, 15 | Read: resourceLXCBridgeRead, 16 | Update: nil, 17 | Delete: resourceLXCBridgeDelete, 18 | 19 | Schema: map[string]*schema.Schema{ 20 | "name": &schema.Schema{ 21 | Type: schema.TypeString, 22 | Required: true, 23 | ForceNew: true, 24 | }, 25 | 26 | "hostInterface": &schema.Schema{ 27 | Type: schema.TypeString, 28 | Optional: true, 29 | ForceNew: true, 30 | }, 31 | 32 | "mac": &schema.Schema{ 33 | Type: schema.TypeString, 34 | Computed: true, 35 | }, 36 | }, 37 | } 38 | } 39 | 40 | func resourceLXCBridgeCreate(d *schema.ResourceData, meta interface{}) error { 41 | br := d.Get("name").(string) 42 | var bridge netlink.Link 43 | 44 | bridge, err := netlink.LinkByName(br) 45 | if err != nil { 46 | bridge = &netlink.Bridge{netlink.LinkAttrs{ 47 | Name: d.Get("name").(string), 48 | }} 49 | if err := netlink.LinkAdd(bridge); err != nil { 50 | return fmt.Errorf("Error creating bridge %s: %v", br, err) 51 | } 52 | 53 | if ifaceName, ok := d.GetOk("hostInterface"); ok { 54 | iface, err := netlink.LinkByName(ifaceName.(string)) 55 | if err != nil { 56 | return fmt.Errorf("Error adding host interface %s to bridge %s : unknow host interface %v", ifaceName, br ,err) 57 | } 58 | 59 | if err := netlink.LinkSetMasterByIndex(iface, bridge.Attrs().Index); err != nil { 60 | return fmt.Errorf("Error adding host interface %s to bridge %s : %v", ifaceName, br, err) 61 | } 62 | } 63 | log.Printf("[INFO] Created new bridge %s: %v", br, bridge) 64 | } else { 65 | log.Printf("[INFO] Found existing bridge %s: %v", br, bridge) 66 | } 67 | 68 | log.Printf("[INFO] Bringing bridge %s up", br) 69 | if err := netlink.LinkSetUp(bridge); err != nil { 70 | return fmt.Errorf("Error bringing bridge %s up: %v", br, err) 71 | } 72 | 73 | d.SetId(strconv.Itoa(bridge.Attrs().Index)) 74 | 75 | return resourceLXCBridgeRead(d, meta) 76 | } 77 | 78 | func resourceLXCBridgeRead(d *schema.ResourceData, meta interface{}) error { 79 | bridgeIndex, err := strconv.Atoi(d.Id()) 80 | if err != nil { 81 | return fmt.Errorf("Internal error reading resource ID: %v", err) 82 | } 83 | 84 | bridge, err := netlink.LinkByIndex(bridgeIndex) 85 | if err != nil { 86 | return fmt.Errorf("Unable to find bridge %v: %v", bridgeIndex, err) 87 | } 88 | 89 | d.Set("mac", bridge.Attrs().HardwareAddr.String()) 90 | 91 | log.Printf("[INFO] Bridge info: %v", bridge) 92 | 93 | return nil 94 | } 95 | 96 | func resourceLXCBridgeDelete(d *schema.ResourceData, meta interface{}) error { 97 | bridgeIndex, err := strconv.Atoi(d.Id()) 98 | if err != nil { 99 | return fmt.Errorf("Internal error reading resource ID: %v", err) 100 | } 101 | 102 | bridge, err := netlink.LinkByIndex(bridgeIndex) 103 | if err != nil { 104 | return fmt.Errorf("Unable to find bridge %v: %v", bridgeIndex, err) 105 | } 106 | 107 | links, err := netlink.LinkList() 108 | if err != nil { 109 | return fmt.Errorf("Error listing interfaces: %v", err) 110 | } 111 | 112 | bridgeEmpty := true 113 | for _, link := range links { 114 | if link.Attrs().MasterIndex == bridge.Attrs().Index { 115 | bridgeEmpty = false 116 | log.Printf("[INFO] Link %s is still attached to bridge %s", link.Attrs().Name, bridge.Attrs().Name) 117 | } 118 | } 119 | 120 | if bridgeEmpty == false { 121 | return fmt.Errorf("Unable to delete bridge %s. Interfaces are still attached to it.", bridge.Attrs().Name) 122 | } else { 123 | if err := netlink.LinkDel(bridge); err != nil { 124 | return fmt.Errorf("Error deleting bridge: %s", err) 125 | } 126 | } 127 | 128 | return nil 129 | } 130 | -------------------------------------------------------------------------------- /lxc/utils.go: -------------------------------------------------------------------------------- 1 | package lxc 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | "github.com/hashicorp/terraform/helper/resource" 12 | "github.com/hashicorp/terraform/helper/schema" 13 | "gopkg.in/lxc/go-lxc.v2" 14 | ) 15 | 16 | func lxcContainerStateRefreshFunc(name, lxcpath string) resource.StateRefreshFunc { 17 | return func() (interface{}, string, error) { 18 | c, err := lxc.NewContainer(name, lxcpath) 19 | if err != nil { 20 | return c, "", err 21 | } 22 | state := c.State() 23 | return c, fmt.Sprintf("%s", state), nil 24 | } 25 | } 26 | 27 | func lxcWaitForState(c *lxc.Container, LXCPath string, pendingStates []string, targetState string) error { 28 | stateConf := &resource.StateChangeConf{ 29 | Pending: pendingStates, 30 | Target: targetState, 31 | Refresh: lxcContainerStateRefreshFunc(c.Name(), LXCPath), 32 | Timeout: 10 * time.Minute, 33 | Delay: 5 * time.Second, 34 | MinTimeout: 1 * time.Second, 35 | } 36 | 37 | _, err := stateConf.WaitForState() 38 | if err != nil { 39 | return fmt.Errorf("Error waiting for container (%s) to change to state (%s): %s", c.Name(), targetState, err) 40 | } 41 | 42 | return nil 43 | } 44 | 45 | func lxcOptions(c *lxc.Container, d *schema.ResourceData, meta interface{}) error { 46 | config := meta.(*Config) 47 | var options []string 48 | optionsFound := false 49 | includeFound := false 50 | configFile := config.LXCPath + "/" + c.Name() + "/config" 51 | customConfigFile := config.LXCPath + "/" + c.Name() + "/config_tf" 52 | includeLine := fmt.Sprintf("lxc.include = %s", customConfigFile) 53 | 54 | networkInterfaces := d.Get("network_interface").([]interface{}) 55 | for _, n := range networkInterfaces { 56 | nic := n.(map[string]interface{}) 57 | options = append(options, fmt.Sprintf("lxc.network.type = %s", nic["type"])) 58 | for k, v := range nic["options"].(map[string]interface{}) { 59 | options = append(options, fmt.Sprintf("lxc.network.%s = %s", k, v.(string))) 60 | } 61 | } 62 | 63 | containerOptions := d.Get("options").(map[string]interface{}) 64 | if containerOptions != nil { 65 | optionsFound = true 66 | for k, v := range containerOptions { 67 | options = append(options, fmt.Sprintf("%s = %s", k, v.(string))) 68 | } 69 | } 70 | 71 | configFileContents, err := ioutil.ReadFile(configFile) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | if optionsFound == true { 77 | lines := strings.Split(string(configFileContents), "\n") 78 | for _, line := range lines { 79 | if line == includeLine { 80 | includeFound = true 81 | } 82 | } 83 | 84 | // if the lxc.include line was not found, add it. 85 | if includeFound == false { 86 | lines = append(lines, includeLine, "\n") 87 | if err := ioutil.WriteFile(configFile, []byte(strings.Join(lines, "\n")), 0640); err != nil { 88 | return err 89 | } 90 | } 91 | 92 | // now rewrite all custom config options 93 | log.Printf("[DEBUG] %v", options) 94 | if err := ioutil.WriteFile(customConfigFile, []byte(strings.Join(options, "\n")), 0640); err != nil { 95 | return err 96 | } 97 | } 98 | 99 | return nil 100 | } 101 | 102 | func lxcCheckBackend(backend string) (lxc.BackendStore, error) { 103 | switch backend { 104 | case "btrfs": 105 | return lxc.Btrfs, nil 106 | case "directory": 107 | return lxc.Directory, nil 108 | case "lvm": 109 | return lxc.LVM, nil 110 | case "zfs": 111 | return lxc.ZFS, nil 112 | case "aufs": 113 | return lxc.Aufs, nil 114 | case "overlayfs": 115 | return lxc.Overlayfs, nil 116 | case "loopback": 117 | return lxc.Loopback, nil 118 | case "best": 119 | return lxc.Best, nil 120 | default: 121 | return 0, fmt.Errorf("Invalid backend. Possible values are: btrfs, directory, lvm, zfs, aufs, overlayfs, loopback, or best.") 122 | } 123 | } 124 | 125 | func lxcIPAddressConfiguration(c *lxc.Container, d *schema.ResourceData) error { 126 | // Loop through all interfaces and see if one is marked as management 127 | managementNIC := "eth0" 128 | i := 0 129 | networkInterfaces := d.Get("network_interface").([]interface{}) 130 | for _, n := range networkInterfaces { 131 | nic := n.(map[string]interface{}) 132 | if nic["management"] == true { 133 | managementNIC = fmt.Sprintf("%s%s", "eth", strconv.Itoa(i)) 134 | } 135 | i++ 136 | } 137 | 138 | // Get the IP addresses of the management NIC 139 | // For now, we'll just use the first returned IP. 140 | d.Set("address_v4", "") 141 | ipv4s, err := c.IPv4Address(managementNIC) 142 | if err == nil { 143 | if len(ipv4s) > 0 { 144 | d.Set("address_v4", ipv4s[0]) 145 | d.SetConnInfo(map[string]string{ 146 | "type": "ssh", 147 | "host": ipv4s[0], 148 | }) 149 | } 150 | } 151 | 152 | d.Set("address_v6", "") 153 | ipv6s, err := c.IPv6Address(managementNIC) 154 | if err == nil { 155 | if len(ipv6s) > 0 { 156 | d.Set("address_v6", ipv6s[0]) 157 | } 158 | } 159 | 160 | return nil 161 | } 162 | -------------------------------------------------------------------------------- /lxc/resource_lxc_clone.go: -------------------------------------------------------------------------------- 1 | package lxc 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/hashicorp/terraform/helper/schema" 9 | "gopkg.in/lxc/go-lxc.v2" 10 | ) 11 | 12 | func resourceLXCClone() *schema.Resource { 13 | return &schema.Resource{ 14 | Create: resourceLXCCloneCreate, 15 | Read: resourceLXCCloneRead, 16 | Update: nil, 17 | Delete: resourceLXCCloneDelete, 18 | 19 | Schema: map[string]*schema.Schema{ 20 | "name": &schema.Schema{ 21 | Type: schema.TypeString, 22 | Required: true, 23 | ForceNew: true, 24 | }, 25 | "backend": &schema.Schema{ 26 | Type: schema.TypeString, 27 | Optional: true, 28 | Default: "directory", 29 | ForceNew: true, 30 | }, 31 | "source": &schema.Schema{ 32 | Type: schema.TypeString, 33 | Required: true, 34 | ForceNew: true, 35 | }, 36 | "keep_mac": &schema.Schema{ 37 | Type: schema.TypeBool, 38 | Optional: true, 39 | Default: false, 40 | ForceNew: true, 41 | }, 42 | "snapshot": &schema.Schema{ 43 | Type: schema.TypeBool, 44 | Optional: true, 45 | Default: false, 46 | ForceNew: true, 47 | }, 48 | "options": &schema.Schema{ 49 | Type: schema.TypeMap, 50 | Optional: true, 51 | Default: nil, 52 | ForceNew: true, 53 | }, 54 | "network_interface": &schema.Schema{ 55 | Type: schema.TypeList, 56 | Optional: true, 57 | ForceNew: true, 58 | Elem: &schema.Resource{ 59 | Schema: map[string]*schema.Schema{ 60 | "type": &schema.Schema{ 61 | Type: schema.TypeString, 62 | Optional: true, 63 | Default: "veth", 64 | }, 65 | "management": &schema.Schema{ 66 | Type: schema.TypeString, 67 | Optional: true, 68 | Default: false, 69 | }, 70 | "options": &schema.Schema{ 71 | Type: schema.TypeMap, 72 | Optional: true, 73 | Default: nil, 74 | }, 75 | }, 76 | }, 77 | }, 78 | 79 | // exported 80 | "address_v4": &schema.Schema{ 81 | Type: schema.TypeString, 82 | Computed: true, 83 | }, 84 | "address_v6": &schema.Schema{ 85 | Type: schema.TypeString, 86 | Computed: true, 87 | }, 88 | }, 89 | } 90 | } 91 | 92 | func resourceLXCCloneCreate(d *schema.ResourceData, meta interface{}) error { 93 | var c *lxc.Container 94 | config := meta.(*Config) 95 | 96 | backendType, err := lxcCheckBackend(d.Get("backend").(string)) 97 | if err != nil { 98 | return err 99 | } 100 | 101 | name := d.Get("name").(string) 102 | source := d.Get("source").(string) 103 | 104 | c, err = lxc.NewContainer(name, config.LXCPath) 105 | if err != nil { 106 | return err 107 | } 108 | 109 | cl, err := lxc.NewContainer(source, config.LXCPath) 110 | if err != nil { 111 | return err 112 | } 113 | 114 | // the source container must be stopped 115 | log.Printf("[INFO] Stopping %s", source) 116 | if cl.State() == lxc.RUNNING { 117 | if err := cl.Stop(); err != nil { 118 | return err 119 | } 120 | if err := lxcWaitForState(c, config.LXCPath, []string{"RUNNING", "STOPPING"}, "STOPPED"); err != nil { 121 | return err 122 | } 123 | 124 | } 125 | 126 | log.Printf("[INFO] Cloning %s as %s", source, name) 127 | err = cl.Clone(name, lxc.CloneOptions{ 128 | Backend: backendType, 129 | ConfigPath: config.LXCPath, 130 | KeepMAC: d.Get("keep_mac").(bool), 131 | Snapshot: d.Get("snapshot").(bool), 132 | }) 133 | if err != nil { 134 | return err 135 | } 136 | 137 | d.SetId(c.Name()) 138 | 139 | if err := lxcOptions(c, d, config); err != nil { 140 | return err 141 | } 142 | 143 | // causes lxc to re-read the config file 144 | c, err = lxc.NewContainer(name, config.LXCPath) 145 | if err != nil { 146 | return err 147 | } 148 | 149 | log.Printf("[INFO] Starting container %s\n", c.Name()) 150 | if err := c.Start(); err != nil { 151 | return fmt.Errorf("Unable to start container: %s", err) 152 | } 153 | 154 | if err := lxcWaitForState(c, config.LXCPath, []string{"STOPPED", "STARTING"}, "RUNNING"); err != nil { 155 | return err 156 | } 157 | 158 | log.Printf("[INFO] Waiting container to startup networking...\n") 159 | c.WaitIPAddresses(5 * time.Second) 160 | 161 | return resourceLXCCloneRead(d, meta) 162 | } 163 | 164 | func resourceLXCCloneRead(d *schema.ResourceData, meta interface{}) error { 165 | config := meta.(*Config) 166 | 167 | c, err := lxc.NewContainer(d.Id(), config.LXCPath) 168 | if err != nil { 169 | return err 170 | } 171 | 172 | if err = lxcIPAddressConfiguration(c, d); err != nil { 173 | return err 174 | } 175 | 176 | return nil 177 | } 178 | 179 | func resourceLXCCloneDelete(d *schema.ResourceData, meta interface{}) error { 180 | config := meta.(*Config) 181 | c, err := lxc.NewContainer(d.Id(), config.LXCPath) 182 | if err != nil { 183 | return err 184 | } 185 | 186 | if c.State() == lxc.RUNNING { 187 | if err := c.Stop(); err != nil { 188 | return err 189 | } 190 | 191 | if err := lxcWaitForState(c, config.LXCPath, []string{"RUNNING", "STOPPING"}, "STOPPED"); err != nil { 192 | return err 193 | } 194 | } 195 | 196 | if err := c.Destroy(); err != nil { 197 | return err 198 | } 199 | 200 | return nil 201 | } 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # terraform-provider-lxc 2 | 3 | LXC provider plugin for Terraform. 4 | 5 | ## Installation 6 | 7 | ### Requirements 8 | 9 | 1. [Terraform](http://terraform.io). Make sure you have it installed and it's accessible from your `$PATH`. 10 | 2. LXC 11 | 12 | ### From Source (only method right now) 13 | 14 | * Install the `lxc-dev` package appropriate for your distribution. 15 | * Install Go and [configure](https://golang.org/doc/code.html) your workspace. 16 | * Install `godep`: 17 | 18 | ```shell 19 | $ go get github.com/tools/godep 20 | ``` 21 | 22 | * Download this repo: 23 | 24 | ```shell 25 | $ go get github.com/jtopjian/terraform-provider-lxc 26 | ``` 27 | 28 | * Install the dependencies: 29 | 30 | ```shell 31 | $ cd $GOPATH/src/github.com/jtopjian/terraform-provider-lxc 32 | $ godep restore 33 | ``` 34 | 35 | * Compile it: 36 | 37 | ```shell 38 | $ go build -o terraform-provider-lxc 39 | ``` 40 | 41 | * Copy it to a directory: 42 | 43 | ```shell 44 | $ sudo cp terraform-provider-lxc ~/lxc-demo 45 | ``` 46 | 47 | ## Usage 48 | 49 | Here's a simple Terraform file to get you started: 50 | 51 | ```ruby 52 | provider "lxc" {} 53 | 54 | resource "lxc_container" "ubuntu" { 55 | name = "ubuntu" 56 | } 57 | 58 | resource "lxc_clone" "ubuntu_clone" { 59 | name = "ubuntu_clone" 60 | source = "${lxc_container.ubuntu.name}" 61 | } 62 | ``` 63 | 64 | Here's a more complete example that does the following: 65 | 66 | * Creates a new bridge called `my_bridge`. 67 | * Creates an Ubuntu container with two interfaces: one on the default `lxcbr0` and one on `my_bridge`. 68 | * Creates an Ubuntu container with one interface on the `my_bridge` bridge. 69 | 70 | ```ruby 71 | provider "lxc" {} 72 | 73 | resource "lxc_bridge" "my_bridge" { 74 | name = "my_bridge" 75 | } 76 | 77 | resource "lxc_container" "ubuntu" { 78 | name = "ubuntu" 79 | template_name = "ubuntu" 80 | template_release = "trusty" 81 | template_arch = "amd64" 82 | template_extra_args = ["--auth-key", "/root/.ssh/id_rsa.pub"] 83 | network_interface { 84 | type = "veth" 85 | options { 86 | link = "lxcbr0" 87 | flags = "up" 88 | hwaddr = "00:16:3e:xx:xx:xx" 89 | } 90 | } 91 | network_interface { 92 | type = "veth" 93 | options { 94 | link = "${lxc_bridge.my_bridge.name}" 95 | flags = "up" 96 | hwaddr = "00:16:3e:xx:xx:xx" 97 | veth.pair = "foobar" 98 | ipv4 = "192.168.255.1/24" 99 | } 100 | } 101 | } 102 | 103 | resource "lxc_container" "ubuntu2" { 104 | name = "ubuntu2" 105 | template_name = "ubuntu" 106 | template_release = "trusty" 107 | template_arch = "amd64" 108 | template_extra_args = ["--auth-key", "/root/.ssh/id_rsa.pub"] 109 | network_interface { 110 | type = "veth" 111 | options { 112 | link = "${lxc_bridge.my_bridge.name}" 113 | flags = "up" 114 | hwaddr = "00:16:3e:xx:xx:xx" 115 | veth.pair = "barfoo" 116 | ipv4 = "192.168.255.2/24" 117 | } 118 | } 119 | } 120 | ``` 121 | 122 | For either example, save it to a `.tf` file and run: 123 | 124 | ```shell 125 | $ terraform plan 126 | $ terraform apply 127 | $ terraform show 128 | ``` 129 | 130 | ## Reference 131 | 132 | ### Provider 133 | 134 | #### Example 135 | 136 | ```ruby 137 | provider "lxc" { 138 | lxc_path = "/var/lib/lxc" 139 | } 140 | ``` 141 | 142 | #### Parameters 143 | 144 | * `lxc_path`: Optional. Explicitly set the path to where containers will be built. 145 | 146 | ### lxc_bridge 147 | 148 | #### Example 149 | 150 | ```ruby 151 | resource "lxc_bridge" "my_bridge" { 152 | name = "my_bridge" 153 | } 154 | ``` 155 | 156 | #### Parameters 157 | 158 | * `name`: Required. The name of the bridge. 159 | 160 | #### Exported Parameters 161 | 162 | * `mac`: The MAC address of the new bridge. 163 | 164 | ### lxc_container 165 | 166 | #### Example 167 | 168 | ```ruby 169 | resource "lxc_container" "my_container" { 170 | name = "my_container" 171 | backend = "zfs" 172 | network_interface { 173 | type = "veth" 174 | options { 175 | link = "lxcbr0" 176 | flags = "up" 177 | hwaddr = "00:16:3e:xx:xx:xx" 178 | } 179 | } 180 | } 181 | ``` 182 | 183 | #### Parameters 184 | 185 | * `name`: Required. The name of the container. 186 | * `backend`: Optional. The storage backend to use. Valid options are: btrfs, directory, lvm, zfs, aufs, overlayfs, loopback, or best. Defaults to `directory`. 187 | * `exec`: Optional. Commands to run after container creation. This won't be interpreted by a shell so use `bash -c "{shellcode}"` if you want a shell. 188 | * `template_name`: Optional. Defaults to `download`. See `/usr/share/lxc/templates` for more template options. 189 | * `template_distro`: Optional. Defaults to `ubuntu`. 190 | * `template_release`: Optional. Defaults to `trusty`. 191 | * `template_arch`: Optional. Defaults to `amd64`. 192 | * `template_variant`: Optional. Defaults to `default`. 193 | * `template_server`: Optional. Defaults to `images.linuxcontainers.org`. 194 | * `template_key_id`: Optional. 195 | * `template_key_server`: Optional. 196 | * `template_flush_cache`: Optional. Defaults to `false`. 197 | * `template_force_cache`: Optional. Defaults to `false`. 198 | * `template_disable_gpg_validation`: Optional. defaults to `false`. 199 | * `template_extra_args`: Optional. A list of extra parameters to pass to the template. 200 | * `options`: Optional. A set of key/value pairs of extra LXC options. See `lxc.container.conf(5)`. 201 | * `network_interface`: Optional. Defines a NIC. 202 | * `type`: Optional. The type of NIC. Defaults to `veth`. 203 | * `management`: Optional. Make this NIC the management / accessible NIC. 204 | * `options`: Optional. A set of key/value `lxc.network.*` pairs for the NIC. 205 | 206 | #### Notes 207 | 208 | Because `lxc.network.type` _must_ be the first line that denotes a new NIC, a separate `network_interface` parameter is used rather than bundling it all into `options` 209 | 210 | #### Exported Parameters 211 | 212 | * `address_v4`: The first discovered IPv4 address of the container. 213 | * `address_v6`: The first discovered IPv6 address of the container. 214 | 215 | ### lxc_clone 216 | 217 | #### Example 218 | 219 | ```ruby 220 | resource "lxc_clone" "my_clone" { 221 | name = "my_clone" 222 | source = "my_container" 223 | backend = "zfs" 224 | network_interface { 225 | type = "veth" 226 | options { 227 | link = "lxcbr0" 228 | flags = "up" 229 | hwaddr = "00:16:3e:xx:xx:xx" 230 | } 231 | } 232 | } 233 | ``` 234 | 235 | #### Parameters 236 | 237 | * `name`: Required. The name of the container. 238 | * `source`: Required. The source of this clone. 239 | * `backend`: Optional. The storage backend to use. Valid options are: btrfs, directory, lvm, zfs, aufs, overlayfs, loopback, or best. Defaults to `directory`. 240 | * `keep_mac`: Optional. Keep the MAC address(es) of the source. Defaults to `false`. 241 | * `snapshot`: Optional. Whether to clone as a snapshot instead of copy. Defaults to `false`. 242 | * `options`: Optional. A set of key/value pairs of extra LXC options. See `lxc.container.conf(5)`. 243 | * `network_interface`: Optional. Defines a NIC. 244 | * `type`: Optional. The type of NIC. Defaults to `veth`. 245 | * `management`: Optional. Make this NIC the management / accessible NIC. 246 | * `options`: Optional. A set of key/value `lxc.network.*` pairs for the NIC. 247 | 248 | #### Exported Parameters 249 | 250 | * `address_v4`: The first discovered IPv4 address of the container. 251 | * `address_v6`: The first discovered IPv6 address of the container. 252 | -------------------------------------------------------------------------------- /lxc/resource_lxc_container.go: -------------------------------------------------------------------------------- 1 | package lxc 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/google/shlex" 9 | "github.com/hashicorp/terraform/helper/schema" 10 | "gopkg.in/lxc/go-lxc.v2" 11 | ) 12 | 13 | func resourceLXCContainer() *schema.Resource { 14 | return &schema.Resource{ 15 | Create: resourceLXCContainerCreate, 16 | Read: resourceLXCContainerRead, 17 | Update: nil, 18 | Delete: resourceLXCContainerDelete, 19 | 20 | Schema: map[string]*schema.Schema{ 21 | "name": &schema.Schema{ 22 | Type: schema.TypeString, 23 | Required: true, 24 | ForceNew: true, 25 | }, 26 | "backend": &schema.Schema{ 27 | Type: schema.TypeString, 28 | Optional: true, 29 | Default: "directory", 30 | ForceNew: true, 31 | }, 32 | "template_name": &schema.Schema{ 33 | Type: schema.TypeString, 34 | Optional: true, 35 | Default: "download", 36 | ForceNew: true, 37 | }, 38 | "template_distro": &schema.Schema{ 39 | Type: schema.TypeString, 40 | Optional: true, 41 | Default: "ubuntu", 42 | ForceNew: true, 43 | }, 44 | "template_release": &schema.Schema{ 45 | Type: schema.TypeString, 46 | Optional: true, 47 | Default: "trusty", 48 | ForceNew: true, 49 | }, 50 | "template_arch": &schema.Schema{ 51 | Type: schema.TypeString, 52 | Optional: true, 53 | Default: "amd64", 54 | ForceNew: true, 55 | }, 56 | "template_variant": &schema.Schema{ 57 | Type: schema.TypeString, 58 | Optional: true, 59 | Default: "default", 60 | ForceNew: true, 61 | }, 62 | "template_server": &schema.Schema{ 63 | Type: schema.TypeString, 64 | Optional: true, 65 | Default: "images.linuxcontainers.org", 66 | ForceNew: true, 67 | }, 68 | "template_key_id": &schema.Schema{ 69 | Type: schema.TypeString, 70 | Optional: true, 71 | ForceNew: true, 72 | }, 73 | "template_key_server": &schema.Schema{ 74 | Type: schema.TypeString, 75 | Optional: true, 76 | ForceNew: true, 77 | }, 78 | "template_flush_cache": &schema.Schema{ 79 | Type: schema.TypeBool, 80 | Optional: true, 81 | Default: false, 82 | ForceNew: true, 83 | }, 84 | "template_force_cache": &schema.Schema{ 85 | Type: schema.TypeBool, 86 | Optional: true, 87 | Default: false, 88 | ForceNew: true, 89 | }, 90 | "template_disable_gpg_validation": &schema.Schema{ 91 | Type: schema.TypeBool, 92 | Optional: true, 93 | Default: false, 94 | ForceNew: true, 95 | }, 96 | "template_extra_args": &schema.Schema{ 97 | Type: schema.TypeList, 98 | Optional: true, 99 | Elem: &schema.Schema{Type: schema.TypeString}, 100 | ForceNew: true, 101 | }, 102 | "options": &schema.Schema{ 103 | Type: schema.TypeMap, 104 | Optional: true, 105 | Default: nil, 106 | ForceNew: true, 107 | }, 108 | "network_interface": &schema.Schema{ 109 | Type: schema.TypeList, 110 | Optional: true, 111 | ForceNew: true, 112 | Elem: &schema.Resource{ 113 | Schema: map[string]*schema.Schema{ 114 | "type": &schema.Schema{ 115 | Type: schema.TypeString, 116 | Optional: true, 117 | Default: "veth", 118 | }, 119 | "management": &schema.Schema{ 120 | Type: schema.TypeString, 121 | Optional: true, 122 | Default: false, 123 | }, 124 | "options": &schema.Schema{ 125 | Type: schema.TypeMap, 126 | Optional: true, 127 | Default: nil, 128 | }, 129 | }, 130 | }, 131 | }, 132 | 133 | // exported 134 | "address_v4": &schema.Schema{ 135 | Type: schema.TypeString, 136 | Computed: true, 137 | }, 138 | "address_v6": &schema.Schema{ 139 | Type: schema.TypeString, 140 | Computed: true, 141 | }, 142 | "exec": &schema.Schema{ 143 | Type: schema.TypeList, 144 | Optional: true, 145 | Elem: &schema.Schema{Type: schema.TypeString}, 146 | ForceNew: true, 147 | }, 148 | }, 149 | } 150 | } 151 | 152 | func resourceLXCContainerCreate(d *schema.ResourceData, meta interface{}) error { 153 | var c *lxc.Container 154 | config := meta.(*Config) 155 | 156 | backendType, err := lxcCheckBackend(d.Get("backend").(string)) 157 | if err != nil { 158 | return err 159 | } 160 | 161 | name := d.Get("name").(string) 162 | c, err = lxc.NewContainer(name, config.LXCPath) 163 | if err != nil { 164 | return err 165 | } 166 | 167 | log.Printf("[INFO] Attempting to create container %s\n", c.Name()) 168 | 169 | var ea []string 170 | for _, v := range d.Get("template_extra_args").([]interface{}) { 171 | ea = append(ea, v.(string)) 172 | } 173 | 174 | var options lxc.TemplateOptions 175 | templateName := d.Get("template_name").(string) 176 | if templateName == "download" { 177 | options = lxc.TemplateOptions{ 178 | Backend: backendType, 179 | Template: d.Get("template_name").(string), 180 | Distro: d.Get("template_distro").(string), 181 | Release: d.Get("template_release").(string), 182 | Arch: d.Get("template_arch").(string), 183 | Variant: d.Get("template_variant").(string), 184 | Server: d.Get("template_server").(string), 185 | KeyID: d.Get("template_key_id").(string), 186 | KeyServer: d.Get("template_key_server").(string), 187 | FlushCache: d.Get("template_flush_cache").(bool), 188 | ForceCache: d.Get("template_force_cache").(bool), 189 | DisableGPGValidation: d.Get("template_disable_gpg_validation").(bool), 190 | ExtraArgs: ea, 191 | } 192 | } else { 193 | options = lxc.TemplateOptions{ 194 | Backend: backendType, 195 | Template: d.Get("template_name").(string), 196 | Release: d.Get("template_release").(string), 197 | Arch: d.Get("template_arch").(string), 198 | FlushCache: d.Get("template_flush_cache").(bool), 199 | ExtraArgs: ea, 200 | } 201 | } 202 | 203 | if err := c.Create(options); err != nil { 204 | return err 205 | } 206 | 207 | d.SetId(c.Name()) 208 | 209 | if err := lxcOptions(c, d, config); err != nil { 210 | return err 211 | } 212 | 213 | // causes lxc to re-read the config file 214 | c, err = lxc.NewContainer(name, config.LXCPath) 215 | if err != nil { 216 | return err 217 | } 218 | 219 | log.Printf("[INFO] Starting container %s\n", c.Name()) 220 | if err := c.Start(); err != nil { 221 | return fmt.Errorf("Unable to start container: %s", err) 222 | } 223 | 224 | if err := lxcWaitForState(c, config.LXCPath, []string{"STOPPED", "STARTING"}, "RUNNING"); err != nil { 225 | return err 226 | } 227 | 228 | if commands, defined := d.GetOk("exec"); defined { 229 | if defined { 230 | for _, command := range commands.([]interface{}) { 231 | args, err := shlex.Split(command.(string)) 232 | if( err != nil ){ 233 | log.Printf("[ERROR] Error parsing arguments for command %d, skipping to next command",command.(string)) 234 | }else{ 235 | log.Printf("[INFO] Running command in container %s : %s\n", c.Name(), command.(string)) 236 | c.RunCommand(args,lxc.DefaultAttachOptions) 237 | } 238 | } 239 | } 240 | } 241 | 242 | log.Printf("[INFO] Waiting container to startup networking...\n") 243 | c.WaitIPAddresses(5 * time.Second) 244 | 245 | return resourceLXCContainerRead(d, meta) 246 | } 247 | 248 | func resourceLXCContainerRead(d *schema.ResourceData, meta interface{}) error { 249 | config := meta.(*Config) 250 | 251 | c, err := lxc.NewContainer(d.Id(), config.LXCPath) 252 | if err != nil { 253 | return err 254 | } 255 | 256 | if err = lxcIPAddressConfiguration(c, d); err != nil { 257 | return err 258 | } 259 | 260 | return nil 261 | } 262 | 263 | func resourceLXCContainerDelete(d *schema.ResourceData, meta interface{}) error { 264 | config := meta.(*Config) 265 | c, err := lxc.NewContainer(d.Id(), config.LXCPath) 266 | if err != nil { 267 | return err 268 | } 269 | 270 | if c.State() == lxc.RUNNING { 271 | if err := c.Stop(); err != nil { 272 | return err 273 | } 274 | 275 | if err := lxcWaitForState(c, config.LXCPath, []string{"RUNNING", "STOPPING"}, "STOPPED"); err != nil { 276 | return err 277 | } 278 | } 279 | 280 | if err := c.Destroy(); err != nil { 281 | return err 282 | } 283 | 284 | return nil 285 | } 286 | --------------------------------------------------------------------------------