Skip to content

Commit

Permalink
provisioners: Allow provisioning over IPv6
Browse files Browse the repository at this point in the history
  • Loading branch information
kristinn authored and jen20 committed Sep 3, 2016
1 parent d772389 commit bc5518f
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 0 deletions.
17 changes: 17 additions & 0 deletions communicator/shared/shared.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package shared

import (
"fmt"
"net"
)

// IpFormat formats the IP correctly, so we don't provide IPv6 address in an IPv4 format during node communication. We return the ip parameter as is if it's an IPv4 address or a hostname.
func IpFormat(ip string) string {
ipObj := net.ParseIP(ip)
// Return the ip/host as is if it's either a hostname or an IPv4 address.
if ipObj == nil || ipObj.To4() != nil {
return ip
}

return fmt.Sprintf("[%s]", ip)
}
26 changes: 26 additions & 0 deletions communicator/shared/shared_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package shared

import (
"testing"
)

func TestIpFormatting_Ipv4(t *testing.T) {
formatted := IpFormat("127.0.0.1")
if formatted != "127.0.0.1" {
t.Fatal("expected", "127.0.0.1", "got", formatted)
}
}

func TestIpFormatting_Hostname(t *testing.T) {
formatted := IpFormat("example.com")
if formatted != "example.com" {
t.Fatal("expected", "example.com", "got", formatted)
}
}

func TestIpFormatting_Ipv6(t *testing.T) {
formatted := IpFormat("::1")
if formatted != "[::1]" {
t.Fatal("expected", "[::1]", "got", formatted)
}
}
10 changes: 10 additions & 0 deletions communicator/ssh/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"
"time"

"github.com/hashicorp/terraform/communicator/shared"
"github.com/hashicorp/terraform/helper/pathorcontents"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/mapstructure"
Expand Down Expand Up @@ -84,6 +85,11 @@ func parseConnectionInfo(s *terraform.InstanceState) (*connectionInfo, error) {
if connInfo.User == "" {
connInfo.User = DefaultUser
}

// Format the host if needed.
// Needed for IPv6 support.
connInfo.Host = shared.IpFormat(connInfo.Host)

if connInfo.Port == 0 {
connInfo.Port = DefaultPort
}
Expand All @@ -107,6 +113,10 @@ func parseConnectionInfo(s *terraform.InstanceState) (*connectionInfo, error) {

// Default all bastion config attrs to their non-bastion counterparts
if connInfo.BastionHost != "" {
// Format the bastion host if needed.
// Needed for IPv6 support.
connInfo.BastionHost = shared.IpFormat(connInfo.BastionHost)

if connInfo.BastionUser == "" {
connInfo.BastionUser = connInfo.User
}
Expand Down
62 changes: 62 additions & 0 deletions communicator/ssh/provisioner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,68 @@ func TestProvisioner_connInfo(t *testing.T) {
}
}

func TestProvisioner_connInfoIpv6(t *testing.T) {
r := &terraform.InstanceState{
Ephemeral: terraform.EphemeralState{
ConnInfo: map[string]string{
"type": "ssh",
"user": "root",
"password": "supersecret",
"private_key": "someprivatekeycontents",
"host": "::1",
"port": "22",
"timeout": "30s",

"bastion_host": "::1",
},
},
}

conf, err := parseConnectionInfo(r)
if err != nil {
t.Fatalf("err: %v", err)
}

if conf.Host != "[::1]" {
t.Fatalf("bad: %v", conf)
}

if conf.BastionHost != "[::1]" {
t.Fatalf("bad %v", conf)
}
}

func TestProvisioner_connInfoHostname(t *testing.T) {
r := &terraform.InstanceState{
Ephemeral: terraform.EphemeralState{
ConnInfo: map[string]string{
"type": "ssh",
"user": "root",
"password": "supersecret",
"private_key": "someprivatekeycontents",
"host": "example.com",
"port": "22",
"timeout": "30s",

"bastion_host": "example.com",
},
},
}

conf, err := parseConnectionInfo(r)
if err != nil {
t.Fatalf("err: %v", err)
}

if conf.Host != "example.com" {
t.Fatalf("bad: %v", conf)
}

if conf.BastionHost != "example.com" {
t.Fatalf("bad %v", conf)
}
}

func TestProvisioner_connInfoLegacy(t *testing.T) {
r := &terraform.InstanceState{
Ephemeral: terraform.EphemeralState{
Expand Down
6 changes: 6 additions & 0 deletions communicator/winrm/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"
"time"

"github.com/hashicorp/terraform/communicator/shared"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/mapstructure"
)
Expand Down Expand Up @@ -72,6 +73,11 @@ func parseConnectionInfo(s *terraform.InstanceState) (*connectionInfo, error) {
if connInfo.User == "" {
connInfo.User = DefaultUser
}

// Format the host if needed.
// Needed for IPv6 support.
connInfo.Host = shared.IpFormat(connInfo.Host)

if connInfo.Port == 0 {
connInfo.Port = DefaultPort
}
Expand Down
86 changes: 86 additions & 0 deletions communicator/winrm/provisioner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,92 @@ func TestProvisioner_connInfo(t *testing.T) {
}
}

func TestProvisioner_connInfoIpv6(t *testing.T) {
r := &terraform.InstanceState{
Ephemeral: terraform.EphemeralState{
ConnInfo: map[string]string{
"type": "winrm",
"user": "Administrator",
"password": "supersecret",
"host": "::1",
"port": "5985",
"https": "true",
"timeout": "30s",
},
},
}

conf, err := parseConnectionInfo(r)
if err != nil {
t.Fatalf("err: %v", err)
}

if conf.User != "Administrator" {
t.Fatalf("expected: %v: got: %v", "Administrator", conf)
}
if conf.Password != "supersecret" {
t.Fatalf("expected: %v: got: %v", "supersecret", conf)
}
if conf.Host != "[::1]" {
t.Fatalf("expected: %v: got: %v", "[::1]", conf)
}
if conf.Port != 5985 {
t.Fatalf("expected: %v: got: %v", 5985, conf)
}
if conf.HTTPS != true {
t.Fatalf("expected: %v: got: %v", true, conf)
}
if conf.Timeout != "30s" {
t.Fatalf("expected: %v: got: %v", "30s", conf)
}
if conf.ScriptPath != DefaultScriptPath {
t.Fatalf("expected: %v: got: %v", DefaultScriptPath, conf)
}
}

func TestProvisioner_connInfoHostname(t *testing.T) {
r := &terraform.InstanceState{
Ephemeral: terraform.EphemeralState{
ConnInfo: map[string]string{
"type": "winrm",
"user": "Administrator",
"password": "supersecret",
"host": "example.com",
"port": "5985",
"https": "true",
"timeout": "30s",
},
},
}

conf, err := parseConnectionInfo(r)
if err != nil {
t.Fatalf("err: %v", err)
}

if conf.User != "Administrator" {
t.Fatalf("expected: %v: got: %v", "Administrator", conf)
}
if conf.Password != "supersecret" {
t.Fatalf("expected: %v: got: %v", "supersecret", conf)
}
if conf.Host != "example.com" {
t.Fatalf("expected: %v: got: %v", "example.com", conf)
}
if conf.Port != 5985 {
t.Fatalf("expected: %v: got: %v", 5985, conf)
}
if conf.HTTPS != true {
t.Fatalf("expected: %v: got: %v", true, conf)
}
if conf.Timeout != "30s" {
t.Fatalf("expected: %v: got: %v", "30s", conf)
}
if conf.ScriptPath != DefaultScriptPath {
t.Fatalf("expected: %v: got: %v", DefaultScriptPath, conf)
}
}

func TestProvisioner_formatDuration(t *testing.T) {
cases := map[string]struct {
InstanceState *terraform.InstanceState
Expand Down

0 comments on commit bc5518f

Please sign in to comment.