From 94b54c2a30cd2b6dfc1deabdba66c32208c3f4bc Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Sat, 2 Jan 2016 12:20:55 +0100 Subject: [PATCH 1/4] Add support of custom networks in docker --- provider.go | 1 + resource_docker_network.go | 135 +++++++++++++++++++++++++++++++ resource_docker_network_funcs.go | 115 ++++++++++++++++++++++++++ resource_docker_network_test.go | 65 +++++++++++++++ 4 files changed, 316 insertions(+) create mode 100644 resource_docker_network.go create mode 100644 resource_docker_network_funcs.go create mode 100644 resource_docker_network_test.go diff --git a/provider.go b/provider.go index fdc8b771..799fd9bd 100644 --- a/provider.go +++ b/provider.go @@ -28,6 +28,7 @@ func Provider() terraform.ResourceProvider { ResourcesMap: map[string]*schema.Resource{ "docker_container": resourceDockerContainer(), "docker_image": resourceDockerImage(), + "docker_network": resourceDockerNetwork(), }, ConfigureFunc: providerConfigure, diff --git a/resource_docker_network.go b/resource_docker_network.go new file mode 100644 index 00000000..4c14b2de --- /dev/null +++ b/resource_docker_network.go @@ -0,0 +1,135 @@ +package docker + +import ( + "bytes" + "fmt" + "sort" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceDockerNetwork() *schema.Resource { + return &schema.Resource{ + Create: resourceDockerNetworkCreate, + Read: resourceDockerNetworkRead, + Delete: resourceDockerNetworkDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "check_duplicate": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "driver": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "options": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "ipam_driver": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "ipam_config": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: getIpamConfigElem(), + Set: resourceDockerIpamConfigHash, + }, + + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "scope": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func getIpamConfigElem() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "subnet": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "ip_range": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "gateway": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "aux_address": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + }, + } +} + +func resourceDockerIpamConfigHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + + if v, ok := m["subnet"]; ok { + buf.WriteString(fmt.Sprintf("%v-", v.(string))) + } + + if v, ok := m["ip_range"]; ok { + buf.WriteString(fmt.Sprintf("%v-", v.(string))) + } + + if v, ok := m["gateway"]; ok { + buf.WriteString(fmt.Sprintf("%v-", v.(string))) + } + + if v, ok := m["aux_address"]; ok { + auxAddress := v.(map[string]interface{}) + + keys := make([]string, len(auxAddress)) + i := 0 + for k, _ := range auxAddress { + keys[i] = k + i++ + } + sort.Strings(keys) + + for _, k := range keys { + buf.WriteString(fmt.Sprintf("%v-%v-", k, auxAddress[k].(string))) + } + } + + return hashcode.String(buf.String()) +} diff --git a/resource_docker_network_funcs.go b/resource_docker_network_funcs.go new file mode 100644 index 00000000..61954f4a --- /dev/null +++ b/resource_docker_network_funcs.go @@ -0,0 +1,115 @@ +package docker + +import ( + "fmt" + + dc "github.com/fsouza/go-dockerclient" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceDockerNetworkCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*dc.Client) + + createOpts := dc.CreateNetworkOptions{ + Name: d.Get("name").(string), + } + if v, ok := d.GetOk("check_duplicate"); ok { + createOpts.CheckDuplicate = v.(bool) + } + if v, ok := d.GetOk("driver"); ok { + createOpts.Driver = v.(string) + } + if v, ok := d.GetOk("options"); ok { + createOpts.Options = v.(map[string]interface{}) + } + + ipamOpts := dc.IPAMOptions{} + ipamOptsSet := false + if v, ok := d.GetOk("ipam_driver"); ok { + ipamOpts.Driver = v.(string) + ipamOptsSet = true + } + if v, ok := d.GetOk("ipam_config"); ok { + ipamOpts.Config = ipamConfigSetToIpamConfigs(v.(*schema.Set)) + ipamOptsSet = true + } + + if ipamOptsSet { + createOpts.IPAM = ipamOpts + } + + var err error + var retNetwork *dc.Network + if retNetwork, err = client.CreateNetwork(createOpts); err != nil { + return fmt.Errorf("Unable to create network: %s", err) + } + if retNetwork == nil { + return fmt.Errorf("Returned network is nil") + } + + d.SetId(retNetwork.ID) + d.Set("name", retNetwork.Name) + d.Set("scope", retNetwork.Scope) + d.Set("driver", retNetwork.Driver) + d.Set("options", retNetwork.Options) + + return nil +} + +func resourceDockerNetworkRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*dc.Client) + + var err error + var retNetwork *dc.Network + if retNetwork, err = client.NetworkInfo(d.Id()); err != nil { + if _, ok := err.(*dc.NoSuchNetwork); !ok { + return fmt.Errorf("Unable to inspect network: %s", err) + } + } + if retNetwork == nil { + d.SetId("") + return nil + } + + d.Set("scope", retNetwork.Scope) + d.Set("driver", retNetwork.Driver) + d.Set("options", retNetwork.Options) + + return nil +} + +func resourceDockerNetworkDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*dc.Client) + + if err := client.RemoveNetwork(d.Id()); err != nil { + if _, ok := err.(*dc.NoSuchNetwork); !ok { + return fmt.Errorf("Error deleting network %s: %s", d.Id(), err) + } + } + + d.SetId("") + return nil +} + +func ipamConfigSetToIpamConfigs(ipamConfigSet *schema.Set) []dc.IPAMConfig { + ipamConfigs := make([]dc.IPAMConfig, ipamConfigSet.Len()) + + for i, ipamConfigInt := range ipamConfigSet.List() { + ipamConfigRaw := ipamConfigInt.(map[string]interface{}) + + ipamConfig := dc.IPAMConfig{} + ipamConfig.Subnet = ipamConfigRaw["subnet"].(string) + ipamConfig.IPRange = ipamConfigRaw["ip_range"].(string) + ipamConfig.Gateway = ipamConfigRaw["gateway"].(string) + + auxAddressRaw := ipamConfigRaw["aux_address"].(map[string]interface{}) + ipamConfig.AuxAddress = make(map[string]string, len(auxAddressRaw)) + for k, v := range auxAddressRaw { + ipamConfig.AuxAddress[k] = v.(string) + } + + ipamConfigs[i] = ipamConfig + } + + return ipamConfigs +} diff --git a/resource_docker_network_test.go b/resource_docker_network_test.go new file mode 100644 index 00000000..6e3bb4e3 --- /dev/null +++ b/resource_docker_network_test.go @@ -0,0 +1,65 @@ +package docker + +import ( + "fmt" + "testing" + + dc "github.com/fsouza/go-dockerclient" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDockerNetwork_basic(t *testing.T) { + var n dc.Network + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDockerNetworkConfig, + Check: resource.ComposeTestCheckFunc( + testAccNetwork("docker_network.foo", &n), + ), + }, + }, + }) +} + +func testAccNetwork(n string, network *dc.Network) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + client := testAccProvider.Meta().(*dc.Client) + networks, err := client.ListNetworks() + if err != nil { + return err + } + + for _, n := range networks { + if n.ID == rs.Primary.ID { + inspected, err := client.NetworkInfo(n.ID) + if err != nil { + return fmt.Errorf("Network could not be obtained: %s", err) + } + *network = *inspected + return nil + } + } + + return fmt.Errorf("Network not found: %s", rs.Primary.ID) + } +} + +const testAccDockerNetworkConfig = ` +resource "docker_network" "foo" { + name = "bar" +} +` From 99f83853f75337d75eebd413ca5acce8c8a40345 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 4 Jan 2016 20:58:54 +0100 Subject: [PATCH 2/4] Add the networks entry --- resource_docker_container.go | 6 ++++++ resource_docker_container_funcs.go | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/resource_docker_container.go b/resource_docker_container.go index 32385049..f20ff43f 100644 --- a/resource_docker_container.go +++ b/resource_docker_container.go @@ -238,6 +238,12 @@ func resourceDockerContainer() *schema.Resource { Optional: true, ForceNew: true, }, + + "networks": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + }, }, } } diff --git a/resource_docker_container_funcs.go b/resource_docker_container_funcs.go index 814941bb..605db710 100644 --- a/resource_docker_container_funcs.go +++ b/resource_docker_container_funcs.go @@ -148,6 +148,14 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err d.SetId(retContainer.ID) + if v, ok := d.GetOk("networks"); ok { + connectionOpts := &dc.NetworkConnectionOptions{Container: retContainer.ID} + + for _, network := range v.(*schema.Set).List() { + client.ConnectNetwork(network.(string), connectionOpts) + } + } + creationTime = time.Now() if err := client.StartContainer(retContainer.ID, hostConfig); err != nil { return fmt.Errorf("Unable to start container: %s", err) From c823565d5d79f7348a3bdd00e3e46832d59f7f2a Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Mon, 4 Jan 2016 21:03:53 +0100 Subject: [PATCH 3/4] Fix typo --- resource_docker_container_funcs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resource_docker_container_funcs.go b/resource_docker_container_funcs.go index 605db710..08cfe190 100644 --- a/resource_docker_container_funcs.go +++ b/resource_docker_container_funcs.go @@ -149,7 +149,7 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err d.SetId(retContainer.ID) if v, ok := d.GetOk("networks"); ok { - connectionOpts := &dc.NetworkConnectionOptions{Container: retContainer.ID} + connectionOpts := dc.NetworkConnectionOptions{Container: retContainer.ID} for _, network := range v.(*schema.Set).List() { client.ConnectNetwork(network.(string), connectionOpts) From bca87ec0ffb42cb187031b9b48b11393e9816b39 Mon Sep 17 00:00:00 2001 From: Colin Hebert Date: Tue, 5 Jan 2016 03:46:24 +0100 Subject: [PATCH 4/4] Add Elem and Set to the network set --- resource_docker_container.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resource_docker_container.go b/resource_docker_container.go index f20ff43f..6e5bc07d 100644 --- a/resource_docker_container.go +++ b/resource_docker_container.go @@ -243,6 +243,8 @@ func resourceDockerContainer() *schema.Resource { Type: schema.TypeSet, Optional: true, ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: stringSetHash, }, }, }