fixes ports on containers (#95)

- `docker_container` needs only internal port to be specified. Closes #8 and #89 for the documentation
- `docker_container` writes external port now correctly into `tfstate`. Closes #73
This commit is contained in:
Manuel Vogel 2018-10-09 22:32:26 +02:00 committed by GitHub
parent 3a8335be04
commit 85e56c1ab0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 178 additions and 13 deletions

View file

@ -3,6 +3,10 @@
IMPROVEMENTS
* Add support for running tests on Windows [GH-54] and ([#90](https://github.com/terraform-providers/terraform-provider-docker/pull/90))
BUG FIXES
* Fixes issue with internal and external ports on containers [GH-8] and ([#89](https://github.com/terraform-providers/terraform-provider-docker/pull/90))
* Fixes `tfstate` having correct external port for containers [GH-73]
## 1.0.2 (September 27, 2018)
BUG FIXES

View file

@ -205,12 +205,14 @@ func resourceDockerContainer() *schema.Resource {
"external": &schema.Schema{
Type: schema.TypeInt,
Default: "32678",
Optional: true,
ForceNew: true,
},
"ip": &schema.Schema{
Type: schema.TypeString,
Default: "0.0.0.0",
Optional: true,
ForceNew: true,
},

View file

@ -3,12 +3,17 @@ package docker
import (
"archive/tar"
"bytes"
"encoding/json"
"errors"
"fmt"
"log"
"strconv"
"strings"
"time"
"context"
"math/rand"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
@ -16,7 +21,6 @@ import (
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
"github.com/hashicorp/terraform/helper/schema"
"math/rand"
)
var (
@ -284,6 +288,9 @@ func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error
return fmt.Errorf("Error inspecting container %s: %s", apiContainer.ID, err)
}
jsonObj, _ := json.MarshalIndent(container, "", "\t")
log.Printf("[DEBUG] Docker container inspect: %s", jsonObj)
if container.State.Running ||
!container.State.Running && !d.Get("must_run").(bool) {
break
@ -319,12 +326,16 @@ func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error
d.Set("ip_prefix_length", container.NetworkSettings.IPPrefixLen)
d.Set("gateway", container.NetworkSettings.Gateway)
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)
}
}
return nil
}
func resourceDockerContainerUpdate(d *schema.ResourceData, meta interface{}) error {
// TODO call resourceDockerContainerRead here
return nil
}
@ -354,6 +365,28 @@ func resourceDockerContainerDelete(d *schema.ResourceData, meta interface{}) err
return nil
}
// TODO extract to structures_container.go
func flattenContainerPorts(in nat.PortMap) *schema.Set {
var out = make([]interface{}, 0)
for port, portBindings := range in {
m := make(map[string]interface{})
for _, portBinding := range portBindings {
portProtocolSplit := strings.Split(string(port), "/")
convertedInternal, _ := strconv.Atoi(portProtocolSplit[0])
convertedExternal, _ := strconv.Atoi(portBinding.HostPort)
m["internal"] = convertedInternal
m["external"] = convertedExternal
m["ip"] = portBinding.HostIP
m["protocol"] = portProtocolSplit[1]
out = append(out, m)
}
}
portsSpecResource := resourceDockerContainer().Schema["ports"].Elem.(*schema.Resource)
f := schema.HashResource(portsSpecResource)
return schema.NewSet(f, out)
}
// TODO move to separate flattener file
func stringListToStringSlice(stringList []interface{}) []string {
ret := []string{}
for _, v := range stringList {
@ -424,19 +457,20 @@ func portSetToDockerPorts(ports *schema.Set) (map[nat.Port]struct{}, map[nat.Por
exposedPort := nat.Port(strconv.Itoa(internal) + "/" + protocol)
retExposedPorts[exposedPort] = struct{}{}
external, extOk := port["external"].(int)
ip, ipOk := port["ip"].(string)
portBinding := nat.PortBinding{}
external, extOk := port["external"].(int)
if extOk {
portBinding := nat.PortBinding{
HostPort: strconv.Itoa(external),
portBinding.HostPort = strconv.Itoa(external)
}
ip, ipOk := port["ip"].(string)
if ipOk {
portBinding.HostIP = ip
}
retPortBindings[exposedPort] = append(retPortBindings[exposedPort], portBinding)
}
}
return retExposedPorts, retPortBindings
}

View file

@ -9,6 +9,7 @@ import (
"testing"
"context"
"github.com/docker/docker/api/types"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
@ -375,6 +376,98 @@ func TestAccDockerContainer_device(t *testing.T) {
},
})
}
func TestAccDockerContainer_port_internal(t *testing.T) {
var c types.ContainerJSON
testCheck := func(*terraform.State) error {
portMap := c.NetworkSettings.NetworkSettingsBase.Ports
portBindings, ok := portMap["80/tcp"]
if !ok || len(portMap["80/tcp"]) == 0 {
return fmt.Errorf("Port 80 on tcp is not set")
}
portBindingsLength := len(portBindings)
if portBindingsLength != 1 {
return fmt.Errorf("Expected 1 binding on port 80, but was %d", portBindingsLength)
}
if len(portBindings[0].HostIP) == 0 {
return fmt.Errorf("Expected host IP to be set, but was empty")
}
if len(portBindings[0].HostPort) == 0 {
return fmt.Errorf("Expected host port to be set, but was empty")
}
return nil
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDockerContainerInternalPortConfig,
Check: resource.ComposeTestCheckFunc(
testAccContainerRunning("docker_container.foo", &c),
testCheck,
resource.TestCheckResourceAttr("docker_container.foo", "name", "tf-test"),
resource.TestCheckResourceAttr("docker_container.foo", "ports.#", "1"),
resource.TestCheckResourceAttr("docker_container.foo", "ports.2978131916.internal", "80"),
resource.TestCheckResourceAttr("docker_container.foo", "ports.2978131916.ip", "0.0.0.0"),
resource.TestCheckResourceAttr("docker_container.foo", "ports.2978131916.protocol", "tcp"),
resource.TestCheckResourceAttr("docker_container.foo", "ports.2978131916.external", "32678"),
),
},
},
})
}
func TestAccDockerContainer_port(t *testing.T) {
var c types.ContainerJSON
testCheck := func(*terraform.State) error {
portMap := c.NetworkSettings.NetworkSettingsBase.Ports
portBindings, ok := portMap["80/tcp"]
if !ok || len(portMap["80/tcp"]) == 0 {
return fmt.Errorf("Port 80 on tcp is not set")
}
portBindingsLength := len(portBindings)
if portBindingsLength != 1 {
return fmt.Errorf("Expected 1 binding on port 80, but was %d", portBindingsLength)
}
if len(portBindings[0].HostIP) == 0 {
return fmt.Errorf("Expected host IP to be set, but was empty")
}
if len(portBindings[0].HostPort) == 0 {
return fmt.Errorf("Expected host port to be set, but was empty")
}
return nil
}
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccDockerContainerPortConfig,
Check: resource.ComposeTestCheckFunc(
testAccContainerRunning("docker_container.foo", &c),
testCheck,
resource.TestCheckResourceAttr("docker_container.foo", "name", "tf-test"),
resource.TestCheckResourceAttr("docker_container.foo", "ports.#", "1"),
resource.TestCheckResourceAttr("docker_container.foo", "ports.2498386340.internal", "80"),
resource.TestCheckResourceAttr("docker_container.foo", "ports.2498386340.ip", "0.0.0.0"),
resource.TestCheckResourceAttr("docker_container.foo", "ports.2498386340.protocol", "tcp"),
resource.TestCheckResourceAttr("docker_container.foo", "ports.2498386340.external", "32787"),
),
},
},
})
}
func testAccContainerRunning(n string, container *types.ContainerJSON) resource.TestCheckFunc {
return func(s *terraform.State) error {
@ -539,3 +632,35 @@ resource "docker_container" "foo" {
}
}
`
const testAccDockerContainerInternalPortConfig = `
resource "docker_image" "foo" {
name = "nginx:latest"
keep_locally = true
}
resource "docker_container" "foo" {
name = "tf-test"
image = "${docker_image.foo.latest}"
ports {
internal = "80"
}
}
`
const testAccDockerContainerPortConfig = `
resource "docker_image" "foo" {
name = "nginx:latest"
keep_locally = true
}
resource "docker_container" "foo" {
name = "tf-test"
image = "${docker_image.foo.latest}"
ports {
internal = "80"
external = "32787"
}
}
`

View file

@ -122,8 +122,8 @@ the port mappings of the container. Each `ports` block supports
the following:
* `internal` - (Required, int) Port within the container.
* `external` - (Required, int) Port exposed out of the container.
* `ip` - (Optional, string) IP address/mask that can access this port.
* `external` - (Optional, int) Port exposed out of the container, defaults to `32768`.
* `ip` - (Optional, string) IP address/mask that can access this port, default to `0.0.0.0`
* `protocol` - (Optional, string) Protocol that can be used over this port,
defaults to TCP.