mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2025-12-20 22:59:42 -05:00
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:
parent
3a8335be04
commit
85e56c1ab0
5 changed files with 178 additions and 13 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,18 +457,19 @@ 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),
|
||||
}
|
||||
if ipOk {
|
||||
portBinding.HostIP = ip
|
||||
}
|
||||
retPortBindings[exposedPort] = append(retPortBindings[exposedPort], portBinding)
|
||||
portBinding.HostPort = strconv.Itoa(external)
|
||||
}
|
||||
|
||||
ip, ipOk := port["ip"].(string)
|
||||
if ipOk {
|
||||
portBinding.HostIP = ip
|
||||
}
|
||||
|
||||
retPortBindings[exposedPort] = append(retPortBindings[exposedPort], portBinding)
|
||||
}
|
||||
|
||||
return retExposedPorts, retPortBindings
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue