Merge branch 'master' into stable-website
This commit is contained in:
commit
3b18741004
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -1,10 +1,22 @@
|
||||||
|
## 0.11.5 (March 21, 2018)
|
||||||
|
|
||||||
|
IMPROVEMENTS:
|
||||||
|
|
||||||
|
* provisioner/chef: Allow specifying a channel ([#17355](https://github.com/hashicorp/terraform/issues/17355))
|
||||||
|
|
||||||
|
BUG FIXES:
|
||||||
|
|
||||||
|
* core: Fix the timeout handling for provisioners ([#17646](https://github.com/hashicorp/terraform/issues/17646))
|
||||||
|
* core: Ensure that state is unlocked after running console, import, graph or push commands ([#17645](https://github.com/hashicorp/terraform/issues/17645))
|
||||||
|
* core: Don't open multiple file descriptors for local state files, which would cause reading the state to fail on Windows [[#17636](https://github.com/hashicorp/terraform/issues/17636)]
|
||||||
|
|
||||||
## 0.11.4 (March 15, 2018)
|
## 0.11.4 (March 15, 2018)
|
||||||
|
|
||||||
IMPROVEMENTS:
|
IMPROVEMENTS:
|
||||||
|
|
||||||
* cli: `terraform state list` now accepts a new argument `-id=...` for filtering resources for display by their remote ids ([#17221](https://github.com/hashicorp/terraform/issues/17221))
|
* cli: `terraform state list` now accepts a new argument `-id=...` for filtering resources for display by their remote ids ([#17221](https://github.com/hashicorp/terraform/issues/17221))
|
||||||
* cli: `terraform destroy` now uses the option `-auto-approve` instead of `-force`, for consistency with `terraform apply`. The old flag is preserved for backward-compatibility, but is now deprecated; it will be retained for at least one major release. ([#17218](https://github.com/hashicorp/terraform/issues/17218))
|
* cli: `terraform destroy` now uses the option `-auto-approve` instead of `-force`, for consistency with `terraform apply`. The old flag is preserved for backward-compatibility, but is now deprecated; it will be retained for at least one major release. ([#17218](https://github.com/hashicorp/terraform/issues/17218))
|
||||||
* connection/ssh: Add support for host key verifiation ([#17354](https://github.com/hashicorp/terraform/issues/17354))
|
* connection/ssh: Add support for host key verification ([#17354](https://github.com/hashicorp/terraform/issues/17354))
|
||||||
* backend/s3: add support for the cn-northwest-1 region ([#17216](https://github.com/hashicorp/terraform/issues/17216))
|
* backend/s3: add support for the cn-northwest-1 region ([#17216](https://github.com/hashicorp/terraform/issues/17216))
|
||||||
* provisioner/local-exec: Allow setting custom environment variables when running commands ([#13880](https://github.com/hashicorp/terraform/issues/13880))
|
* provisioner/local-exec: Allow setting custom environment variables when running commands ([#13880](https://github.com/hashicorp/terraform/issues/13880))
|
||||||
* provisioner/habitat: Detect if hab user exists and only create if necessary ([#17195](https://github.com/hashicorp/terraform/issues/17195))
|
* provisioner/habitat: Detect if hab user exists and only create if necessary ([#17195](https://github.com/hashicorp/terraform/issues/17195))
|
||||||
|
@ -14,7 +26,6 @@ IMPROVEMENTS:
|
||||||
BUG FIXES:
|
BUG FIXES:
|
||||||
|
|
||||||
* core: Make sure state is locked during initial refresh ([#17422](https://github.com/hashicorp/terraform/issues/17422))
|
* core: Make sure state is locked during initial refresh ([#17422](https://github.com/hashicorp/terraform/issues/17422))
|
||||||
* core: Fix interpolation error when count references another interpolated count value ([#17368](https://github.com/hashicorp/terraform/issues/17368))
|
|
||||||
* core: Halt on fatal provisioner errors, rather than retrying until a timeout ([#17359](https://github.com/hashicorp/terraform/issues/17359))
|
* core: Halt on fatal provisioner errors, rather than retrying until a timeout ([#17359](https://github.com/hashicorp/terraform/issues/17359))
|
||||||
* core: When handling a forced exit due to multiple interrupts, prevent the process from exiting while the state is being written ([#17323](https://github.com/hashicorp/terraform/issues/17323))
|
* core: When handling a forced exit due to multiple interrupts, prevent the process from exiting while the state is being written ([#17323](https://github.com/hashicorp/terraform/issues/17323))
|
||||||
* core: Fix handling of locals and outputs at destroy time ([#17241](https://github.com/hashicorp/terraform/issues/17241))
|
* core: Fix handling of locals and outputs at destroy time ([#17241](https://github.com/hashicorp/terraform/issues/17241))
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
chmod = "find %s -maxdepth 1 -type f -exec /bin/chmod %d {} +"
|
chmod = "find %s -maxdepth 1 -type f -exec /bin/chmod %d {} +"
|
||||||
installURL = "https://www.chef.io/chef/install.sh"
|
installURL = "https://omnitruck.chef.io/install.sh"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *provisioner) linuxInstallChefClient(o terraform.UIOutput, comm communicator.Communicator) error {
|
func (p *provisioner) linuxInstallChefClient(o terraform.UIOutput, comm communicator.Communicator) error {
|
||||||
|
@ -34,7 +34,7 @@ func (p *provisioner) linuxInstallChefClient(o terraform.UIOutput, comm communic
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then execute the install.sh scrip to download and install Chef Client
|
// Then execute the install.sh scrip to download and install Chef Client
|
||||||
err = p.runCommand(o, comm, fmt.Sprintf("%sbash ./install.sh -v %q", prefix, p.Version))
|
err = p.runCommand(o, comm, fmt.Sprintf("%sbash ./install.sh -v %q -c %s", prefix, p.Version, p.Channel))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,9 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
|
||||||
},
|
},
|
||||||
|
|
||||||
Commands: map[string]bool{
|
Commands: map[string]bool{
|
||||||
"sudo curl -LO https://www.chef.io/chef/install.sh": true,
|
"sudo curl -LO https://omnitruck.chef.io/install.sh": true,
|
||||||
"sudo bash ./install.sh -v \"\"": true,
|
"sudo bash ./install.sh -v \"\" -c stable": true,
|
||||||
"sudo rm -f install.sh": true,
|
"sudo rm -f install.sh": true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -43,9 +43,9 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
|
||||||
},
|
},
|
||||||
|
|
||||||
Commands: map[string]bool{
|
Commands: map[string]bool{
|
||||||
"curl -LO https://www.chef.io/chef/install.sh": true,
|
"curl -LO https://omnitruck.chef.io/install.sh": true,
|
||||||
"bash ./install.sh -v \"\"": true,
|
"bash ./install.sh -v \"\" -c stable": true,
|
||||||
"rm -f install.sh": true,
|
"rm -f install.sh": true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -61,9 +61,9 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
|
||||||
},
|
},
|
||||||
|
|
||||||
Commands: map[string]bool{
|
Commands: map[string]bool{
|
||||||
"http_proxy='http://proxy.local' curl -LO https://www.chef.io/chef/install.sh": true,
|
"http_proxy='http://proxy.local' curl -LO https://omnitruck.chef.io/install.sh": true,
|
||||||
"http_proxy='http://proxy.local' bash ./install.sh -v \"\"": true,
|
"http_proxy='http://proxy.local' bash ./install.sh -v \"\" -c stable": true,
|
||||||
"http_proxy='http://proxy.local' rm -f install.sh": true,
|
"http_proxy='http://proxy.local' rm -f install.sh": true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -79,9 +79,9 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
|
||||||
},
|
},
|
||||||
|
|
||||||
Commands: map[string]bool{
|
Commands: map[string]bool{
|
||||||
"https_proxy='https://proxy.local' curl -LO https://www.chef.io/chef/install.sh": true,
|
"https_proxy='https://proxy.local' curl -LO https://omnitruck.chef.io/install.sh": true,
|
||||||
"https_proxy='https://proxy.local' bash ./install.sh -v \"\"": true,
|
"https_proxy='https://proxy.local' bash ./install.sh -v \"\" -c stable": true,
|
||||||
"https_proxy='https://proxy.local' rm -f install.sh": true,
|
"https_proxy='https://proxy.local' rm -f install.sh": true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -99,9 +99,9 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
|
||||||
|
|
||||||
Commands: map[string]bool{
|
Commands: map[string]bool{
|
||||||
"http_proxy='http://proxy.local' no_proxy='http://local.local,http://local.org' " +
|
"http_proxy='http://proxy.local' no_proxy='http://local.local,http://local.org' " +
|
||||||
"curl -LO https://www.chef.io/chef/install.sh": true,
|
"curl -LO https://omnitruck.chef.io/install.sh": true,
|
||||||
"http_proxy='http://proxy.local' no_proxy='http://local.local,http://local.org' " +
|
"http_proxy='http://proxy.local' no_proxy='http://local.local,http://local.org' " +
|
||||||
"bash ./install.sh -v \"\"": true,
|
"bash ./install.sh -v \"\" -c stable": true,
|
||||||
"http_proxy='http://proxy.local' no_proxy='http://local.local,http://local.org' " +
|
"http_proxy='http://proxy.local' no_proxy='http://local.local,http://local.org' " +
|
||||||
"rm -f install.sh": true,
|
"rm -f install.sh": true,
|
||||||
},
|
},
|
||||||
|
@ -119,9 +119,28 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
|
||||||
},
|
},
|
||||||
|
|
||||||
Commands: map[string]bool{
|
Commands: map[string]bool{
|
||||||
"curl -LO https://www.chef.io/chef/install.sh": true,
|
"curl -LO https://omnitruck.chef.io/install.sh": true,
|
||||||
"bash ./install.sh -v \"11.18.6\"": true,
|
"bash ./install.sh -v \"11.18.6\" -c stable": true,
|
||||||
"rm -f install.sh": true,
|
"rm -f install.sh": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"Channel": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"channel": "current",
|
||||||
|
"node_name": "nodename1",
|
||||||
|
"prevent_sudo": true,
|
||||||
|
"run_list": []interface{}{"cookbook::recipe"},
|
||||||
|
"server_url": "https://chef.local",
|
||||||
|
"user_name": "bob",
|
||||||
|
"user_key": "USER-KEY",
|
||||||
|
"version": "11.18.6",
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"curl -LO https://omnitruck.chef.io/install.sh": true,
|
||||||
|
"bash ./install.sh -v \"11.18.6\" -c current": true,
|
||||||
|
"rm -f install.sh": true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,6 +86,7 @@ type provisionFn func(terraform.UIOutput, communicator.Communicator) error
|
||||||
|
|
||||||
type provisioner struct {
|
type provisioner struct {
|
||||||
Attributes map[string]interface{}
|
Attributes map[string]interface{}
|
||||||
|
Channel string
|
||||||
ClientOptions []string
|
ClientOptions []string
|
||||||
DisableReporting bool
|
DisableReporting bool
|
||||||
Environment string
|
Environment string
|
||||||
|
@ -149,6 +150,11 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
"channel": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "stable",
|
||||||
|
},
|
||||||
"client_options": &schema.Schema{
|
"client_options": &schema.Schema{
|
||||||
Type: schema.TypeList,
|
Type: schema.TypeList,
|
||||||
Elem: &schema.Schema{Type: schema.TypeString},
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
@ -306,11 +312,11 @@ func applyFn(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, comm.Timeout())
|
retryCtx, cancel := context.WithTimeout(ctx, comm.Timeout())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Wait and retry until we establish the connection
|
// Wait and retry until we establish the connection
|
||||||
err = communicator.Retry(ctx, func() error {
|
err = communicator.Retry(retryCtx, func() error {
|
||||||
return comm.Connect(o)
|
return comm.Connect(o)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -725,6 +731,7 @@ func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader, doneCh chan<
|
||||||
|
|
||||||
func decodeConfig(d *schema.ResourceData) (*provisioner, error) {
|
func decodeConfig(d *schema.ResourceData) (*provisioner, error) {
|
||||||
p := &provisioner{
|
p := &provisioner{
|
||||||
|
Channel: d.Get("channel").(string),
|
||||||
ClientOptions: getStringList(d.Get("client_options")),
|
ClientOptions: getStringList(d.Get("client_options")),
|
||||||
DisableReporting: d.Get("disable_reporting").(bool),
|
DisableReporting: d.Get("disable_reporting").(bool),
|
||||||
Environment: d.Get("environment").(string),
|
Environment: d.Get("environment").(string),
|
||||||
|
|
|
@ -23,7 +23,7 @@ switch ($winver)
|
||||||
|
|
||||||
if ([System.IntPtr]::Size -eq 4) {$machine_arch = "i686"} else {$machine_arch = "x86_64"}
|
if ([System.IntPtr]::Size -eq 4) {$machine_arch = "i686"} else {$machine_arch = "x86_64"}
|
||||||
|
|
||||||
$url = "http://www.chef.io/chef/download?p=windows&pv=$machine_os&m=$machine_arch&v=%s"
|
$url = "http://omnitruck.chef.io/%s/chef/download?p=windows&pv=$machine_os&m=$machine_arch&v=%s"
|
||||||
$dest = [System.IO.Path]::GetTempFileName()
|
$dest = [System.IO.Path]::GetTempFileName()
|
||||||
$dest = [System.IO.Path]::ChangeExtension($dest, ".msi")
|
$dest = [System.IO.Path]::ChangeExtension($dest, ".msi")
|
||||||
$downloader = New-Object System.Net.WebClient
|
$downloader = New-Object System.Net.WebClient
|
||||||
|
@ -48,7 +48,7 @@ Start-Process -FilePath msiexec -ArgumentList /qn, /i, $dest -Wait
|
||||||
|
|
||||||
func (p *provisioner) windowsInstallChefClient(o terraform.UIOutput, comm communicator.Communicator) error {
|
func (p *provisioner) windowsInstallChefClient(o terraform.UIOutput, comm communicator.Communicator) error {
|
||||||
script := path.Join(path.Dir(comm.ScriptPath()), "ChefClient.ps1")
|
script := path.Join(path.Dir(comm.ScriptPath()), "ChefClient.ps1")
|
||||||
content := fmt.Sprintf(installScript, p.Version, p.HTTPProxy, strings.Join(p.NOProxy, ","))
|
content := fmt.Sprintf(installScript, p.Channel, p.Version, p.HTTPProxy, strings.Join(p.NOProxy, ","))
|
||||||
|
|
||||||
// Copy the script to the new instance
|
// Copy the script to the new instance
|
||||||
if err := comm.UploadScript(script, strings.NewReader(content)); err != nil {
|
if err := comm.UploadScript(script, strings.NewReader(content)); err != nil {
|
||||||
|
|
|
@ -72,6 +72,26 @@ func TestResourceProvider_windowsInstallChefClient(t *testing.T) {
|
||||||
"ChefClient.ps1": versionWindowsInstallScript,
|
"ChefClient.ps1": versionWindowsInstallScript,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"Channel": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"channel": "current",
|
||||||
|
"node_name": "nodename1",
|
||||||
|
"run_list": []interface{}{"cookbook::recipe"},
|
||||||
|
"server_url": "https://chef.local",
|
||||||
|
"user_name": "bob",
|
||||||
|
"user_key": "USER-KEY",
|
||||||
|
"version": "11.18.6",
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"powershell -NoProfile -ExecutionPolicy Bypass -File ChefClient.ps1": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
UploadScripts: map[string]string{
|
||||||
|
"ChefClient.ps1": channelWindowsInstallScript,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
o := new(terraform.MockUIOutput)
|
o := new(terraform.MockUIOutput)
|
||||||
|
@ -219,7 +239,7 @@ switch ($winver)
|
||||||
|
|
||||||
if ([System.IntPtr]::Size -eq 4) {$machine_arch = "i686"} else {$machine_arch = "x86_64"}
|
if ([System.IntPtr]::Size -eq 4) {$machine_arch = "i686"} else {$machine_arch = "x86_64"}
|
||||||
|
|
||||||
$url = "http://www.chef.io/chef/download?p=windows&pv=$machine_os&m=$machine_arch&v="
|
$url = "http://omnitruck.chef.io/stable/chef/download?p=windows&pv=$machine_os&m=$machine_arch&v="
|
||||||
$dest = [System.IO.Path]::GetTempFileName()
|
$dest = [System.IO.Path]::GetTempFileName()
|
||||||
$dest = [System.IO.Path]::ChangeExtension($dest, ".msi")
|
$dest = [System.IO.Path]::ChangeExtension($dest, ".msi")
|
||||||
$downloader = New-Object System.Net.WebClient
|
$downloader = New-Object System.Net.WebClient
|
||||||
|
@ -256,7 +276,7 @@ switch ($winver)
|
||||||
|
|
||||||
if ([System.IntPtr]::Size -eq 4) {$machine_arch = "i686"} else {$machine_arch = "x86_64"}
|
if ([System.IntPtr]::Size -eq 4) {$machine_arch = "i686"} else {$machine_arch = "x86_64"}
|
||||||
|
|
||||||
$url = "http://www.chef.io/chef/download?p=windows&pv=$machine_os&m=$machine_arch&v="
|
$url = "http://omnitruck.chef.io/stable/chef/download?p=windows&pv=$machine_os&m=$machine_arch&v="
|
||||||
$dest = [System.IO.Path]::GetTempFileName()
|
$dest = [System.IO.Path]::GetTempFileName()
|
||||||
$dest = [System.IO.Path]::ChangeExtension($dest, ".msi")
|
$dest = [System.IO.Path]::ChangeExtension($dest, ".msi")
|
||||||
$downloader = New-Object System.Net.WebClient
|
$downloader = New-Object System.Net.WebClient
|
||||||
|
@ -293,7 +313,43 @@ switch ($winver)
|
||||||
|
|
||||||
if ([System.IntPtr]::Size -eq 4) {$machine_arch = "i686"} else {$machine_arch = "x86_64"}
|
if ([System.IntPtr]::Size -eq 4) {$machine_arch = "i686"} else {$machine_arch = "x86_64"}
|
||||||
|
|
||||||
$url = "http://www.chef.io/chef/download?p=windows&pv=$machine_os&m=$machine_arch&v=11.18.6"
|
$url = "http://omnitruck.chef.io/stable/chef/download?p=windows&pv=$machine_os&m=$machine_arch&v=11.18.6"
|
||||||
|
$dest = [System.IO.Path]::GetTempFileName()
|
||||||
|
$dest = [System.IO.Path]::ChangeExtension($dest, ".msi")
|
||||||
|
$downloader = New-Object System.Net.WebClient
|
||||||
|
|
||||||
|
$http_proxy = ''
|
||||||
|
if ($http_proxy -ne '') {
|
||||||
|
$no_proxy = ''
|
||||||
|
if ($no_proxy -eq ''){
|
||||||
|
$no_proxy = "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
$proxy = New-Object System.Net.WebProxy($http_proxy, $true, ,$no_proxy.Split(','))
|
||||||
|
$downloader.proxy = $proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host 'Downloading Chef Client...'
|
||||||
|
$downloader.DownloadFile($url, $dest)
|
||||||
|
|
||||||
|
Write-Host 'Installing Chef Client...'
|
||||||
|
Start-Process -FilePath msiexec -ArgumentList /qn, /i, $dest -Wait
|
||||||
|
`
|
||||||
|
const channelWindowsInstallScript = `
|
||||||
|
$winver = [System.Environment]::OSVersion.Version | % {"{0}.{1}" -f $_.Major,$_.Minor}
|
||||||
|
|
||||||
|
switch ($winver)
|
||||||
|
{
|
||||||
|
"6.0" {$machine_os = "2008"}
|
||||||
|
"6.1" {$machine_os = "2008r2"}
|
||||||
|
"6.2" {$machine_os = "2012"}
|
||||||
|
"6.3" {$machine_os = "2012"}
|
||||||
|
default {$machine_os = "2008r2"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([System.IntPtr]::Size -eq 4) {$machine_arch = "i686"} else {$machine_arch = "x86_64"}
|
||||||
|
|
||||||
|
$url = "http://omnitruck.chef.io/current/chef/download?p=windows&pv=$machine_os&m=$machine_arch&v=11.18.6"
|
||||||
$dest = [System.IO.Path]::GetTempFileName()
|
$dest = [System.IO.Path]::GetTempFileName()
|
||||||
$dest = [System.IO.Path]::ChangeExtension($dest, ".msi")
|
$dest = [System.IO.Path]::ChangeExtension($dest, ".msi")
|
||||||
$downloader = New-Object System.Net.WebClient
|
$downloader = New-Object System.Net.WebClient
|
||||||
|
|
|
@ -48,9 +48,6 @@ func applyFn(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, comm.Timeout())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// Get the source
|
// Get the source
|
||||||
src, deleteSource, err := getSrc(data)
|
src, deleteSource, err := getSrc(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -99,8 +96,11 @@ func getSrc(data *schema.ResourceData) (string, bool, error) {
|
||||||
|
|
||||||
// copyFiles is used to copy the files from a source to a destination
|
// copyFiles is used to copy the files from a source to a destination
|
||||||
func copyFiles(ctx context.Context, comm communicator.Communicator, src, dst string) error {
|
func copyFiles(ctx context.Context, comm communicator.Communicator, src, dst string) error {
|
||||||
|
retryCtx, cancel := context.WithTimeout(ctx, comm.Timeout())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
// Wait and retry until we establish the connection
|
// Wait and retry until we establish the connection
|
||||||
err := communicator.Retry(ctx, func() error {
|
err := communicator.Retry(retryCtx, func() error {
|
||||||
return comm.Connect(nil)
|
return comm.Connect(nil)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -231,10 +231,10 @@ func applyFn(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, comm.Timeout())
|
retryCtx, cancel := context.WithTimeout(ctx, comm.Timeout())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
err = communicator.Retry(ctx, func() error {
|
err = communicator.Retry(retryCtx, func() error {
|
||||||
return comm.Connect(o)
|
return comm.Connect(o)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -157,20 +157,23 @@ func runScripts(
|
||||||
comm communicator.Communicator,
|
comm communicator.Communicator,
|
||||||
scripts []io.ReadCloser) error {
|
scripts []io.ReadCloser) error {
|
||||||
|
|
||||||
// Wait for the context to end and then disconnect
|
retryCtx, cancel := context.WithTimeout(ctx, comm.Timeout())
|
||||||
go func() {
|
defer cancel()
|
||||||
<-ctx.Done()
|
|
||||||
comm.Disconnect()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Wait and retry until we establish the connection
|
// Wait and retry until we establish the connection
|
||||||
err := communicator.Retry(ctx, func() error {
|
err := communicator.Retry(retryCtx, func() error {
|
||||||
return comm.Connect(o)
|
return comm.Connect(o)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for the context to end and then disconnect
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
comm.Disconnect()
|
||||||
|
}()
|
||||||
|
|
||||||
for _, script := range scripts {
|
for _, script := range scripts {
|
||||||
var cmd *remote.Cmd
|
var cmd *remote.Cmd
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,15 @@ package remoteexec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/communicator"
|
||||||
|
"github.com/hashicorp/terraform/communicator/remote"
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
@ -206,6 +210,59 @@ func TestResourceProvider_CollectScripts_scriptsEmpty(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProvisionerTimeout(t *testing.T) {
|
||||||
|
o := new(terraform.MockUIOutput)
|
||||||
|
c := new(communicator.MockCommunicator)
|
||||||
|
|
||||||
|
disconnected := make(chan struct{})
|
||||||
|
c.DisconnectFunc = func() error {
|
||||||
|
close(disconnected)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
completed := make(chan struct{})
|
||||||
|
c.CommandFunc = func(cmd *remote.Cmd) error {
|
||||||
|
defer close(completed)
|
||||||
|
cmd.Init()
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
cmd.SetExitStatus(0, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c.ConnTimeout = time.Second
|
||||||
|
c.UploadScripts = map[string]string{"hello": "echo hello"}
|
||||||
|
c.RemoteScriptPath = "hello"
|
||||||
|
|
||||||
|
p := Provisioner().(*schema.Provisioner)
|
||||||
|
conf := map[string]interface{}{
|
||||||
|
"inline": []interface{}{"echo hello"},
|
||||||
|
}
|
||||||
|
|
||||||
|
scripts, err := collectScripts(schema.TestResourceDataRaw(
|
||||||
|
t, p.Schema, conf))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
if err := runScripts(ctx, o, c, scripts); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-disconnected:
|
||||||
|
t.Fatal("communicator disconnected before command completed")
|
||||||
|
case <-completed:
|
||||||
|
}
|
||||||
|
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
func testConfig(t *testing.T, c map[string]interface{}) *terraform.ResourceConfig {
|
func testConfig(t *testing.T, c map[string]interface{}) *terraform.ResourceConfig {
|
||||||
r, err := config.NewRawConfig(c)
|
r, err := config.NewRawConfig(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -131,17 +131,11 @@ func applyFn(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancelFunc := context.WithTimeout(ctx, comm.Timeout())
|
retryCtx, cancel := context.WithTimeout(ctx, comm.Timeout())
|
||||||
defer cancelFunc()
|
defer cancel()
|
||||||
|
|
||||||
// Wait for the context to end and then disconnect
|
|
||||||
go func() {
|
|
||||||
<-ctx.Done()
|
|
||||||
comm.Disconnect()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Wait and retry until we establish the connection
|
// Wait and retry until we establish the connection
|
||||||
err = communicator.Retry(ctx, func() error {
|
err = communicator.Retry(retryCtx, func() error {
|
||||||
return comm.Connect(o)
|
return comm.Connect(o)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -149,6 +143,12 @@ func applyFn(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for the context to end and then disconnect
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
comm.Disconnect()
|
||||||
|
}()
|
||||||
|
|
||||||
var src, dst string
|
var src, dst string
|
||||||
|
|
||||||
o.Output("Provisioning with Salt...")
|
o.Output("Provisioning with Salt...")
|
||||||
|
|
|
@ -81,6 +81,13 @@ func (c *ConsoleCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := opReq.StateLocker.Unlock(nil)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Setup the UI so we can output directly to stdout
|
// Setup the UI so we can output directly to stdout
|
||||||
ui := &cli.BasicUi{
|
ui := &cli.BasicUi{
|
||||||
Writer: wrappedstreams.Stdout(),
|
Writer: wrappedstreams.Stdout(),
|
||||||
|
|
|
@ -112,6 +112,13 @@ func (c *GraphCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := opReq.StateLocker.Unlock(nil)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Determine the graph type
|
// Determine the graph type
|
||||||
graphType := terraform.GraphTypePlan
|
graphType := terraform.GraphTypePlan
|
||||||
if plan != nil {
|
if plan != nil {
|
||||||
|
|
|
@ -184,6 +184,13 @@ func (c *ImportCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := opReq.StateLocker.Unlock(nil)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Perform the import. Note that as you can see it is possible for this
|
// Perform the import. Note that as you can see it is possible for this
|
||||||
// API to import more than one resource at once. For now, we only allow
|
// API to import more than one resource at once. For now, we only allow
|
||||||
// one while we stabilize this feature.
|
// one while we stabilize this feature.
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -175,11 +176,17 @@ func TestImport_remoteState(t *testing.T) {
|
||||||
"test_instance.foo",
|
"test_instance.foo",
|
||||||
"bar",
|
"bar",
|
||||||
}
|
}
|
||||||
|
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 0 {
|
||||||
fmt.Println(ui.OutputWriter)
|
fmt.Println(ui.OutputWriter)
|
||||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// verify that the local state was unlocked after import
|
||||||
|
if _, err := os.Stat(filepath.Join(td, fmt.Sprintf(".%s.lock.info", statePath))); !os.IsNotExist(err) {
|
||||||
|
t.Fatal("state left locked after import")
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that we were called
|
// Verify that we were called
|
||||||
if !configured {
|
if !configured {
|
||||||
t.Fatal("Configure should be called")
|
t.Fatal("Configure should be called")
|
||||||
|
|
|
@ -146,6 +146,13 @@ func (c *PushCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := opReq.StateLocker.Unlock(nil)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// Get the configuration
|
// Get the configuration
|
||||||
config := ctx.Module().Config()
|
config := ctx.Module().Config()
|
||||||
if name == "" {
|
if name == "" {
|
||||||
|
|
|
@ -18,6 +18,9 @@ type MockCommunicator struct {
|
||||||
Uploads map[string]string
|
Uploads map[string]string
|
||||||
UploadScripts map[string]string
|
UploadScripts map[string]string
|
||||||
UploadDirs map[string]string
|
UploadDirs map[string]string
|
||||||
|
CommandFunc func(*remote.Cmd) error
|
||||||
|
DisconnectFunc func() error
|
||||||
|
ConnTimeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect implementation of communicator.Communicator interface
|
// Connect implementation of communicator.Communicator interface
|
||||||
|
@ -27,11 +30,17 @@ func (c *MockCommunicator) Connect(o terraform.UIOutput) error {
|
||||||
|
|
||||||
// Disconnect implementation of communicator.Communicator interface
|
// Disconnect implementation of communicator.Communicator interface
|
||||||
func (c *MockCommunicator) Disconnect() error {
|
func (c *MockCommunicator) Disconnect() error {
|
||||||
|
if c.DisconnectFunc != nil {
|
||||||
|
return c.DisconnectFunc()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timeout implementation of communicator.Communicator interface
|
// Timeout implementation of communicator.Communicator interface
|
||||||
func (c *MockCommunicator) Timeout() time.Duration {
|
func (c *MockCommunicator) Timeout() time.Duration {
|
||||||
|
if c.ConnTimeout != 0 {
|
||||||
|
return c.ConnTimeout
|
||||||
|
}
|
||||||
return time.Duration(5 * time.Second)
|
return time.Duration(5 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +53,10 @@ func (c *MockCommunicator) ScriptPath() string {
|
||||||
func (c *MockCommunicator) Start(r *remote.Cmd) error {
|
func (c *MockCommunicator) Start(r *remote.Cmd) error {
|
||||||
r.Init()
|
r.Init()
|
||||||
|
|
||||||
|
if c.CommandFunc != nil {
|
||||||
|
return c.CommandFunc(r)
|
||||||
|
}
|
||||||
|
|
||||||
if !c.Commands[r.Command] {
|
if !c.Commands[r.Command] {
|
||||||
return fmt.Errorf("Command not found!")
|
return fmt.Errorf("Command not found!")
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"github.com/hashicorp/terraform/version"
|
"github.com/hashicorp/terraform/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUserAgent(t *testing.T) {
|
func TestNew_userAgent(t *testing.T) {
|
||||||
var actualUserAgent string
|
var actualUserAgent string
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
actualUserAgent = req.UserAgent()
|
actualUserAgent = req.UserAgent()
|
||||||
|
|
|
@ -2,15 +2,29 @@ package httpclient
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/version"
|
"github.com/hashicorp/terraform/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const userAgentFormat = "Terraform/%s"
|
const userAgentFormat = "Terraform/%s"
|
||||||
|
const uaEnvVar = "TF_APPEND_USER_AGENT"
|
||||||
|
|
||||||
func UserAgentString() string {
|
func UserAgentString() string {
|
||||||
return fmt.Sprintf(userAgentFormat, version.Version)
|
ua := fmt.Sprintf(userAgentFormat, version.Version)
|
||||||
|
|
||||||
|
if add := os.Getenv(uaEnvVar); add != "" {
|
||||||
|
add = strings.TrimSpace(add)
|
||||||
|
if len(add) > 0 {
|
||||||
|
ua += " " + add
|
||||||
|
log.Printf("[DEBUG] Using modified User-Agent: %s", ua)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ua
|
||||||
}
|
}
|
||||||
|
|
||||||
type userAgentRoundTripper struct {
|
type userAgentRoundTripper struct {
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
package httpclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserAgentString_env(t *testing.T) {
|
||||||
|
expectedBase := fmt.Sprintf(userAgentFormat, version.Version)
|
||||||
|
if oldenv, isSet := os.LookupEnv(uaEnvVar); isSet {
|
||||||
|
defer os.Setenv(uaEnvVar, oldenv)
|
||||||
|
} else {
|
||||||
|
defer os.Unsetenv(uaEnvVar)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range []struct {
|
||||||
|
expected string
|
||||||
|
additional string
|
||||||
|
}{
|
||||||
|
{expectedBase, ""},
|
||||||
|
{expectedBase, " "},
|
||||||
|
{expectedBase, " \n"},
|
||||||
|
|
||||||
|
{fmt.Sprintf("%s test/1", expectedBase), "test/1"},
|
||||||
|
{fmt.Sprintf("%s test/2", expectedBase), "test/2 "},
|
||||||
|
{fmt.Sprintf("%s test/3", expectedBase), " test/3 "},
|
||||||
|
{fmt.Sprintf("%s test/4", expectedBase), "test/4 \n"},
|
||||||
|
} {
|
||||||
|
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
||||||
|
if c.additional == "" {
|
||||||
|
os.Unsetenv(uaEnvVar)
|
||||||
|
} else {
|
||||||
|
os.Setenv(uaEnvVar, c.additional)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := UserAgentString()
|
||||||
|
|
||||||
|
if c.expected != actual {
|
||||||
|
t.Fatalf("Expected User-Agent '%s' does not match '%s'", c.expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -119,8 +119,20 @@ func (s *LocalState) RefreshState() error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
if s.PathOut == "" {
|
||||||
|
s.PathOut = s.Path
|
||||||
|
}
|
||||||
|
|
||||||
var reader io.Reader
|
var reader io.Reader
|
||||||
if !s.written {
|
|
||||||
|
// The s.Path file is only OK to read if we have not written any state out
|
||||||
|
// (in which case the same state needs to be read in), and no state output file
|
||||||
|
// has been opened (possibly via a lock) or the input path is different
|
||||||
|
// than the output path.
|
||||||
|
// This is important for Windows, as if the input file is the same as the
|
||||||
|
// output file, and the output file has been locked already, we can't open
|
||||||
|
// the file again.
|
||||||
|
if !s.written && (s.stateFileOut == nil || s.Path != s.PathOut) {
|
||||||
// we haven't written a state file yet, so load from Path
|
// we haven't written a state file yet, so load from Path
|
||||||
f, err := os.Open(s.Path)
|
f, err := os.Open(s.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -166,3 +166,42 @@ func testLocalState(t *testing.T) *LocalState {
|
||||||
|
|
||||||
return ls
|
return ls
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure we can refresh while the state is locked
|
||||||
|
func TestLocalState_refreshWhileLocked(t *testing.T) {
|
||||||
|
f, err := ioutil.TempFile("", "tf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = terraform.WriteState(TestStateInitial(), f)
|
||||||
|
f.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &LocalState{Path: f.Name()}
|
||||||
|
defer os.Remove(s.Path)
|
||||||
|
|
||||||
|
// lock first
|
||||||
|
info := NewLockInfo()
|
||||||
|
info.Operation = "test"
|
||||||
|
lockID, err := s.Lock(info)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := s.Unlock(lockID); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := s.RefreshState(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
readState := s.State()
|
||||||
|
if readState == nil || readState.Lineage == "" {
|
||||||
|
t.Fatal("missing state")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2969,6 +2969,28 @@ STATE:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure that outputs missing references due to targetting are removed from
|
||||||
|
// the graph.
|
||||||
|
func TestContext2Plan_outputContainsTargetedResource(t *testing.T) {
|
||||||
|
m := testModule(t, "plan-untargeted-resource-output")
|
||||||
|
p := testProvider("aws")
|
||||||
|
p.DiffFn = testDiffFn
|
||||||
|
ctx := testContext2(t, &ContextOpts{
|
||||||
|
Module: m,
|
||||||
|
ProviderResolver: ResourceProviderResolverFixed(
|
||||||
|
map[string]ResourceProviderFactory{
|
||||||
|
"aws": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Targets: []string{"module.mod.aws_instance.a"},
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := ctx.Plan()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// https://github.com/hashicorp/terraform/issues/4515
|
// https://github.com/hashicorp/terraform/issues/4515
|
||||||
func TestContext2Plan_targetedOverTen(t *testing.T) {
|
func TestContext2Plan_targetedOverTen(t *testing.T) {
|
||||||
m := testModule(t, "plan-targeted-over-ten")
|
m := testModule(t, "plan-targeted-over-ten")
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -1876,13 +1877,21 @@ var ErrNoState = errors.New("no state")
|
||||||
// ReadState reads a state structure out of a reader in the format that
|
// ReadState reads a state structure out of a reader in the format that
|
||||||
// was written by WriteState.
|
// was written by WriteState.
|
||||||
func ReadState(src io.Reader) (*State, error) {
|
func ReadState(src io.Reader) (*State, error) {
|
||||||
buf := bufio.NewReader(src)
|
// check for a nil file specifically, since that produces a platform
|
||||||
if _, err := buf.Peek(1); err != nil {
|
// specific error if we try to use it in a bufio.Reader.
|
||||||
// the error is either io.EOF or "invalid argument", and both are from
|
if f, ok := src.(*os.File); ok && f == nil {
|
||||||
// an empty state.
|
|
||||||
return nil, ErrNoState
|
return nil, ErrNoState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf := bufio.NewReader(src)
|
||||||
|
|
||||||
|
if _, err := buf.Peek(1); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil, ErrNoState
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if err := testForV0State(buf); err != nil {
|
if err := testForV0State(buf); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
module "mod" {
|
||||||
|
source = "./mod"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
resource "aws_instance" "c" {
|
||||||
|
name = "${module.mod.output}"
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
locals {
|
||||||
|
"one" = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "a" {
|
||||||
|
count = "${local.one}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "b" {
|
||||||
|
count = "${local.one}"
|
||||||
|
}
|
||||||
|
|
||||||
|
output "output" {
|
||||||
|
value = "${join("", coalescelist(aws_instance.a.*.id, aws_instance.b.*.id))}"
|
||||||
|
}
|
|
@ -217,6 +217,12 @@ func filterPartialOutputs(v interface{}, targetedNodes *dag.Set, g *Graph) bool
|
||||||
if _, ok := d.(*NodeCountBoundary); ok {
|
if _, ok := d.(*NodeCountBoundary); ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !targetedNodes.Include(d) {
|
||||||
|
// this one is going to be removed, so it doesn't count
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// as soon as we see a real dependency, we mark this as
|
// as soon as we see a real dependency, we mark this as
|
||||||
// non-removable
|
// non-removable
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -53,6 +53,12 @@ providers {
|
||||||
# two expressions match different versions then _both_ are included in
|
# two expressions match different versions then _both_ are included in
|
||||||
# the bundle archive.
|
# the bundle archive.
|
||||||
google = ["~> 1.0", "~> 2.0"]
|
google = ["~> 1.0", "~> 2.0"]
|
||||||
|
|
||||||
|
# Include a custom plugin to the bundle. Will search for the plugin in the
|
||||||
|
# plugins directory, and package it with the bundle archive. Plugin must have
|
||||||
|
# a name of the form: terraform-provider-*, and must be build with the operating
|
||||||
|
# system and architecture that terraform enterprise is running, e.g. linux and amd64
|
||||||
|
customplugin = ["0.1"]
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -100,6 +106,13 @@ this composite version number so that bundle archives can be easily
|
||||||
distinguished from official release archives and from each other when multiple
|
distinguished from official release archives and from each other when multiple
|
||||||
bundles contain the same core Terraform version.
|
bundles contain the same core Terraform version.
|
||||||
|
|
||||||
|
To include custom plugins in the bundle file, create a local directory "./plugins"
|
||||||
|
and put all the plugins you want to include there. Optionally, you can use the
|
||||||
|
`-plugin-dir` flag to specify a location where to find the plugins. To be recognized
|
||||||
|
as a valid plugin, the file must have a name of the form: "terraform-provider-*-v*". In
|
||||||
|
addition, ensure that the plugin is build using the same operating system and
|
||||||
|
architecture used for terraform enterprise. Typically this will be linux and amd64.
|
||||||
|
|
||||||
## Provider Resolution Behavior
|
## Provider Resolution Behavior
|
||||||
|
|
||||||
Terraform's provider resolution behavior is such that if a given constraint
|
Terraform's provider resolution behavior is such that if a given constraint
|
||||||
|
@ -112,13 +125,6 @@ of the versions available from the bundle. If a suitable version cannot be
|
||||||
found in the bundle, Terraform _will_ attempt to satisfy that dependency by
|
found in the bundle, Terraform _will_ attempt to satisfy that dependency by
|
||||||
automatic installation from the official repository.
|
automatic installation from the official repository.
|
||||||
|
|
||||||
To disable automatic installation altogether -- and thus cause a hard failure
|
|
||||||
if no local plugins match -- the `-plugin-dir` option can be passed to
|
|
||||||
`terraform init`, giving the directory into which the bundle was extracted.
|
|
||||||
The presence of this option overrides all of the normal automatic discovery
|
|
||||||
and installation behavior, and thus forces the use of only the plugins that
|
|
||||||
can be found in the directory indicated.
|
|
||||||
|
|
||||||
The downloaded provider archives are verified using the same signature check
|
The downloaded provider archives are verified using the same signature check
|
||||||
that is used for auto-installed plugins, using Hashicorp's release key. At
|
that is used for auto-installed plugins, using Hashicorp's release key. At
|
||||||
this time, the core Terraform archive itself is _not_ verified in this way;
|
this time, the core Terraform archive itself is _not_ verified in this way;
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
|
|
||||||
getter "github.com/hashicorp/go-getter"
|
getter "github.com/hashicorp/go-getter"
|
||||||
"github.com/hashicorp/terraform/plugin"
|
"github.com/hashicorp/terraform/plugin"
|
||||||
"github.com/hashicorp/terraform/plugin/discovery"
|
discovery "github.com/hashicorp/terraform/plugin/discovery"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,10 +23,66 @@ type PackageCommand struct {
|
||||||
ui cli.Ui
|
ui cli.Ui
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shameless stackoverflow copy + pasta https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang
|
||||||
|
func CopyFile(src, dst string) (err error) {
|
||||||
|
sfi, err := os.Stat(src)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !sfi.Mode().IsRegular() {
|
||||||
|
// cannot copy non-regular files (e.g., directories,
|
||||||
|
// symlinks, devices, etc.)
|
||||||
|
return fmt.Errorf("CopyFile: non-regular source file %s (%q)", sfi.Name(), sfi.Mode().String())
|
||||||
|
}
|
||||||
|
dfi, err := os.Stat(dst)
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !(dfi.Mode().IsRegular()) {
|
||||||
|
return fmt.Errorf("CopyFile: non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String())
|
||||||
|
}
|
||||||
|
if os.SameFile(sfi, dfi) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = os.Link(src, dst); err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = copyFileContents(src, dst)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// see above
|
||||||
|
func copyFileContents(src, dst string) (err error) {
|
||||||
|
in, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
out, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
cerr := out.Close()
|
||||||
|
if err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if _, err = io.Copy(out, in); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = out.Sync()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (c *PackageCommand) Run(args []string) int {
|
func (c *PackageCommand) Run(args []string) int {
|
||||||
flags := flag.NewFlagSet("package", flag.ExitOnError)
|
flags := flag.NewFlagSet("package", flag.ExitOnError)
|
||||||
osPtr := flags.String("os", "", "Target operating system")
|
osPtr := flags.String("os", "", "Target operating system")
|
||||||
archPtr := flags.String("arch", "", "Target CPU architecture")
|
archPtr := flags.String("arch", "", "Target CPU architecture")
|
||||||
|
pluginDirPtr := flags.String("plugin-dir", "", "Path to custom plugins directory")
|
||||||
err := flags.Parse(args)
|
err := flags.Parse(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ui.Error(err.Error())
|
c.ui.Error(err.Error())
|
||||||
|
@ -35,12 +91,16 @@ func (c *PackageCommand) Run(args []string) int {
|
||||||
|
|
||||||
osName := runtime.GOOS
|
osName := runtime.GOOS
|
||||||
archName := runtime.GOARCH
|
archName := runtime.GOARCH
|
||||||
|
pluginDir := "./plugins"
|
||||||
if *osPtr != "" {
|
if *osPtr != "" {
|
||||||
osName = *osPtr
|
osName = *osPtr
|
||||||
}
|
}
|
||||||
if *archPtr != "" {
|
if *archPtr != "" {
|
||||||
archName = *archPtr
|
archName = *archPtr
|
||||||
}
|
}
|
||||||
|
if *pluginDirPtr != "" {
|
||||||
|
pluginDir = *pluginDirPtr
|
||||||
|
}
|
||||||
|
|
||||||
if flags.NArg() != 1 {
|
if flags.NArg() != 1 {
|
||||||
c.ui.Error("Configuration filename is required")
|
c.ui.Error("Configuration filename is required")
|
||||||
|
@ -70,10 +130,17 @@ func (c *PackageCommand) Run(args []string) int {
|
||||||
|
|
||||||
coreZipURL := c.coreURL(config.Terraform.Version, osName, archName)
|
coreZipURL := c.coreURL(config.Terraform.Version, osName, archName)
|
||||||
err = getter.Get(workDir, coreZipURL)
|
err = getter.Get(workDir, coreZipURL)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ui.Error(fmt.Sprintf("Failed to fetch core package from %s: %s", coreZipURL, err))
|
c.ui.Error(fmt.Sprintf("Failed to fetch core package from %s: %s", coreZipURL, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.ui.Info(fmt.Sprintf("Fetching 3rd party plugins in directory: %s", pluginDir))
|
||||||
|
dirs := []string{pluginDir} //FindPlugins requires an array
|
||||||
|
localPlugins := discovery.FindPlugins("provider", dirs)
|
||||||
|
for k, _ := range localPlugins {
|
||||||
|
c.ui.Info(fmt.Sprintf("plugin: %s (%s)", k.Name, k.Version))
|
||||||
|
}
|
||||||
installer := &discovery.ProviderInstaller{
|
installer := &discovery.ProviderInstaller{
|
||||||
Dir: workDir,
|
Dir: workDir,
|
||||||
|
|
||||||
|
@ -92,19 +159,29 @@ func (c *PackageCommand) Run(args []string) int {
|
||||||
Ui: c.ui,
|
Ui: c.ui,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(config.Providers) > 0 {
|
for name, constraintStrs := range config.Providers {
|
||||||
c.ui.Output(fmt.Sprintf("Checking for available provider plugins on %s...",
|
for _, constraintStr := range constraintStrs {
|
||||||
discovery.GetReleaseHost()))
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, constraints := range config.Providers {
|
|
||||||
for _, constraint := range constraints {
|
|
||||||
c.ui.Output(fmt.Sprintf("- Resolving %q provider (%s)...",
|
c.ui.Output(fmt.Sprintf("- Resolving %q provider (%s)...",
|
||||||
name, constraint))
|
name, constraintStr))
|
||||||
_, err := installer.Get(name, constraint.MustParse())
|
foundPlugins := discovery.PluginMetaSet{}
|
||||||
if err != nil {
|
constraint := constraintStr.MustParse()
|
||||||
c.ui.Error(fmt.Sprintf("- Failed to resolve %s provider %s: %s", name, constraint, err))
|
for plugin, _ := range localPlugins {
|
||||||
return 1
|
if plugin.Name == name && constraint.Allows(plugin.Version.MustParse()) {
|
||||||
|
foundPlugins.Add(plugin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(foundPlugins) > 0 {
|
||||||
|
plugin := foundPlugins.Newest()
|
||||||
|
CopyFile(plugin.Path, workDir+"/terraform-provider-"+plugin.Name+"-v"+plugin.Version.MustParse().String()) //put into temp dir
|
||||||
|
} else { //attempt to get from the public registry if not found locally
|
||||||
|
c.ui.Output(fmt.Sprintf("- Checking for provider plugin on %s...",
|
||||||
|
discovery.GetReleaseHost()))
|
||||||
|
_, err := installer.Get(name, constraint)
|
||||||
|
if err != nil {
|
||||||
|
c.ui.Error(fmt.Sprintf("- Failed to resolve %s provider %s: %s", name, constraint, err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,11 +279,13 @@ current working directory containing a Terraform binary along with zero or
|
||||||
more provider plugin binaries.
|
more provider plugin binaries.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-os=name Target operating system the archive will be built for. Defaults
|
-os=name Target operating system the archive will be built for. Defaults
|
||||||
to that of the system where the command is being run.
|
to that of the system where the command is being run.
|
||||||
|
|
||||||
-arch=name Target CPU architecture the archive will be built for. Defaults
|
-arch=name Target CPU architecture the archive will be built for. Defaults
|
||||||
to that of the system where the command is being run.
|
to that of the system where the command is being run.
|
||||||
|
|
||||||
|
-plugin-dir=path The path to the custom plugins directory. Defaults to "./plugins".
|
||||||
|
|
||||||
The resulting zip file can be used to more easily install Terraform and
|
The resulting zip file can be used to more easily install Terraform and
|
||||||
a fixed set of providers together on a server, so that Terraform's provider
|
a fixed set of providers together on a server, so that Terraform's provider
|
||||||
|
@ -233,7 +312,13 @@ not a normal Terraform configuration file. The file format looks like this:
|
||||||
# Each item in these lists allows a distinct version to be added. If the
|
# Each item in these lists allows a distinct version to be added. If the
|
||||||
# two expressions match different versions then _both_ are included in
|
# two expressions match different versions then _both_ are included in
|
||||||
# the bundle archive.
|
# the bundle archive.
|
||||||
google = ["~> 1.0", "~> 2.0"]
|
google = ["~> 1.0", "~> 2.0"]
|
||||||
|
|
||||||
|
#Include a custom plugin to the bundle. Will search for the plugin in the
|
||||||
|
#plugins directory, and package it with the bundle archive. Plugin must have
|
||||||
|
#a name of the form: terraform-provider-*-v*, and must be built with the operating
|
||||||
|
#system and architecture that terraform enterprise is running, e.g. linux and amd64
|
||||||
|
customplugin = ["0.1"]
|
||||||
}
|
}
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// The main version number that is being run at the moment.
|
// The main version number that is being run at the moment.
|
||||||
const Version = "0.11.4"
|
const Version = "0.11.5"
|
||||||
|
|
||||||
// A pre-release marker for the version. If this is "" (empty string)
|
// A pre-release marker for the version. If this is "" (empty string)
|
||||||
// then it means that it is a final release. Otherwise, this is a pre-release
|
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||||
|
|
|
@ -51,7 +51,7 @@ a configuration in the future: create the new configuration and run
|
||||||
You do not need to specify every required argument in the backend configuration.
|
You do not need to specify every required argument in the backend configuration.
|
||||||
Omitting certain arguments may be desirable to avoid storing secrets, such as
|
Omitting certain arguments may be desirable to avoid storing secrets, such as
|
||||||
access keys, within the main configuration. When some or all of the arguments
|
access keys, within the main configuration. When some or all of the arguments
|
||||||
are ommitted, we call this a _partial configuration_.
|
are omitted, we call this a _partial configuration_.
|
||||||
|
|
||||||
With a partial configuration, the remaining configuration arguments must be
|
With a partial configuration, the remaining configuration arguments must be
|
||||||
provided as part of
|
provided as part of
|
||||||
|
|
|
@ -3,7 +3,7 @@ layout: "docs"
|
||||||
page_title: "Community Providers"
|
page_title: "Community Providers"
|
||||||
sidebar_current: "docs-providers-community"
|
sidebar_current: "docs-providers-community"
|
||||||
description: |-
|
description: |-
|
||||||
Category for database vendors.
|
Category for community-built providers.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Community Providers
|
# Community Providers
|
||||||
|
|
|
@ -3,7 +3,7 @@ layout: "docs"
|
||||||
page_title: "Infrastructure Software Providers"
|
page_title: "Infrastructure Software Providers"
|
||||||
sidebar_current: "docs-providers-infra"
|
sidebar_current: "docs-providers-infra"
|
||||||
description: |-
|
description: |-
|
||||||
Category for standard cloud vendors.
|
Category for infrastructure management vendors.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Infrastructure Software Providers
|
# Infrastructure Software Providers
|
||||||
|
|
|
@ -3,7 +3,7 @@ layout: "docs"
|
||||||
page_title: "Misc Providers"
|
page_title: "Misc Providers"
|
||||||
sidebar_current: "docs-providers-misc"
|
sidebar_current: "docs-providers-misc"
|
||||||
description: |-
|
description: |-
|
||||||
Category for database vendors.
|
Category for miscellaneous vendors.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Miscellaneous Providers
|
# Miscellaneous Providers
|
||||||
|
|
|
@ -3,7 +3,7 @@ layout: "docs"
|
||||||
page_title: "Network Providers"
|
page_title: "Network Providers"
|
||||||
sidebar_current: "docs-providers-network"
|
sidebar_current: "docs-providers-network"
|
||||||
description: |-
|
description: |-
|
||||||
Category for netowrk vendors.
|
Category for network vendors.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Network Providers
|
# Network Providers
|
||||||
|
|
|
@ -65,6 +65,9 @@ The following arguments are supported:
|
||||||
for the new node. These can also be loaded from a file on disk using the [`file()`
|
for the new node. These can also be loaded from a file on disk using the [`file()`
|
||||||
interpolation function](/docs/configuration/interpolation.html#file_path_).
|
interpolation function](/docs/configuration/interpolation.html#file_path_).
|
||||||
|
|
||||||
|
* `channel (string)` - (Optional) The Chef Client release channel to install from. If not
|
||||||
|
set, the `stable` channel will be used.
|
||||||
|
|
||||||
* `client_options (array)` - (Optional) A list of optional Chef Client configuration
|
* `client_options (array)` - (Optional) A list of optional Chef Client configuration
|
||||||
options. See the [Chef Client ](https://docs.chef.io/config_rb_client.html) documentation
|
options. See the [Chef Client ](https://docs.chef.io/config_rb_client.html) documentation
|
||||||
for all available options.
|
for all available options.
|
||||||
|
|
Loading…
Reference in New Issue