Skip to content
This repository has been archived by the owner on Dec 11, 2020. It is now read-only.

Container network fixes #104

Merged
merged 13 commits into from
Oct 25, 2018
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
## 1.1.0 (Unreleased)

IMPROVEMENTS
* Adds labels for `network`, `volume` and `secret` to support docker stacks. [[92](https://github.com/terraform-providers/terraform-provider-docker/pull/92)]
* Adds labels for `network`, `volume` and `secret` to support docker stacks. [[#92](https://github.com/terraform-providers/terraform-provider-docker/pull/92)]

BUG FIXES
* Fixes that new network were appended to the default bridge [GH-10]
* Fixes that container resource returns a non-existent IP address [GH-36]
* Fixes container's ip_address is empty when using custom network [GH-9] and [[#50](https://github.com/terraform-providers/terraform-provider-docker/pull/50)]
* Fixes terraform destroy failing to remove a bridge network [GH-98] and [[#50](https://github.com/terraform-providers/terraform-provider-docker/pull/50)]


## 1.0.4 (October 17, 2018)

BUG FIXES
* Support and fix for random external ports for containers [[#102](https://github.com/terraform-providers/terraform-provider-docker/issues/102)] and ([103](https://github.com/terraform-providers/terraform-provider-docker/pull/103))
* Support and fix for random external ports for containers [[#102](https://github.com/terraform-providers/terraform-provider-docker/issues/102)] and ([#103](https://github.com/terraform-providers/terraform-provider-docker/pull/103))

## 1.0.3 (October 12, 2018)

Expand Down
47 changes: 37 additions & 10 deletions docker/resource_docker_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,25 +291,53 @@ func resourceDockerContainer() *schema.Resource {
},

"ip_address": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Type: schema.TypeString,
Computed: true,
Deprecated: "Use ip_adresses_data instead. This field exposes the data of the container's first network.",
},

"ip_prefix_length": &schema.Schema{
Type: schema.TypeInt,
Computed: true,
Type: schema.TypeInt,
Computed: true,
Deprecated: "Use ip_prefix_length from ip_adresses_data instead. This field exposes the data of the container's first network.",
},

"gateway": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Type: schema.TypeString,
Computed: true,
Deprecated: "Use gateway from ip_adresses_data instead. This field exposes the data of the container's first network.",
},

"bridge": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},

"network_data": &schema.Schema{
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"network_name": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"ip_address": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"ip_prefix_length": &schema.Schema{
Type: schema.TypeInt,
Computed: true,
},
"gateway": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
},
},

"privileged": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Expand Down Expand Up @@ -398,10 +426,9 @@ func resourceDockerContainer() *schema.Resource {
},

"network_mode": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validateStringMatchesPattern(`^(bridge|host|none|container:.+|service:.+)$`),
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},

"networks": &schema.Schema{
Expand Down
47 changes: 45 additions & 2 deletions docker/resource_docker_container_funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,9 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
}

if err := client.NetworkDisconnect(context.Background(), "bridge", retContainer.ID, false); err != nil {
return fmt.Errorf("Unable to disconnect the default network: %s", err)
if !strings.Contains(err.Error(), "is not connected to the network bridge") {
return fmt.Errorf("Unable to disconnect the default network: %s", err)
}
}

for _, rawNetwork := range v.(*schema.Set).List() {
Expand Down Expand Up @@ -300,7 +302,7 @@ func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error
}

jsonObj, _ := json.MarshalIndent(container, "", "\t")
log.Printf("[DEBUG] Docker container inspect: %s", jsonObj)
log.Printf("[INFO] Docker container inspect: %s", jsonObj)

if container.State.Running ||
!container.State.Running && !d.Get("must_run").(bool) {
Expand Down Expand Up @@ -333,13 +335,27 @@ func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error

// Read Network Settings
if container.NetworkSettings != nil {
// TODO remove deprecated attributes in next major
d.Set("ip_address", container.NetworkSettings.IPAddress)
d.Set("ip_prefix_length", container.NetworkSettings.IPPrefixLen)
d.Set("gateway", container.NetworkSettings.Gateway)
if container.NetworkSettings != nil && len(container.NetworkSettings.Networks) > 0 {
// Still support deprecated outputs
for _, settings := range container.NetworkSettings.Networks {
d.Set("ip_address", settings.IPAddress)
d.Set("ip_prefix_length", settings.IPPrefixLen)
d.Set("gateway", settings.Gateway)
break
}
}

d.Set("bridge", container.NetworkSettings.Bridge)
if err := d.Set("ports", flattenContainerPorts(container.NetworkSettings.Ports)); err != nil {
log.Printf("[WARN] failed to set ports from API: %s", err)
}
if err := d.Set("network_data", flattenContainerNetworks(container.NetworkSettings)); err != nil {
log.Printf("[WARN] failed to set network settings from API: %s", err)
}
}

return nil
Expand Down Expand Up @@ -372,6 +388,16 @@ func resourceDockerContainerDelete(d *schema.ResourceData, meta interface{}) err
return fmt.Errorf("Error deleting container %s: %s", d.Id(), err)
}

waitOkC, errorC := client.ContainerWait(context.Background(), d.Id(), container.WaitConditionRemoved)
select {
case waitOk := <-waitOkC:
log.Printf("[INFO] Container exited with code [%v]: '%s'", waitOk.StatusCode, d.Id())
case err := <-errorC:
if !(strings.Contains(err.Error(), "No such container") || strings.Contains(err.Error(), "is already in progress")) {
return fmt.Errorf("Error waiting for container removal '%s': %s", d.Id(), err)
}
}

d.SetId("")
return nil
}
Expand All @@ -394,6 +420,23 @@ func flattenContainerPorts(in nat.PortMap) []interface{} {
}
return out
}
func flattenContainerNetworks(in *types.NetworkSettings) []interface{} {
var out = make([]interface{}, 0)
if in == nil || in.Networks == nil || len(in.Networks) == 0 {
return out
}

networks := in.Networks
for networkName, networkData := range networks {
m := make(map[string]interface{})
m["network_name"] = networkName
m["ip_address"] = networkData.IPAddress
m["ip_prefix_length"] = networkData.IPPrefixLen
m["gateway"] = networkData.Gateway
out = append(out, m)
}
return out
}

// TODO move to separate flattener file
func stringListToStringSlice(stringList []interface{}) []string {
Expand Down
120 changes: 120 additions & 0 deletions docker/resource_docker_container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,73 @@ func TestAccDockerContainer_basic(t *testing.T) {
},
})
}
func TestAccDockerContainer_basic_network(t *testing.T) {
var c types.ContainerJSON
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDockerContainerWith2BridgeNetworkConfig,
Check: resource.ComposeTestCheckFunc(
testAccContainerRunning("docker_container.foo", &c),
resource.TestCheckResourceAttr("docker_container.foo", "bridge", ""),
resource.TestCheckResourceAttrSet("docker_container.foo", "ip_address"),
resource.TestCheckResourceAttrSet("docker_container.foo", "ip_prefix_length"),
resource.TestCheckResourceAttrSet("docker_container.foo", "gateway"),
resource.TestCheckResourceAttr("docker_container.foo", "network_data.#", "2"),
resource.TestCheckResourceAttrSet("docker_container.foo", "network_data.0.network_name"),
resource.TestCheckResourceAttrSet("docker_container.foo", "network_data.0.ip_address"),
resource.TestCheckResourceAttrSet("docker_container.foo", "network_data.0.ip_prefix_length"),
resource.TestCheckResourceAttrSet("docker_container.foo", "network_data.0.gateway"),
resource.TestCheckResourceAttrSet("docker_container.foo", "network_data.1.network_name"),
resource.TestCheckResourceAttrSet("docker_container.foo", "network_data.1.ip_address"),
resource.TestCheckResourceAttrSet("docker_container.foo", "network_data.1.ip_prefix_length"),
resource.TestCheckResourceAttrSet("docker_container.foo", "network_data.1.gateway"),
),
},
},
})
}

func TestAccDockerContainer_2networks_withmode(t *testing.T) {
var c types.ContainerJSON
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDockerContainer2NetworksConfig,
Check: resource.ComposeTestCheckFunc(
testAccContainerRunning("docker_container.foo", &c),
resource.TestCheckResourceAttr("docker_container.foo", "bridge", ""),
resource.TestCheckResourceAttrSet("docker_container.foo", "ip_address"),
resource.TestCheckResourceAttrSet("docker_container.foo", "ip_prefix_length"),
resource.TestCheckResourceAttrSet("docker_container.foo", "gateway"),
resource.TestCheckResourceAttr("docker_container.foo", "network_data.#", "2"),
resource.TestCheckResourceAttrSet("docker_container.foo", "network_data.0.network_name"),
resource.TestCheckResourceAttrSet("docker_container.foo", "network_data.0.ip_address"),
resource.TestCheckResourceAttrSet("docker_container.foo", "network_data.0.ip_prefix_length"),
resource.TestCheckResourceAttrSet("docker_container.foo", "network_data.0.gateway"),
resource.TestCheckResourceAttrSet("docker_container.foo", "network_data.1.network_name"),
resource.TestCheckResourceAttrSet("docker_container.foo", "network_data.1.ip_address"),
resource.TestCheckResourceAttrSet("docker_container.foo", "network_data.1.ip_prefix_length"),
resource.TestCheckResourceAttrSet("docker_container.foo", "network_data.1.gateway"),
resource.TestCheckResourceAttr("docker_container.bar", "network_alias.#", "1"),
resource.TestCheckResourceAttr("docker_container.bar", "bridge", ""),
resource.TestCheckResourceAttrSet("docker_container.bar", "ip_address"),
resource.TestCheckResourceAttrSet("docker_container.bar", "ip_prefix_length"),
resource.TestCheckResourceAttrSet("docker_container.bar", "gateway"),
resource.TestCheckResourceAttr("docker_container.bar", "network_data.#", "1"),
resource.TestCheckResourceAttrSet("docker_container.bar", "network_data.0.network_name"),
resource.TestCheckResourceAttrSet("docker_container.bar", "network_data.0.ip_address"),
resource.TestCheckResourceAttrSet("docker_container.bar", "network_data.0.ip_prefix_length"),
resource.TestCheckResourceAttrSet("docker_container.bar", "network_data.0.gateway"),
),
},
},
})
}

func TestAccDockerContainerPath_validation(t *testing.T) {
cases := []struct {
Expand Down Expand Up @@ -687,6 +754,29 @@ resource "docker_container" "foo" {
}
`

const testAccDockerContainerWith2BridgeNetworkConfig = `
resource "docker_network" "tftest" {
name = "tftest-contnw"
}

resource "docker_network" "tftest_2" {
name = "tftest-contnw-2"
}

resource "docker_image" "foo" {
name = "nginx:latest"
}

resource "docker_container" "foo" {
name = "tf-test"
image = "${docker_image.foo.latest}"
networks = [
"${docker_network.tftest.name}",
"${docker_network.tftest_2.name}"
]
}
`

const testAccDockerContainerVolumeConfig = `
resource "docker_image" "foo" {
name = "nginx:latest"
Expand Down Expand Up @@ -885,3 +975,33 @@ resource "docker_container" "foo" {
]
}
`
const testAccDockerContainer2NetworksConfig = `
resource "docker_image" "foo" {
name = "nginx:latest"
keep_locally = true
}

resource "docker_network" "test_network_1" {
name = "tftest-1"
}

resource "docker_network" "test_network_2" {
name = "tftest-2"
}

resource "docker_container" "foo" {
name = "tf-test"
image = "${docker_image.foo.latest}"
network_mode = "${docker_network.test_network_1.name}"
networks = ["${docker_network.test_network_2.name}"]
network_alias = ["tftest-container"]
}

resource "docker_container" "bar" {
name = "tf-test-bar"
image = "${docker_image.foo.latest}"
network_mode = "bridge"
networks = ["${docker_network.test_network_2.name}"]
network_alias = ["tftest-container-foo"]
}
`
Loading