provisioners: Allow provisioning over IPv6

This commit is contained in:
Kristinn Örn Sigurðsson 2016-09-04 00:45:24 +02:00 committed by James Nugent
parent d772389518
commit bc5518f993
6 changed files with 207 additions and 0 deletions

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -8,6 +8,7 @@ import (
"os" "os"
"time" "time"
"github.com/hashicorp/terraform/communicator/shared"
"github.com/hashicorp/terraform/helper/pathorcontents" "github.com/hashicorp/terraform/helper/pathorcontents"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
@ -84,6 +85,11 @@ func parseConnectionInfo(s *terraform.InstanceState) (*connectionInfo, error) {
if connInfo.User == "" { if connInfo.User == "" {
connInfo.User = DefaultUser connInfo.User = DefaultUser
} }
// Format the host if needed.
// Needed for IPv6 support.
connInfo.Host = shared.IpFormat(connInfo.Host)
if connInfo.Port == 0 { if connInfo.Port == 0 {
connInfo.Port = DefaultPort connInfo.Port = DefaultPort
} }
@ -107,6 +113,10 @@ func parseConnectionInfo(s *terraform.InstanceState) (*connectionInfo, error) {
// Default all bastion config attrs to their non-bastion counterparts // Default all bastion config attrs to their non-bastion counterparts
if connInfo.BastionHost != "" { if connInfo.BastionHost != "" {
// Format the bastion host if needed.
// Needed for IPv6 support.
connInfo.BastionHost = shared.IpFormat(connInfo.BastionHost)
if connInfo.BastionUser == "" { if connInfo.BastionUser == "" {
connInfo.BastionUser = connInfo.User connInfo.BastionUser = connInfo.User
} }

View File

@ -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) { func TestProvisioner_connInfoLegacy(t *testing.T) {
r := &terraform.InstanceState{ r := &terraform.InstanceState{
Ephemeral: terraform.EphemeralState{ Ephemeral: terraform.EphemeralState{

View File

@ -7,6 +7,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/hashicorp/terraform/communicator/shared"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
) )
@ -72,6 +73,11 @@ func parseConnectionInfo(s *terraform.InstanceState) (*connectionInfo, error) {
if connInfo.User == "" { if connInfo.User == "" {
connInfo.User = DefaultUser connInfo.User = DefaultUser
} }
// Format the host if needed.
// Needed for IPv6 support.
connInfo.Host = shared.IpFormat(connInfo.Host)
if connInfo.Port == 0 { if connInfo.Port == 0 {
connInfo.Port = DefaultPort connInfo.Port = DefaultPort
} }

View File

@ -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) { func TestProvisioner_formatDuration(t *testing.T) {
cases := map[string]struct { cases := map[string]struct {
InstanceState *terraform.InstanceState InstanceState *terraform.InstanceState