Merge remote-tracking branch 'upstream/master' into patch-1
* upstream/master: (66 commits) lang/eval: more evalContext fixups Update CHANGELOG.md Cleanup after v0.12.10 release v0.12.10 website / help: reconcile 'validate' command docs website: Document behavior of `self` object for provisioners vendor: switch to HCL 2.0 in the HCL repository Update communicator/ssh/communicator.go copy client pointer for keep-alive loop Update CHANGELOG.md slow down tfce polling to 1s typos. some code, some text. Remove -check-variables flag from the docs Merge cleanup, remove `license` parameter in favor of bool `accept_license`, adjust how license acceptance is done, update hab provisioner doc. vendor latest go-tfe clean up go mod for go-tfe tfce test additions update to go-tfe 0.3.23 cost estimation status polling go-tfe dep update to 0.3.22 ...
This commit is contained in:
commit
470de23a64
|
@ -15,35 +15,35 @@ Specifically, we have provided checklists below for each type of issue and pull
|
||||||
request that can happen on the project. These checklists represent everything
|
request that can happen on the project. These checklists represent everything
|
||||||
we need to be able to review and respond quickly.
|
we need to be able to review and respond quickly.
|
||||||
|
|
||||||
## HashiCorp vs. Community Providers
|
## HashiCorp, Official, and Community Providers
|
||||||
|
|
||||||
We separate providers out into what we call "HashiCorp Providers" and
|
We separate providers out into what we call "HashiCorp Providers", "Partner Providers" and "Community Providers".
|
||||||
"Community Providers".
|
|
||||||
|
|
||||||
HashiCorp providers are providers that we'll dedicate full time resources to
|
HashiCorp providers are providers that we dedicate full time engineers to
|
||||||
improving, supporting the latest features, and fixing bugs. These are providers
|
improving, supporting the latest features, and fixing bugs. These are providers
|
||||||
we understand deeply and are confident we have the resources to manage
|
we understand deeply and are confident we have the resources to manage
|
||||||
ourselves.
|
ourselves.
|
||||||
|
|
||||||
Community providers are providers where we depend on the community to
|
Partner providers are providers where we depend on our partners to
|
||||||
contribute fixes and enhancements to improve. HashiCorp will run automated
|
contribute fixes and enhancements to improve. HashiCorp will run automated
|
||||||
tests and ensure these providers continue to work, but will not dedicate full
|
tests and ensure these providers continue to work, but will not dedicate full
|
||||||
time resources to add new features to these providers. These providers are
|
time engineers to add new features to these providers. These providers are
|
||||||
available in official Terraform releases, but the functionality is primarily
|
available in official Terraform releases, but the functionality is primarily
|
||||||
contributed.
|
contributed.
|
||||||
|
|
||||||
The current list of HashiCorp Providers is as follows:
|
All HashiCorp and Partner providers can be found in the (terraform-providers github organization)[https://github.com/terraform-providers].
|
||||||
|
Any provider issues should be opened in the provider's repository.
|
||||||
|
|
||||||
* `aws`
|
Our testing standards are the same for both HashiCorp and Official providers,
|
||||||
* `azurerm`
|
|
||||||
* `google`
|
|
||||||
* `opc`
|
|
||||||
|
|
||||||
Our testing standards are the same for both HashiCorp and Community providers,
|
|
||||||
and HashiCorp runs full acceptance test suites for every provider nightly to
|
and HashiCorp runs full acceptance test suites for every provider nightly to
|
||||||
ensure Terraform remains stable.
|
ensure Terraform remains stable.
|
||||||
|
|
||||||
We make the distinction between these two types of providers to help
|
Community Providers are providers that are neither maintained nor tested by
|
||||||
|
HashiCorp. We can make no promises that these providers will work with any given
|
||||||
|
version of Terraform. These providers are not automatically installed by
|
||||||
|
`terraform init` and instead require manual installation.
|
||||||
|
|
||||||
|
We make the distinction between these types of providers to help
|
||||||
highlight the vast amounts of community effort that goes in to making Terraform
|
highlight the vast amounts of community effort that goes in to making Terraform
|
||||||
great, and to help contributors better understand the role HashiCorp employees
|
great, and to help contributors better understand the role HashiCorp employees
|
||||||
play in the various areas of the code base.
|
play in the various areas of the code base.
|
||||||
|
@ -52,9 +52,8 @@ play in the various areas of the code base.
|
||||||
|
|
||||||
### Issue Reporting Checklists
|
### Issue Reporting Checklists
|
||||||
|
|
||||||
We welcome issues of all kinds including feature requests, bug reports, and
|
We welcome feature requests and bug reports. Below you'll find checklists with
|
||||||
general questions. Below you'll find checklists with guidelines for well-formed
|
guidelines for well-formed issues of each type.
|
||||||
issues of each type.
|
|
||||||
|
|
||||||
#### Bug Reports
|
#### Bug Reports
|
||||||
|
|
||||||
|
@ -88,12 +87,15 @@ issues of each type.
|
||||||
|
|
||||||
#### Questions
|
#### Questions
|
||||||
|
|
||||||
- [ ] __Search for answers in Terraform documentation__: We're happy to answer
|
Please do not use GitHub to ask questions! Instead:
|
||||||
questions in GitHub Issues, but it helps reduce issue churn and maintainer
|
|
||||||
workload if you work to find answers to common questions in the
|
* __Search for answers in Terraform documentation__
|
||||||
documentation. Often times Question issues result in documentation updates
|
|
||||||
to help future users, so if you don't find an answer, you can give us
|
* __Ask in the Community Forum__: Use [the community forum](https://discuss.hashicorp.com/c/terraform-core) for questions not answered by the documentation.
|
||||||
pointers for where you'd expect to see it in the docs.
|
|
||||||
|
* __Request an update to the documentation__: If you find that the
|
||||||
|
documentation is confusing or incorrect, open an issue (or a pull request) and
|
||||||
|
let us know.
|
||||||
|
|
||||||
### Issue Lifecycle
|
### Issue Lifecycle
|
||||||
|
|
||||||
|
@ -121,8 +123,6 @@ issues of each type.
|
||||||
Thank you for contributing! Here you'll find information on what to include in
|
Thank you for contributing! Here you'll find information on what to include in
|
||||||
your Pull Request to ensure it is accepted quickly.
|
your Pull Request to ensure it is accepted quickly.
|
||||||
|
|
||||||
* For pull requests that follow the guidelines, we expect to be able to review
|
|
||||||
and merge very quickly.
|
|
||||||
* Pull requests that don't follow the guidelines will be annotated with what
|
* Pull requests that don't follow the guidelines will be annotated with what
|
||||||
they're missing. A community or core team member may be able to swing around
|
they're missing. A community or core team member may be able to swing around
|
||||||
and help finish up the work, but these PRs will generally hang out much
|
and help finish up the work, but these PRs will generally hang out much
|
||||||
|
@ -170,82 +170,15 @@ easy for anybody to help us improve our docs.
|
||||||
site immediately, or is it referencing an upcoming version of Terraform and
|
site immediately, or is it referencing an upcoming version of Terraform and
|
||||||
should get pushed out with the next release?
|
should get pushed out with the next release?
|
||||||
|
|
||||||
#### Enhancement/Bugfix to a Resource
|
|
||||||
|
|
||||||
Working on existing resources is a great way to get started as a Terraform
|
|
||||||
contributor because you can work within existing code and tests to get a feel
|
|
||||||
for what to do.
|
|
||||||
|
|
||||||
- [ ] __Acceptance test coverage of new behavior__: Existing resources each
|
|
||||||
have a set of [acceptance tests][acctests] covering their functionality.
|
|
||||||
These tests should exercise all the behavior of the resource. Whether you are
|
|
||||||
adding something or fixing a bug, the idea is to have an acceptance test that
|
|
||||||
fails if your code were to be removed. Sometimes it is sufficient to
|
|
||||||
"enhance" an existing test by adding an assertion or tweaking the config
|
|
||||||
that is used, but often a new test is better to add. You can copy/paste an
|
|
||||||
existing test and follow the conventions you see there, modifying the test
|
|
||||||
to exercise the behavior of your code.
|
|
||||||
- [ ] __Documentation updates__: If your code makes any changes that need to
|
|
||||||
be documented, you should include those doc updates in the same PR. The
|
|
||||||
[Terraform website][website] source is in this repo and includes
|
|
||||||
instructions for getting a local copy of the site up and running if you'd
|
|
||||||
like to preview your changes.
|
|
||||||
- [ ] __Well-formed Code__: Do your best to follow existing conventions you
|
|
||||||
see in the codebase, and ensure your code is formatted with `go fmt`. (The
|
|
||||||
Travis CI build will fail if `go fmt` has not been run on incoming code.)
|
|
||||||
The PR reviewers can help out on this front, and may provide comments with
|
|
||||||
suggestions on how to improve the code.
|
|
||||||
|
|
||||||
#### New Resource
|
|
||||||
|
|
||||||
Implementing a new resource is a good way to learn more about how Terraform
|
|
||||||
interacts with upstream APIs. There are plenty of examples to draw from in the
|
|
||||||
existing resources, but you still get to implement something completely new.
|
|
||||||
|
|
||||||
- [ ] __Minimal LOC__: It can be inefficient for both the reviewer
|
|
||||||
and author to go through long feedback cycles on a big PR with many
|
|
||||||
resources. We therefore encourage you to only submit **1 resource at a time**.
|
|
||||||
- [ ] __Acceptance tests__: New resources should include acceptance tests
|
|
||||||
covering their behavior. See [Writing Acceptance
|
|
||||||
Tests](#writing-acceptance-tests) below for a detailed guide on how to
|
|
||||||
approach these.
|
|
||||||
- [ ] __Documentation__: Each resource gets a page in the Terraform
|
|
||||||
documentation. The [Terraform website][website] source is in this
|
|
||||||
repo and includes instructions for getting a local copy of the site up and
|
|
||||||
running if you'd like to preview your changes. For a resource, you'll want
|
|
||||||
to add a new file in the appropriate place and add a link to the sidebar for
|
|
||||||
that page.
|
|
||||||
- [ ] __Well-formed Code__: Do your best to follow existing conventions you
|
|
||||||
see in the codebase, and ensure your code is formatted with `go fmt`. (The
|
|
||||||
Travis CI build will fail if `go fmt` has not been run on incoming code.)
|
|
||||||
The PR reviewers can help out on this front, and may provide comments with
|
|
||||||
suggestions on how to improve the code.
|
|
||||||
|
|
||||||
#### New Provider
|
#### New Provider
|
||||||
|
|
||||||
Implementing a new provider gives Terraform the ability to manage resources in
|
Implementing a new provider gives Terraform the ability to manage resources in
|
||||||
a whole new API. It's a larger undertaking, but brings major new functionality
|
a whole new API. It's a larger undertaking, but brings major new functionality
|
||||||
into Terraform.
|
into Terraform.
|
||||||
|
|
||||||
- [ ] __Minimal initial LOC__: Some providers may be big and it can be
|
Terraform Providers are external plugins, not in the Terraform codebase. Please
|
||||||
inefficient for both reviewer & author to go through long feedback cycles
|
see the [Provider Development Program](https://www.terraform.io/guides/terraform-provider-development-program.html) documentation if you are interested in
|
||||||
on a big PR with many resources. We encourage you to only submit
|
submitting a new provider.
|
||||||
the necessary minimum in a single PR, ideally **just the first resource**
|
|
||||||
of the provider.
|
|
||||||
- [ ] __Acceptance tests__: Each provider should include an acceptance test
|
|
||||||
suite with tests for each resource should include acceptance tests covering
|
|
||||||
its behavior. See [Writing Acceptance Tests](#writing-acceptance-tests) below
|
|
||||||
for a detailed guide on how to approach these.
|
|
||||||
- [ ] __Documentation__: Each provider has a section in the Terraform
|
|
||||||
documentation. The [Terraform website][website] source is in this repo and
|
|
||||||
includes instructions for getting a local copy of the site up and running if
|
|
||||||
you'd like to preview your changes. For a provider, you'll want to add new
|
|
||||||
index file and individual pages for each resource.
|
|
||||||
- [ ] __Well-formed Code__: Do your best to follow existing conventions you
|
|
||||||
see in the codebase, and ensure your code is formatted with `go fmt`. (The
|
|
||||||
Travis CI build will fail if `go fmt` has not been run on incoming code.)
|
|
||||||
The PR reviewers can help out on this front, and may provide comments with
|
|
||||||
suggestions on how to improve the code.
|
|
||||||
|
|
||||||
#### Core Bugfix/Enhancement
|
#### Core Bugfix/Enhancement
|
||||||
|
|
||||||
|
@ -281,7 +214,9 @@ get feedback early and often on the effort.
|
||||||
is complicated enough that there are often several ways to implement
|
is complicated enough that there are often several ways to implement
|
||||||
something, each of which has different implications and tradeoffs. Working
|
something, each of which has different implications and tradeoffs. Working
|
||||||
through a plan of attack with the team before you dive into implementation
|
through a plan of attack with the team before you dive into implementation
|
||||||
will help ensure that you're working in the right direction.
|
will help ensure that you're working in the right direction. Opening a GitHub
|
||||||
|
issue, or commenting on an existing issue, is a great way to get these
|
||||||
|
conversations started.
|
||||||
- [ ] __Unit tests__: Terraform's core is covered by hundreds of unit tests at
|
- [ ] __Unit tests__: Terraform's core is covered by hundreds of unit tests at
|
||||||
several different layers of abstraction. Generally the best place to start
|
several different layers of abstraction. Generally the best place to start
|
||||||
is with a "Context Test". These are higher level test that interact
|
is with a "Context Test". These are higher level test that interact
|
||||||
|
@ -304,9 +239,6 @@ get feedback early and often on the effort.
|
||||||
|
|
||||||
### Writing Acceptance Tests
|
### Writing Acceptance Tests
|
||||||
|
|
||||||
Terraform includes an acceptance test harness that does most of the repetitive
|
|
||||||
work involved in testing a resource.
|
|
||||||
|
|
||||||
#### Acceptance Tests Often Cost Money to Run
|
#### Acceptance Tests Often Cost Money to Run
|
||||||
|
|
||||||
Because acceptance tests create real resources, they often cost money to run.
|
Because acceptance tests create real resources, they often cost money to run.
|
||||||
|
@ -326,199 +258,8 @@ Acceptance tests can be run using the `testacc` target in the Terraform
|
||||||
expression. Prior to running the tests provider configuration details such as
|
expression. Prior to running the tests provider configuration details such as
|
||||||
access keys must be made available as environment variables.
|
access keys must be made available as environment variables.
|
||||||
|
|
||||||
For example, to run an acceptance test against the Azure Resource Manager
|
|
||||||
provider, the following environment variables must be set:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
export ARM_SUBSCRIPTION_ID=...
|
|
||||||
export ARM_CLIENT_ID=...
|
|
||||||
export ARM_CLIENT_SECRET=...
|
|
||||||
export ARM_TENANT_ID=...
|
|
||||||
```
|
|
||||||
|
|
||||||
Tests can then be run by specifying the target provider and a regular
|
|
||||||
expression defining the tests to run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ make testacc TEST=./builtin/providers/azurerm TESTARGS='-run=TestAccAzureRMPublicIpStatic_update'
|
|
||||||
==> Checking that code complies with gofmt requirements...
|
|
||||||
go generate ./...
|
|
||||||
TF_ACC=1 go test ./builtin/providers/azurerm -v -run=TestAccAzureRMPublicIpStatic_update -timeout 120m
|
|
||||||
=== RUN TestAccAzureRMPublicIpStatic_update
|
|
||||||
--- PASS: TestAccAzureRMPublicIpStatic_update (177.48s)
|
|
||||||
PASS
|
|
||||||
ok github.com/hashicorp/terraform/builtin/providers/azurerm 177.504s
|
|
||||||
```
|
|
||||||
|
|
||||||
Entire resource test suites can be targeted by using the naming convention to
|
|
||||||
write the regular expression. For example, to run all tests of the
|
|
||||||
`azurerm_public_ip` resource rather than just the update test, you can start
|
|
||||||
testing like this:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ make testacc TEST=./builtin/providers/azurerm TESTARGS='-run=TestAccAzureRMPublicIpStatic'
|
|
||||||
==> Checking that code complies with gofmt requirements...
|
|
||||||
go generate ./...
|
|
||||||
TF_ACC=1 go test ./builtin/providers/azurerm -v -run=TestAccAzureRMPublicIpStatic -timeout 120m
|
|
||||||
=== RUN TestAccAzureRMPublicIpStatic_basic
|
|
||||||
--- PASS: TestAccAzureRMPublicIpStatic_basic (137.74s)
|
|
||||||
=== RUN TestAccAzureRMPublicIpStatic_update
|
|
||||||
--- PASS: TestAccAzureRMPublicIpStatic_update (180.63s)
|
|
||||||
PASS
|
|
||||||
ok github.com/hashicorp/terraform/builtin/providers/azurerm 318.392s
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Writing an Acceptance Test
|
|
||||||
|
|
||||||
Terraform has a framework for writing acceptance tests which minimises the
|
|
||||||
amount of boilerplate code necessary to use common testing patterns. The entry
|
|
||||||
point to the framework is the `resource.Test()` function.
|
|
||||||
|
|
||||||
Tests are divided into `TestStep`s. Each `TestStep` proceeds by applying some
|
|
||||||
Terraform configuration using the provider under test, and then verifying that
|
|
||||||
results are as expected by making assertions using the provider API. It is
|
|
||||||
common for a single test function to exercise both the creation of and updates
|
|
||||||
to a single resource. Most tests follow a similar structure.
|
|
||||||
|
|
||||||
1. Pre-flight checks are made to ensure that sufficient provider configuration
|
|
||||||
is available to be able to proceed - for example in an acceptance test
|
|
||||||
targeting AWS, `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` must be set prior
|
|
||||||
to running acceptance tests. This is common to all tests exercising a single
|
|
||||||
provider.
|
|
||||||
|
|
||||||
Each `TestStep` is defined in the call to `resource.Test()`. Most assertion
|
|
||||||
functions are defined out of band with the tests. This keeps the tests
|
|
||||||
readable, and allows reuse of assertion functions across different tests of the
|
|
||||||
same type of resource. The definition of a complete test looks like this:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func TestAccAzureRMPublicIpStatic_update(t *testing.T) {
|
|
||||||
resource.Test(t, resource.TestCase{
|
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
|
||||||
Providers: testAccProviders,
|
|
||||||
CheckDestroy: testCheckAzureRMPublicIpDestroy,
|
|
||||||
Steps: []resource.TestStep{
|
|
||||||
resource.TestStep{
|
|
||||||
Config: testAccAzureRMVPublicIpStatic_basic,
|
|
||||||
Check: resource.ComposeTestCheckFunc(
|
|
||||||
testCheckAzureRMPublicIpExists("azurerm_public_ip.test"),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
When executing the test, the following steps are taken for each `TestStep`:
|
|
||||||
|
|
||||||
1. The Terraform configuration required for the test is applied. This is
|
|
||||||
responsible for configuring the resource under test, and any dependencies it
|
|
||||||
may have. For example, to test the `azurerm_public_ip` resource, an
|
|
||||||
`azurerm_resource_group` is required. This results in configuration which
|
|
||||||
looks like this:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
resource "azurerm_resource_group" "test" {
|
|
||||||
name = "acceptanceTestResourceGroup1"
|
|
||||||
location = "West US"
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "azurerm_public_ip" "test" {
|
|
||||||
name = "acceptanceTestPublicIp1"
|
|
||||||
location = "West US"
|
|
||||||
resource_group_name = "${azurerm_resource_group.test.name}"
|
|
||||||
public_ip_address_allocation = "static"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
1. Assertions are run using the provider API. These use the provider API
|
|
||||||
directly rather than asserting against the resource state. For example, to
|
|
||||||
verify that the `azurerm_public_ip` described above was created
|
|
||||||
successfully, a test function like this is used:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func testCheckAzureRMPublicIpExists(name string) resource.TestCheckFunc {
|
|
||||||
return func(s *terraform.State) error {
|
|
||||||
// Ensure we have enough information in state to look up in API
|
|
||||||
rs, ok := s.RootModule().Resources[name]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("Not found: %s", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
publicIPName := rs.Primary.Attributes["name"]
|
|
||||||
resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"]
|
|
||||||
if !hasResourceGroup {
|
|
||||||
return fmt.Errorf("Bad: no resource group found in state for public ip: %s", availSetName)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := testAccProvider.Meta().(*ArmClient).publicIPClient
|
|
||||||
|
|
||||||
resp, err := conn.Get(resourceGroup, publicIPName, "")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Bad: Get on publicIPClient: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusNotFound {
|
|
||||||
return fmt.Errorf("Bad: Public IP %q (resource group: %q) does not exist", name, resourceGroup)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Notice that the only information used from the Terraform state is the ID of
|
|
||||||
the resource - though in this case it is necessary to split the ID into
|
|
||||||
constituent parts in order to use the provider API. For computed properties,
|
|
||||||
we instead assert that the value saved in the Terraform state was the
|
|
||||||
expected value if possible. The testing framework provides helper functions
|
|
||||||
for several common types of check - for example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
resource.TestCheckResourceAttr("azurerm_public_ip.test", "domain_name_label", "mylabel01"),
|
|
||||||
```
|
|
||||||
|
|
||||||
1. The resources created by the test are destroyed. This step happens
|
|
||||||
automatically, and is the equivalent of calling `terraform destroy`.
|
|
||||||
|
|
||||||
1. Assertions are made against the provider API to verify that the resources
|
|
||||||
have indeed been removed. If these checks fail, the test fails and reports
|
|
||||||
"dangling resources". The code to ensure that the `azurerm_public_ip` shown
|
|
||||||
above looks like this:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func testCheckAzureRMPublicIpDestroy(s *terraform.State) error {
|
|
||||||
conn := testAccProvider.Meta().(*ArmClient).publicIPClient
|
|
||||||
|
|
||||||
for _, rs := range s.RootModule().Resources {
|
|
||||||
if rs.Type != "azurerm_public_ip" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
name := rs.Primary.Attributes["name"]
|
|
||||||
resourceGroup := rs.Primary.Attributes["resource_group_name"]
|
|
||||||
|
|
||||||
resp, err := conn.Get(resourceGroup, name, "")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusNotFound {
|
|
||||||
return fmt.Errorf("Public IP still exists:\n%#v", resp.Properties)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
These functions usually test only for the resource directly under test: we
|
|
||||||
skip the check that the `azurerm_resource_group` has been destroyed when
|
|
||||||
testing `azurerm_resource_group`, under the assumption that
|
|
||||||
`azurerm_resource_group` is tested independently in its own acceptance
|
|
||||||
tests.
|
|
||||||
|
|
||||||
[website]: https://github.com/hashicorp/terraform/tree/master/website
|
[website]: https://github.com/hashicorp/terraform/tree/master/website
|
||||||
[acctests]: https://github.com/hashicorp/terraform#acceptance-tests
|
[acctests]: https://github.com/hashicorp/terraform#acceptance-tests
|
||||||
|
[community forum]: https://discuss.hashicorp.com/c/terraform-core
|
||||||
[ml]: https://groups.google.com/group/terraform-tool
|
[ml]: https://groups.google.com/group/terraform-tool
|
||||||
|
|
44
CHANGELOG.md
44
CHANGELOG.md
|
@ -1,17 +1,37 @@
|
||||||
## 0.12.9 (Unreleased)
|
## 0.12.11 (Unreleased)
|
||||||
|
|
||||||
NOTES:
|
|
||||||
* core: `ignore_changes` is now processed (in addition to existing behaviors) before the provider plan is run. This means that users may see fewer planned changes when using `ignore_changes`, as before this change, changes to ignored attributes were still being sent to CustomizeDiff in providers (which could mean cascading changes for some resources). This should be indicative that providers are no longer getting changes that were marked as ignored, but if unexpected plans are seen while using `ignore_changes`, investigate the settings in the `ignore_changes` block to ensure the appropriate attributes are set. [GH-22520]
|
|
||||||
|
|
||||||
ENHANCEMENTS:
|
|
||||||
* provisioners/habitat: `accept_license` argument available to automate accepting the EULA, now required by this client [GH-22745]
|
|
||||||
* config: add source addressing to unknown value errors in `for_each` [GH-22760]
|
|
||||||
|
|
||||||
BUG FIXES:
|
BUG FIXES:
|
||||||
* command/console: support -var and -var-file flags [GH-22145]
|
|
||||||
* command/show: Fixed bug with wrong errors being returned or swallowed. [GH-22772]
|
* config: Clean up orphan modules in the presence of -target [GH-21313]
|
||||||
* config: The `cidrhost`, `cidrsubnet`, and `cidrnetmask` functions now behave correctly with IPv6 prefixes that are short enough for the host portion to be greater than 64-bit or 32-bit (depending on the target architecture). [GH-22505]
|
|
||||||
* config: Fixed bug on empty sets with `for_each` [GH-22281]
|
## 0.12.10 (October 07, 2019)
|
||||||
|
|
||||||
|
ENHANCEMENTS:
|
||||||
|
|
||||||
|
* `terraform plan` and `terraform apply` will now warn when the `-target` option is used, to draw attention to the fact that the result of applying the plan is likely to be incomplete, and to remind to re-run `terraform plan` with no targets afterwards to ensure that the configuration has converged. ([#22783](https://github.com/hashicorp/terraform/issues/22783))
|
||||||
|
* config: New function `parseint` for parsing strings containing digits as integers in various bases. ([#22747](https://github.com/hashicorp/terraform/issues/22747))
|
||||||
|
* config: New function `cidrsubnets`, which is a companion to the existing function `cidrsubnet` which can allocate multiple consecutive subnet prefixes (possibly of different prefix lengths) in a single call. ([#22858](https://github.com/hashicorp/terraform/issues/22858))
|
||||||
|
* backend/google: The GCS backend now supports OAuth2 token authentication. ([#21772](https://github.com/hashicorp/terraform/issues/21772))
|
||||||
|
* provisioner/habitat: Multiple updates and fixes, see PR for details ([#22705](https://github.com/hashicorp/terraform/issues/22705))
|
||||||
|
|
||||||
|
BUG FIXES:
|
||||||
|
|
||||||
|
* backend/manta: fix panic when `insecure_skip_tls_verify` was not set ([#22918](https://github.com/hashicorp/terraform/issues/22918))
|
||||||
|
|
||||||
|
## 0.12.9 (September 17, 2019)
|
||||||
|
|
||||||
|
NOTES:
|
||||||
|
* core: `ignore_changes` is now processed (in addition to existing behaviors) before the provider plan is run. This means that users may see fewer planned changes when using `ignore_changes`, as before this change, changes to ignored attributes were still being sent to CustomizeDiff in providers (which could mean cascading changes for some resources). This should be indicative that providers are no longer getting changes that were marked as ignored, but if unexpected plans are seen while using `ignore_changes`, investigate the settings in the `ignore_changes` block to ensure the appropriate attributes are set. ([#22520](https://github.com/hashicorp/terraform/issues/22520))
|
||||||
|
|
||||||
|
ENHANCEMENTS:
|
||||||
|
* provisioners/habitat: `accept_license` argument available to automate accepting the EULA, now required by this client ([#22745](https://github.com/hashicorp/terraform/issues/22745))
|
||||||
|
* config: add source addressing to unknown value errors in `for_each` ([#22760](https://github.com/hashicorp/terraform/issues/22760))
|
||||||
|
|
||||||
|
BUG FIXES:
|
||||||
|
* command/console: support -var and -var-file flags ([#22145](https://github.com/hashicorp/terraform/issues/22145))
|
||||||
|
* command/show: Fixed bug with wrong errors being returned or swallowed. ([#22772](https://github.com/hashicorp/terraform/issues/22772))
|
||||||
|
* config: The `cidrhost`, `cidrsubnet`, and `cidrnetmask` functions now behave correctly with IPv6 prefixes that are short enough for the host portion to be greater than 64-bit or 32-bit (depending on the target architecture). ([#22505](https://github.com/hashicorp/terraform/issues/22505))
|
||||||
|
* config: Fixed bug on empty sets with `for_each` ([#22281](https://github.com/hashicorp/terraform/issues/22281))
|
||||||
|
|
||||||
## 0.12.8 (September 04, 2019)
|
## 0.12.8 (September 04, 2019)
|
||||||
|
|
||||||
|
|
|
@ -112,8 +112,7 @@ To update a dependency:
|
||||||
|
|
||||||
Terraform has a comprehensive [acceptance
|
Terraform has a comprehensive [acceptance
|
||||||
test](http://en.wikipedia.org/wiki/Acceptance_testing) suite covering the
|
test](http://en.wikipedia.org/wiki/Acceptance_testing) suite covering the
|
||||||
built-in providers. Our [Contributing Guide](https://github.com/hashicorp/terraform/blob/master/.github/CONTRIBUTING.md) includes details about how and when to write and run acceptance tests in order to help contributions get accepted quickly.
|
built-in providers.
|
||||||
|
|
||||||
|
|
||||||
### Cross Compilation and Building for Distribution
|
### Cross Compilation and Building for Distribution
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"github.com/zclconf/go-cty/cty/gocty"
|
"github.com/zclconf/go-cty/cty/gocty"
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ package addrs
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -290,7 +290,7 @@ func parseResourceRef(mode ResourceMode, startRange hcl.Range, traversal hcl.Tra
|
||||||
// of the resource, but we don't have enough context here to decide
|
// of the resource, but we don't have enough context here to decide
|
||||||
// so we'll let the caller resolve that ambiguity.
|
// so we'll let the caller resolve that ambiguity.
|
||||||
return &Reference{
|
return &Reference{
|
||||||
Subject: resourceInstAddr,
|
Subject: resourceAddr,
|
||||||
SourceRange: tfdiags.SourceRangeFromHCL(rng),
|
SourceRange: tfdiags.SourceRangeFromHCL(rng),
|
||||||
}, diags
|
}, diags
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-test/deep"
|
"github.com/go-test/deep"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
@ -114,12 +114,10 @@ func TestParseRef(t *testing.T) {
|
||||||
{
|
{
|
||||||
`data.external.foo`,
|
`data.external.foo`,
|
||||||
&Reference{
|
&Reference{
|
||||||
Subject: ResourceInstance{
|
Subject: Resource{
|
||||||
Resource: Resource{
|
Mode: DataResourceMode,
|
||||||
Mode: DataResourceMode,
|
Type: "external",
|
||||||
Type: "external",
|
Name: "foo",
|
||||||
Name: "foo",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
SourceRange: tfdiags.SourceRange{
|
SourceRange: tfdiags.SourceRange{
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
|
@ -592,12 +590,10 @@ func TestParseRef(t *testing.T) {
|
||||||
{
|
{
|
||||||
`boop_instance.foo`,
|
`boop_instance.foo`,
|
||||||
&Reference{
|
&Reference{
|
||||||
Subject: ResourceInstance{
|
Subject: Resource{
|
||||||
Resource: Resource{
|
Mode: ManagedResourceMode,
|
||||||
Mode: ManagedResourceMode,
|
Type: "boop_instance",
|
||||||
Type: "boop_instance",
|
Name: "foo",
|
||||||
Name: "foo",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
SourceRange: tfdiags.SourceRange{
|
SourceRange: tfdiags.SourceRange{
|
||||||
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
Start: tfdiags.SourcePos{Line: 1, Column: 1, Byte: 0},
|
||||||
|
|
|
@ -3,9 +3,9 @@ package addrs
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-test/deep"
|
"github.com/go-test/deep"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProviderConfig is the address of a provider configuration.
|
// ProviderConfig is the address of a provider configuration.
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
|
|
||||||
"github.com/go-test/deep"
|
"github.com/go-test/deep"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseProviderConfigCompact(t *testing.T) {
|
func TestParseProviderConfigCompact(t *testing.T) {
|
||||||
|
|
|
@ -76,9 +76,9 @@ func (b *Local) opApply(
|
||||||
|
|
||||||
// Perform the plan
|
// Perform the plan
|
||||||
log.Printf("[INFO] backend/local: apply calling Plan")
|
log.Printf("[INFO] backend/local: apply calling Plan")
|
||||||
plan, err := tfCtx.Plan()
|
plan, planDiags := tfCtx.Plan()
|
||||||
if err != nil {
|
diags = diags.Append(planDiags)
|
||||||
diags = diags.Append(err)
|
if planDiags.HasErrors() {
|
||||||
b.ReportResult(runningOp, diags)
|
b.ReportResult(runningOp, diags)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,13 @@ func (b *Local) opApply(
|
||||||
b.CLI.Output("")
|
b.CLI.Output("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We'll show any accumulated warnings before we display the prompt,
|
||||||
|
// so the user can consider them when deciding how to answer.
|
||||||
|
if len(diags) > 0 {
|
||||||
|
b.ShowDiagnostics(diags)
|
||||||
|
diags = nil // reset so we won't show the same diagnostics again later
|
||||||
|
}
|
||||||
|
|
||||||
v, err := op.UIIn.Input(stopCtx, &terraform.InputOpts{
|
v, err := op.UIIn.Input(stopCtx, &terraform.InputOpts{
|
||||||
Id: "approve",
|
Id: "approve",
|
||||||
Query: query,
|
Query: query,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -45,6 +46,9 @@ func prepareEtcdv3(t *testing.T) {
|
||||||
t.Log("etcd server tests require setting TF_ACC or TF_ETCDV3_TEST")
|
t.Log("etcd server tests require setting TF_ACC or TF_ETCDV3_TEST")
|
||||||
t.Skip()
|
t.Skip()
|
||||||
}
|
}
|
||||||
|
if reflect.DeepEqual(etcdv3Endpoints, []string{""}) {
|
||||||
|
t.Fatal("etcd server tests require setting TF_ETCDV3_ENDPOINTS")
|
||||||
|
}
|
||||||
cleanupEtcdv3(t)
|
cleanupEtcdv3(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/hashicorp/terraform/helper/pathorcontents"
|
"github.com/hashicorp/terraform/helper/pathorcontents"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/hashicorp/terraform/httpclient"
|
"github.com/hashicorp/terraform/httpclient"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
"golang.org/x/oauth2/jwt"
|
"golang.org/x/oauth2/jwt"
|
||||||
"google.golang.org/api/option"
|
"google.golang.org/api/option"
|
||||||
)
|
)
|
||||||
|
@ -65,6 +66,15 @@ func New() backend.Backend {
|
||||||
Default: "",
|
Default: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"access_token": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
DefaultFunc: schema.MultiEnvDefaultFunc([]string{
|
||||||
|
"GOOGLE_OAUTH_ACCESS_TOKEN",
|
||||||
|
}, nil),
|
||||||
|
Description: "An OAuth2 token used for GCP authentication",
|
||||||
|
},
|
||||||
|
|
||||||
"encryption_key": {
|
"encryption_key": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
@ -116,12 +126,23 @@ func (b *Backend) configure(ctx context.Context) error {
|
||||||
|
|
||||||
var opts []option.ClientOption
|
var opts []option.ClientOption
|
||||||
|
|
||||||
creds := data.Get("credentials").(string)
|
// Add credential source
|
||||||
if creds == "" {
|
var creds string
|
||||||
|
var tokenSource oauth2.TokenSource
|
||||||
|
|
||||||
|
if v, ok := data.GetOk("access_token"); ok {
|
||||||
|
tokenSource = oauth2.StaticTokenSource(&oauth2.Token{
|
||||||
|
AccessToken: v.(string),
|
||||||
|
})
|
||||||
|
} else if v, ok := data.GetOk("credentials"); ok {
|
||||||
|
creds = v.(string)
|
||||||
|
} else {
|
||||||
creds = os.Getenv("GOOGLE_CREDENTIALS")
|
creds = os.Getenv("GOOGLE_CREDENTIALS")
|
||||||
}
|
}
|
||||||
|
|
||||||
if creds != "" {
|
if tokenSource != nil {
|
||||||
|
opts = append(opts, option.WithTokenSource(tokenSource))
|
||||||
|
} else if creds != "" {
|
||||||
var account accountFile
|
var account accountFile
|
||||||
|
|
||||||
// to mirror how the provider works, we accept the file path or the contents
|
// to mirror how the provider works, we accept the file path or the contents
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/helper/logging"
|
"github.com/hashicorp/terraform/helper/logging"
|
||||||
|
|
|
@ -3,7 +3,7 @@ package inmem
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/state/remote"
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
)
|
)
|
||||||
|
|
|
@ -53,7 +53,7 @@ func New() backend.Backend {
|
||||||
"insecure_skip_tls_verify": {
|
"insecure_skip_tls_verify": {
|
||||||
Type: schema.TypeBool,
|
Type: schema.TypeBool,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
DefaultFunc: schema.EnvDefaultFunc("TRITON_SKIP_TLS_VERIFY", ""),
|
DefaultFunc: schema.EnvDefaultFunc("TRITON_SKIP_TLS_VERIFY", false),
|
||||||
},
|
},
|
||||||
|
|
||||||
"path": {
|
"path": {
|
||||||
|
|
|
@ -18,6 +18,14 @@ func testACC(t *testing.T) {
|
||||||
t.Log("Manta backend tests require setting TF_ACC or TF_MANTA_TEST")
|
t.Log("Manta backend tests require setting TF_ACC or TF_MANTA_TEST")
|
||||||
t.Skip()
|
t.Skip()
|
||||||
}
|
}
|
||||||
|
skip = os.Getenv("TRITON_ACCOUNT") == "" && os.Getenv("SDC_ACCOUNT") == ""
|
||||||
|
if skip {
|
||||||
|
t.Fatal("Manta backend tests require setting TRITON_ACCOUNT or SDC_ACCOUNT")
|
||||||
|
}
|
||||||
|
skip = os.Getenv("TRITON_KEY_ID") == "" && os.Getenv("SDC_KEY_ID") == ""
|
||||||
|
if skip {
|
||||||
|
t.Fatal("Manta backend tests require setting TRITON_KEY_ID or SDC_KEY_ID")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackend_impl(t *testing.T) {
|
func TestBackend_impl(t *testing.T) {
|
||||||
|
|
|
@ -6,11 +6,12 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||||
"github.com/aliyun/aliyun-tablestore-go-sdk/tablestore"
|
"github.com/aliyun/aliyun-tablestore-go-sdk/tablestore"
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/configs/hcl2shim"
|
"github.com/hashicorp/terraform/configs/hcl2shim"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// verify that we are doing ACC tests or the OSS tests specifically
|
// verify that we are doing ACC tests or the OSS tests specifically
|
||||||
|
@ -20,6 +21,9 @@ func testACC(t *testing.T) {
|
||||||
t.Log("oss backend tests require setting TF_ACC or TF_OSS_TEST")
|
t.Log("oss backend tests require setting TF_ACC or TF_OSS_TEST")
|
||||||
t.Skip()
|
t.Skip()
|
||||||
}
|
}
|
||||||
|
if skip {
|
||||||
|
t.Fatal("oss backend tests require setting ALICLOUD_ACCESS_KEY or ALICLOUD_ACCESS_KEY_ID")
|
||||||
|
}
|
||||||
if os.Getenv("ALICLOUD_REGION") == "" {
|
if os.Getenv("ALICLOUD_REGION") == "" {
|
||||||
os.Setenv("ALICLOUD_REGION", "cn-beijing")
|
os.Setenv("ALICLOUD_REGION", "cn-beijing")
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
tfe "github.com/hashicorp/go-tfe"
|
tfe "github.com/hashicorp/go-tfe"
|
||||||
|
@ -227,6 +229,87 @@ func (b *Remote) parseVariableValues(op *backend.Operation) (terraform.InputValu
|
||||||
return result, diags
|
return result, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Remote) costEstimate(stopCtx, cancelCtx context.Context, op *backend.Operation, r *tfe.Run) error {
|
||||||
|
if r.CostEstimate == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.CLI != nil {
|
||||||
|
b.CLI.Output("\n------------------------------------------------------------------------\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
msgPrefix := "Cost estimation"
|
||||||
|
if b.CLI != nil {
|
||||||
|
b.CLI.Output(b.Colorize().Color(msgPrefix + ":\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
started := time.Now()
|
||||||
|
updated := started
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
select {
|
||||||
|
case <-stopCtx.Done():
|
||||||
|
return stopCtx.Err()
|
||||||
|
case <-cancelCtx.Done():
|
||||||
|
return cancelCtx.Err()
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the cost estimate to get its current status.
|
||||||
|
ce, err := b.client.CostEstimates.Read(stopCtx, r.CostEstimate.ID)
|
||||||
|
if err != nil {
|
||||||
|
return generalError("Failed to retrieve cost estimate", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ce.Status {
|
||||||
|
case tfe.CostEstimateFinished:
|
||||||
|
delta, err := strconv.ParseFloat(ce.DeltaMonthlyCost, 64)
|
||||||
|
if err != nil {
|
||||||
|
return generalError("Unexpected error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sign := "+"
|
||||||
|
if delta < 0 {
|
||||||
|
sign = "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
deltaRepr := strings.Replace(ce.DeltaMonthlyCost, "-", "", 1)
|
||||||
|
|
||||||
|
if b.CLI != nil {
|
||||||
|
b.CLI.Output(b.Colorize().Color(fmt.Sprintf("Resources: %d of %d estimated", ce.MatchedResourcesCount, ce.ResourcesCount)))
|
||||||
|
b.CLI.Output(b.Colorize().Color(fmt.Sprintf(" $%s/mo %s$%s", ce.ProposedMonthlyCost, sign, deltaRepr)))
|
||||||
|
|
||||||
|
if len(r.PolicyChecks) == 0 && r.HasChanges && op.Type == backend.OperationTypeApply {
|
||||||
|
b.CLI.Output("\n------------------------------------------------------------------------")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
case tfe.CostEstimatePending, tfe.CostEstimateQueued:
|
||||||
|
// Check if 30 seconds have passed since the last update.
|
||||||
|
current := time.Now()
|
||||||
|
if b.CLI != nil && (i == 0 || current.Sub(updated).Seconds() > 30) {
|
||||||
|
updated = current
|
||||||
|
elapsed := ""
|
||||||
|
|
||||||
|
// Calculate and set the elapsed time.
|
||||||
|
if i > 0 {
|
||||||
|
elapsed = fmt.Sprintf(
|
||||||
|
" (%s elapsed)", current.Sub(started).Truncate(30*time.Second))
|
||||||
|
}
|
||||||
|
b.CLI.Output(b.Colorize().Color("Waiting for cost estimate to complete..." + elapsed + "\n"))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
case tfe.CostEstimateErrored:
|
||||||
|
return fmt.Errorf(msgPrefix + " errored.")
|
||||||
|
case tfe.CostEstimateCanceled:
|
||||||
|
return fmt.Errorf(msgPrefix + " canceled.")
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unknown or unexpected cost estimate state: %s", ce.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Remote) checkPolicy(stopCtx, cancelCtx context.Context, op *backend.Operation, r *tfe.Run) error {
|
func (b *Remote) checkPolicy(stopCtx, cancelCtx context.Context, op *backend.Operation, r *tfe.Run) error {
|
||||||
if b.CLI != nil {
|
if b.CLI != nil {
|
||||||
b.CLI.Output("\n------------------------------------------------------------------------\n")
|
b.CLI.Output("\n------------------------------------------------------------------------\n")
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
type mockClient struct {
|
type mockClient struct {
|
||||||
Applies *mockApplies
|
Applies *mockApplies
|
||||||
ConfigurationVersions *mockConfigurationVersions
|
ConfigurationVersions *mockConfigurationVersions
|
||||||
|
CostEstimates *mockCostEstimates
|
||||||
Organizations *mockOrganizations
|
Organizations *mockOrganizations
|
||||||
Plans *mockPlans
|
Plans *mockPlans
|
||||||
PolicyChecks *mockPolicyChecks
|
PolicyChecks *mockPolicyChecks
|
||||||
|
@ -33,6 +34,7 @@ func newMockClient() *mockClient {
|
||||||
c := &mockClient{}
|
c := &mockClient{}
|
||||||
c.Applies = newMockApplies(c)
|
c.Applies = newMockApplies(c)
|
||||||
c.ConfigurationVersions = newMockConfigurationVersions(c)
|
c.ConfigurationVersions = newMockConfigurationVersions(c)
|
||||||
|
c.CostEstimates = newMockCostEstimates(c)
|
||||||
c.Organizations = newMockOrganizations(c)
|
c.Organizations = newMockOrganizations(c)
|
||||||
c.Plans = newMockPlans(c)
|
c.Plans = newMockPlans(c)
|
||||||
c.PolicyChecks = newMockPolicyChecks(c)
|
c.PolicyChecks = newMockPolicyChecks(c)
|
||||||
|
@ -212,6 +214,88 @@ func (m *mockConfigurationVersions) Upload(ctx context.Context, url, path string
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockCostEstimates struct {
|
||||||
|
client *mockClient
|
||||||
|
estimations map[string]*tfe.CostEstimate
|
||||||
|
logs map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockCostEstimates(client *mockClient) *mockCostEstimates {
|
||||||
|
return &mockCostEstimates{
|
||||||
|
client: client,
|
||||||
|
estimations: make(map[string]*tfe.CostEstimate),
|
||||||
|
logs: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create is a helper function to create a mock cost estimation that uses the
|
||||||
|
// configured working directory to find the logfile.
|
||||||
|
func (m *mockCostEstimates) create(cvID, workspaceID string) (*tfe.CostEstimate, error) {
|
||||||
|
id := generateID("ce-")
|
||||||
|
|
||||||
|
ce := &tfe.CostEstimate{
|
||||||
|
ID: id,
|
||||||
|
MatchedResourcesCount: 1,
|
||||||
|
ResourcesCount: 1,
|
||||||
|
DeltaMonthlyCost: "0.00",
|
||||||
|
ProposedMonthlyCost: "0.00",
|
||||||
|
Status: tfe.CostEstimateFinished,
|
||||||
|
}
|
||||||
|
|
||||||
|
w, ok := m.client.Workspaces.workspaceIDs[workspaceID]
|
||||||
|
if !ok {
|
||||||
|
return nil, tfe.ErrResourceNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
logfile := filepath.Join(
|
||||||
|
m.client.ConfigurationVersions.uploadPaths[cvID],
|
||||||
|
w.WorkingDirectory,
|
||||||
|
"cost-estimate.log",
|
||||||
|
)
|
||||||
|
|
||||||
|
if _, err := os.Stat(logfile); os.IsNotExist(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m.logs[ce.ID] = logfile
|
||||||
|
m.estimations[ce.ID] = ce
|
||||||
|
|
||||||
|
return ce, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockCostEstimates) Read(ctx context.Context, costEstimateID string) (*tfe.CostEstimate, error) {
|
||||||
|
ce, ok := m.estimations[costEstimateID]
|
||||||
|
if !ok {
|
||||||
|
return nil, tfe.ErrResourceNotFound
|
||||||
|
}
|
||||||
|
return ce, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockCostEstimates) Logs(ctx context.Context, costEstimateID string) (io.Reader, error) {
|
||||||
|
ce, ok := m.estimations[costEstimateID]
|
||||||
|
if !ok {
|
||||||
|
return nil, tfe.ErrResourceNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
logfile, ok := m.logs[ce.ID]
|
||||||
|
if !ok {
|
||||||
|
return nil, tfe.ErrResourceNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(logfile); os.IsNotExist(err) {
|
||||||
|
return bytes.NewBufferString("logfile does not exist"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, err := ioutil.ReadFile(logfile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ce.Status = tfe.CostEstimateFinished
|
||||||
|
|
||||||
|
return bytes.NewBuffer(logs), nil
|
||||||
|
}
|
||||||
|
|
||||||
// mockInput is a mock implementation of terraform.UIInput.
|
// mockInput is a mock implementation of terraform.UIInput.
|
||||||
type mockInput struct {
|
type mockInput struct {
|
||||||
answers map[string]string
|
answers map[string]string
|
||||||
|
@ -647,6 +731,11 @@ func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*t
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ce, err := m.client.CostEstimates.create(options.ConfigurationVersion.ID, options.Workspace.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
p, err := m.client.Plans.create(options.ConfigurationVersion.ID, options.Workspace.ID)
|
p, err := m.client.Plans.create(options.ConfigurationVersion.ID, options.Workspace.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -658,13 +747,14 @@ func (m *mockRuns) Create(ctx context.Context, options tfe.RunCreateOptions) (*t
|
||||||
}
|
}
|
||||||
|
|
||||||
r := &tfe.Run{
|
r := &tfe.Run{
|
||||||
ID: generateID("run-"),
|
ID: generateID("run-"),
|
||||||
Actions: &tfe.RunActions{IsCancelable: true},
|
Actions: &tfe.RunActions{IsCancelable: true},
|
||||||
Apply: a,
|
Apply: a,
|
||||||
HasChanges: false,
|
CostEstimate: ce,
|
||||||
Permissions: &tfe.RunPermissions{},
|
HasChanges: false,
|
||||||
Plan: p,
|
Permissions: &tfe.RunPermissions{},
|
||||||
Status: tfe.RunPending,
|
Plan: p,
|
||||||
|
Status: tfe.RunPending,
|
||||||
}
|
}
|
||||||
|
|
||||||
if pc != nil {
|
if pc != nil {
|
||||||
|
@ -960,6 +1050,14 @@ func (m *mockWorkspaces) Read(ctx context.Context, organization, workspace strin
|
||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockWorkspaces) ReadByID(ctx context.Context, workspaceID string) (*tfe.Workspace, error) {
|
||||||
|
w, ok := m.workspaceIDs[workspaceID]
|
||||||
|
if !ok {
|
||||||
|
return nil, tfe.ErrResourceNotFound
|
||||||
|
}
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockWorkspaces) Update(ctx context.Context, organization, workspace string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) {
|
func (m *mockWorkspaces) Update(ctx context.Context, organization, workspace string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) {
|
||||||
w, ok := m.workspaceNames[workspace]
|
w, ok := m.workspaceNames[workspace]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -982,6 +1080,28 @@ func (m *mockWorkspaces) Update(ctx context.Context, organization, workspace str
|
||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockWorkspaces) UpdateByID(ctx context.Context, workspaceID string, options tfe.WorkspaceUpdateOptions) (*tfe.Workspace, error) {
|
||||||
|
w, ok := m.workspaceIDs[workspaceID]
|
||||||
|
if !ok {
|
||||||
|
return nil, tfe.ErrResourceNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Name != nil {
|
||||||
|
w.Name = *options.Name
|
||||||
|
}
|
||||||
|
if options.TerraformVersion != nil {
|
||||||
|
w.TerraformVersion = *options.TerraformVersion
|
||||||
|
}
|
||||||
|
if options.WorkingDirectory != nil {
|
||||||
|
w.WorkingDirectory = *options.WorkingDirectory
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(m.workspaceNames, w.Name)
|
||||||
|
m.workspaceNames[w.Name] = w
|
||||||
|
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockWorkspaces) Delete(ctx context.Context, organization, workspace string) error {
|
func (m *mockWorkspaces) Delete(ctx context.Context, organization, workspace string) error {
|
||||||
if w, ok := m.workspaceNames[workspace]; ok {
|
if w, ok := m.workspaceNames[workspace]; ok {
|
||||||
delete(m.workspaceIDs, w.ID)
|
delete(m.workspaceIDs, w.ID)
|
||||||
|
@ -990,6 +1110,14 @@ func (m *mockWorkspaces) Delete(ctx context.Context, organization, workspace str
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockWorkspaces) DeleteByID(ctx context.Context, workspaceID string) error {
|
||||||
|
if w, ok := m.workspaceIDs[workspaceID]; ok {
|
||||||
|
delete(m.workspaceIDs, w.Name)
|
||||||
|
}
|
||||||
|
delete(m.workspaceIDs, workspaceID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockWorkspaces) RemoveVCSConnection(ctx context.Context, organization, workspace string) (*tfe.Workspace, error) {
|
func (m *mockWorkspaces) RemoveVCSConnection(ctx context.Context, organization, workspace string) (*tfe.Workspace, error) {
|
||||||
w, ok := m.workspaceNames[workspace]
|
w, ok := m.workspaceNames[workspace]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -999,6 +1127,15 @@ func (m *mockWorkspaces) RemoveVCSConnection(ctx context.Context, organization,
|
||||||
return w, nil
|
return w, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockWorkspaces) RemoveVCSConnectionByID(ctx context.Context, workspaceID string) (*tfe.Workspace, error) {
|
||||||
|
w, ok := m.workspaceIDs[workspaceID]
|
||||||
|
if !ok {
|
||||||
|
return nil, tfe.ErrResourceNotFound
|
||||||
|
}
|
||||||
|
w.VCSRepo = nil
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockWorkspaces) Lock(ctx context.Context, workspaceID string, options tfe.WorkspaceLockOptions) (*tfe.Workspace, error) {
|
func (m *mockWorkspaces) Lock(ctx context.Context, workspaceID string, options tfe.WorkspaceLockOptions) (*tfe.Workspace, error) {
|
||||||
w, ok := m.workspaceIDs[workspaceID]
|
w, ok := m.workspaceIDs[workspaceID]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -316,6 +316,14 @@ to capture the filesystem context the remote workspace expects:
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show any cost estimation output.
|
||||||
|
if r.CostEstimate != nil {
|
||||||
|
err = b.costEstimate(stopCtx, cancelCtx, op, r)
|
||||||
|
if err != nil {
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Check any configured sentinel policies.
|
// Check any configured sentinel policies.
|
||||||
if len(r.PolicyChecks) > 0 {
|
if len(r.PolicyChecks) > 0 {
|
||||||
err = b.checkPolicy(stopCtx, cancelCtx, op, r)
|
err = b.checkPolicy(stopCtx, cancelCtx, op, r)
|
||||||
|
|
|
@ -59,7 +59,7 @@ func TestRemote_planBasic(t *testing.T) {
|
||||||
t.Fatalf("expected remote backend header in output: %s", output)
|
t.Fatalf("expected remote backend header in output: %s", output)
|
||||||
}
|
}
|
||||||
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
||||||
t.Fatalf("expected plan summery in output: %s", output)
|
t.Fatalf("expected plan summary in output: %s", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ func TestRemote_planLongLine(t *testing.T) {
|
||||||
t.Fatalf("expected remote backend header in output: %s", output)
|
t.Fatalf("expected remote backend header in output: %s", output)
|
||||||
}
|
}
|
||||||
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
||||||
t.Fatalf("expected plan summery in output: %s", output)
|
t.Fatalf("expected plan summary in output: %s", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,7 +374,7 @@ func TestRemote_planNoChanges(t *testing.T) {
|
||||||
|
|
||||||
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
||||||
if !strings.Contains(output, "No changes. Infrastructure is up-to-date.") {
|
if !strings.Contains(output, "No changes. Infrastructure is up-to-date.") {
|
||||||
t.Fatalf("expected no changes in plan summery: %s", output)
|
t.Fatalf("expected no changes in plan summary: %s", output)
|
||||||
}
|
}
|
||||||
if !strings.Contains(output, "Sentinel Result: true") {
|
if !strings.Contains(output, "Sentinel Result: true") {
|
||||||
t.Fatalf("expected policy check result in output: %s", output)
|
t.Fatalf("expected policy check result in output: %s", output)
|
||||||
|
@ -415,7 +415,7 @@ func TestRemote_planForceLocal(t *testing.T) {
|
||||||
t.Fatalf("unexpected remote backend header in output: %s", output)
|
t.Fatalf("unexpected remote backend header in output: %s", output)
|
||||||
}
|
}
|
||||||
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
||||||
t.Fatalf("expected plan summery in output: %s", output)
|
t.Fatalf("expected plan summary in output: %s", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -446,7 +446,7 @@ func TestRemote_planWithoutOperationsEntitlement(t *testing.T) {
|
||||||
t.Fatalf("unexpected remote backend header in output: %s", output)
|
t.Fatalf("unexpected remote backend header in output: %s", output)
|
||||||
}
|
}
|
||||||
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
||||||
t.Fatalf("expected plan summery in output: %s", output)
|
t.Fatalf("expected plan summary in output: %s", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,7 +491,7 @@ func TestRemote_planWorkspaceWithoutOperations(t *testing.T) {
|
||||||
t.Fatalf("unexpected remote backend header in output: %s", output)
|
t.Fatalf("unexpected remote backend header in output: %s", output)
|
||||||
}
|
}
|
||||||
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
||||||
t.Fatalf("expected plan summery in output: %s", output)
|
t.Fatalf("expected plan summary in output: %s", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -562,7 +562,7 @@ func TestRemote_planLockTimeout(t *testing.T) {
|
||||||
t.Fatalf("expected lock timout error in output: %s", output)
|
t.Fatalf("expected lock timout error in output: %s", output)
|
||||||
}
|
}
|
||||||
if strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
if strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
||||||
t.Fatalf("unexpected plan summery in output: %s", output)
|
t.Fatalf("unexpected plan summary in output: %s", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -654,7 +654,7 @@ func TestRemote_planWithWorkingDirectory(t *testing.T) {
|
||||||
t.Fatalf("expected remote backend header in output: %s", output)
|
t.Fatalf("expected remote backend header in output: %s", output)
|
||||||
}
|
}
|
||||||
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
||||||
t.Fatalf("expected plan summery in output: %s", output)
|
t.Fatalf("expected plan summary in output: %s", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -709,7 +709,41 @@ func TestRemote_planWithWorkingDirectoryFromCurrentPath(t *testing.T) {
|
||||||
t.Fatalf("expected remote backend header in output: %s", output)
|
t.Fatalf("expected remote backend header in output: %s", output)
|
||||||
}
|
}
|
||||||
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
||||||
t.Fatalf("expected plan summery in output: %s", output)
|
t.Fatalf("expected plan summary in output: %s", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemote_planCostEstimation(t *testing.T) {
|
||||||
|
b, bCleanup := testBackendDefault(t)
|
||||||
|
defer bCleanup()
|
||||||
|
|
||||||
|
op, configCleanup := testOperationPlan(t, "./testdata/plan-cost-estimation")
|
||||||
|
defer configCleanup()
|
||||||
|
|
||||||
|
op.Workspace = backend.DefaultStateName
|
||||||
|
|
||||||
|
run, err := b.Operation(context.Background(), op)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error starting operation: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-run.Done()
|
||||||
|
if run.Result != backend.OperationSuccess {
|
||||||
|
t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
|
||||||
|
}
|
||||||
|
if run.PlanEmpty {
|
||||||
|
t.Fatalf("expected a non-empty plan")
|
||||||
|
}
|
||||||
|
|
||||||
|
output := b.CLI.(*cli.MockUi).OutputWriter.String()
|
||||||
|
if !strings.Contains(output, "Running plan in the remote backend") {
|
||||||
|
t.Fatalf("expected remote backend header in output: %s", output)
|
||||||
|
}
|
||||||
|
if !strings.Contains(output, "Resources: 1 of 1 estimated") {
|
||||||
|
t.Fatalf("expected cost estimate result in output: %s", output)
|
||||||
|
}
|
||||||
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
||||||
|
t.Fatalf("expected plan summary in output: %s", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -743,7 +777,7 @@ func TestRemote_planPolicyPass(t *testing.T) {
|
||||||
t.Fatalf("expected policy check result in output: %s", output)
|
t.Fatalf("expected policy check result in output: %s", output)
|
||||||
}
|
}
|
||||||
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
||||||
t.Fatalf("expected plan summery in output: %s", output)
|
t.Fatalf("expected plan summary in output: %s", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -782,7 +816,7 @@ func TestRemote_planPolicyHardFail(t *testing.T) {
|
||||||
t.Fatalf("expected policy check result in output: %s", output)
|
t.Fatalf("expected policy check result in output: %s", output)
|
||||||
}
|
}
|
||||||
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
||||||
t.Fatalf("expected plan summery in output: %s", output)
|
t.Fatalf("expected plan summary in output: %s", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -821,7 +855,7 @@ func TestRemote_planPolicySoftFail(t *testing.T) {
|
||||||
t.Fatalf("expected policy check result in output: %s", output)
|
t.Fatalf("expected policy check result in output: %s", output)
|
||||||
}
|
}
|
||||||
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
|
||||||
t.Fatalf("expected plan summery in output: %s", output)
|
t.Fatalf("expected plan summary in output: %s", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
Cost estimation:
|
||||||
|
|
||||||
|
Waiting for cost estimation to complete...
|
||||||
|
Resources: 1 of 1 estimated
|
||||||
|
$25.488/mo +$25.488
|
|
@ -1,5 +1,4 @@
|
||||||
Terraform v0.11.7
|
Terraform v0.12.9
|
||||||
|
|
||||||
Configuring remote state backend...
|
Configuring remote state backend...
|
||||||
Initializing Terraform configuration...
|
Initializing Terraform configuration...
|
||||||
Refreshing Terraform state in-memory prior to plan...
|
Refreshing Terraform state in-memory prior to plan...
|
||||||
|
|
|
@ -115,6 +115,7 @@ func testBackend(t *testing.T, obj cty.Value) (*Remote, func()) {
|
||||||
b.CLI = cli.NewMockUi()
|
b.CLI = cli.NewMockUi()
|
||||||
b.client.Applies = mc.Applies
|
b.client.Applies = mc.Applies
|
||||||
b.client.ConfigurationVersions = mc.ConfigurationVersions
|
b.client.ConfigurationVersions = mc.ConfigurationVersions
|
||||||
|
b.client.CostEstimates = mc.CostEstimates
|
||||||
b.client.Organizations = mc.Organizations
|
b.client.Organizations = mc.Organizations
|
||||||
b.client.Plans = mc.Plans
|
b.client.Plans = mc.Plans
|
||||||
b.client.PolicyChecks = mc.PolicyChecks
|
b.client.PolicyChecks = mc.PolicyChecks
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
uuid "github.com/hashicorp/go-uuid"
|
uuid "github.com/hashicorp/go-uuid"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
|
|
|
@ -3,7 +3,7 @@ package backend
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
|
|
|
@ -646,12 +646,7 @@ resource "test_resource_nested_set" "foo" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`),
|
`),
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(),
|
||||||
func(s *terraform.State) error {
|
|
||||||
fmt.Println(s)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -14,6 +14,11 @@ func testResourceTimeout() *schema.Resource {
|
||||||
Update: testResourceTimeoutUpdate,
|
Update: testResourceTimeoutUpdate,
|
||||||
Delete: testResourceTimeoutDelete,
|
Delete: testResourceTimeoutDelete,
|
||||||
|
|
||||||
|
// Due to the schema version also being stashed in the private/meta
|
||||||
|
// data, we need to ensure that it does not overwrite the map
|
||||||
|
// containing the timeouts.
|
||||||
|
SchemaVersion: 1,
|
||||||
|
|
||||||
Timeouts: &schema.ResourceTimeout{
|
Timeouts: &schema.ResourceTimeout{
|
||||||
Create: schema.DefaultTimeout(time.Second),
|
Create: schema.DefaultTimeout(time.Second),
|
||||||
Update: schema.DefaultTimeout(time.Second),
|
Update: schema.DefaultTimeout(time.Second),
|
||||||
|
|
|
@ -0,0 +1,376 @@
|
||||||
|
package habitat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/hashicorp/terraform/communicator"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
const installURL = "https://raw.githubusercontent.com/habitat-sh/habitat/master/components/hab/install.sh"
|
||||||
|
const systemdUnit = `[Unit]
|
||||||
|
Description=Habitat Supervisor
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/bin/hab sup run{{ .SupOptions }}
|
||||||
|
Restart=on-failure
|
||||||
|
{{ if .GatewayAuthToken -}}
|
||||||
|
Environment="HAB_SUP_GATEWAY_AUTH_TOKEN={{ .GatewayAuthToken }}"
|
||||||
|
{{ end -}}
|
||||||
|
{{ if .BuilderAuthToken -}}
|
||||||
|
Environment="HAB_AUTH_TOKEN={{ .BuilderAuthToken }}"
|
||||||
|
{{ end -}}
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
`
|
||||||
|
|
||||||
|
func (p *provisioner) linuxInstallHabitat(o terraform.UIOutput, comm communicator.Communicator) error {
|
||||||
|
// Download the hab installer
|
||||||
|
if err := p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("curl --silent -L0 %s > install.sh", installURL))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the install script
|
||||||
|
var command string
|
||||||
|
if p.Version == "" {
|
||||||
|
command = fmt.Sprintf("bash ./install.sh ")
|
||||||
|
} else {
|
||||||
|
command = fmt.Sprintf("bash ./install.sh -v %s", p.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.runCommand(o, comm, p.linuxGetCommand(command)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept the license
|
||||||
|
if p.AcceptLicense {
|
||||||
|
var cmd string
|
||||||
|
|
||||||
|
if p.UseSudo == true {
|
||||||
|
cmd = "env HAB_LICENSE=accept sudo -E /bin/bash -c 'hab -V'"
|
||||||
|
} else {
|
||||||
|
cmd = "env HAB_LICENSE=accept /bin/bash -c 'hab -V'"
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.runCommand(o, comm, cmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the hab user
|
||||||
|
if err := p.createHabUser(o, comm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup the installer
|
||||||
|
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("rm -f install.sh")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provisioner) createHabUser(o terraform.UIOutput, comm communicator.Communicator) error {
|
||||||
|
var addUser bool
|
||||||
|
|
||||||
|
// Install busybox to get us the user tools we need
|
||||||
|
if err := p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab install core/busybox"))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for existing hab user
|
||||||
|
if err := p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab pkg exec core/busybox id hab"))); err != nil {
|
||||||
|
o.Output("No existing hab user detected, creating...")
|
||||||
|
addUser = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if addUser {
|
||||||
|
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab pkg exec core/busybox adduser -D -g \"\" hab")))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provisioner) linuxStartHabitat(o terraform.UIOutput, comm communicator.Communicator) error {
|
||||||
|
// Install the supervisor first
|
||||||
|
var command string
|
||||||
|
if p.Version == "" {
|
||||||
|
command += p.linuxGetCommand(fmt.Sprintf("hab install core/hab-sup"))
|
||||||
|
} else {
|
||||||
|
command += p.linuxGetCommand(fmt.Sprintf("hab install core/hab-sup/%s", p.Version))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.runCommand(o, comm, command); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up supervisor options
|
||||||
|
options := ""
|
||||||
|
if p.PermanentPeer {
|
||||||
|
options += " --permanent-peer"
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.ListenCtl != "" {
|
||||||
|
options += fmt.Sprintf(" --listen-ctl %s", p.ListenCtl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.ListenGossip != "" {
|
||||||
|
options += fmt.Sprintf(" --listen-gossip %s", p.ListenGossip)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.ListenHTTP != "" {
|
||||||
|
options += fmt.Sprintf(" --listen-http %s", p.ListenHTTP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Peer != "" {
|
||||||
|
options += fmt.Sprintf(" %s", p.Peer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.Peers) > 0 {
|
||||||
|
if len(p.Peers) == 1 {
|
||||||
|
options += fmt.Sprintf(" --peer %s", p.Peers[0])
|
||||||
|
} else {
|
||||||
|
options += fmt.Sprintf(" --peer %s", strings.Join(p.Peers, " --peer "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.RingKey != "" {
|
||||||
|
options += fmt.Sprintf(" --ring %s", p.RingKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.URL != "" {
|
||||||
|
options += fmt.Sprintf(" --url %s", p.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Channel != "" {
|
||||||
|
options += fmt.Sprintf(" --channel %s", p.Channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Events != "" {
|
||||||
|
options += fmt.Sprintf(" --events %s", p.Events)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Organization != "" {
|
||||||
|
options += fmt.Sprintf(" --org %s", p.Organization)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.HttpDisable == true {
|
||||||
|
options += fmt.Sprintf(" --http-disable")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.AutoUpdate == true {
|
||||||
|
options += fmt.Sprintf(" --auto-update")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.SupOptions = options
|
||||||
|
|
||||||
|
// Start hab depending on service type
|
||||||
|
switch p.ServiceType {
|
||||||
|
case "unmanaged":
|
||||||
|
return p.linuxStartHabitatUnmanaged(o, comm, options)
|
||||||
|
case "systemd":
|
||||||
|
return p.linuxStartHabitatSystemd(o, comm, options)
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported service type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This func is a little different than the others since we need to expose HAB_AUTH_TOKEN to a shell
|
||||||
|
// sub-process that's actually running the supervisor.
|
||||||
|
func (p *provisioner) linuxStartHabitatUnmanaged(o terraform.UIOutput, comm communicator.Communicator, options string) error {
|
||||||
|
var token string
|
||||||
|
|
||||||
|
// Create the sup directory for the log file
|
||||||
|
if err := p.runCommand(o, comm, p.linuxGetCommand("mkdir -p /hab/sup/default && chmod o+w /hab/sup/default")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set HAB_AUTH_TOKEN if provided
|
||||||
|
if p.BuilderAuthToken != "" {
|
||||||
|
token = fmt.Sprintf("env HAB_AUTH_TOKEN=%s ", p.BuilderAuthToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("(%ssetsid hab sup run%s > /hab/sup/default/sup.log 2>&1 <&1 &) ; sleep 1", token, options)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provisioner) linuxStartHabitatSystemd(o terraform.UIOutput, comm communicator.Communicator, options string) error {
|
||||||
|
// Create a new template and parse the client config into it
|
||||||
|
unitString := template.Must(template.New("hab-supervisor.service").Parse(systemdUnit))
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := unitString.Execute(&buf, p)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error executing %s.service template: %s", p.ServiceName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.linuxUploadSystemdUnit(o, comm, &buf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("systemctl enable %s && systemctl start %s", p.ServiceName, p.ServiceName)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provisioner) linuxUploadSystemdUnit(o terraform.UIOutput, comm communicator.Communicator, contents *bytes.Buffer) error {
|
||||||
|
destination := fmt.Sprintf("/etc/systemd/system/%s.service", p.ServiceName)
|
||||||
|
|
||||||
|
if p.UseSudo {
|
||||||
|
tempPath := fmt.Sprintf("/tmp/%s.service", p.ServiceName)
|
||||||
|
if err := comm.Upload(tempPath, contents); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("mv %s %s", tempPath, destination)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return comm.Upload(destination, contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provisioner) linuxUploadRingKey(o terraform.UIOutput, comm communicator.Communicator) error {
|
||||||
|
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf(`echo -e "%s" | hab ring key import`, p.RingKeyContent)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provisioner) linuxUploadCtlSecret(o terraform.UIOutput, comm communicator.Communicator) error {
|
||||||
|
destination := fmt.Sprintf("/hab/sup/default/CTL_SECRET")
|
||||||
|
// Create the destination directory
|
||||||
|
err := p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("mkdir -p %s", filepath.Dir(destination))))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyContent := strings.NewReader(p.CtlSecret)
|
||||||
|
if p.UseSudo {
|
||||||
|
tempPath := fmt.Sprintf("/tmp/CTL_SECRET")
|
||||||
|
if err := comm.Upload(tempPath, keyContent); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("mv %s %s && chown root:root %s && chmod 0600 %s", tempPath, destination, destination, destination)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return comm.Upload(destination, keyContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Habitat Services
|
||||||
|
//
|
||||||
|
func (p *provisioner) linuxStartHabitatService(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
|
||||||
|
var options string
|
||||||
|
|
||||||
|
if err := p.linuxInstallHabitatPackage(o, comm, service); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := p.uploadUserTOML(o, comm, service); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload service group key
|
||||||
|
if service.ServiceGroupKey != "" {
|
||||||
|
err := p.uploadServiceGroupKey(o, comm, service.ServiceGroupKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.Topology != "" {
|
||||||
|
options += fmt.Sprintf(" --topology %s", service.Topology)
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.Strategy != "" {
|
||||||
|
options += fmt.Sprintf(" --strategy %s", service.Strategy)
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.Channel != "" {
|
||||||
|
options += fmt.Sprintf(" --channel %s", service.Channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.URL != "" {
|
||||||
|
options += fmt.Sprintf(" --url %s", service.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.Group != "" {
|
||||||
|
options += fmt.Sprintf(" --group %s", service.Group)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, bind := range service.Binds {
|
||||||
|
options += fmt.Sprintf(" --bind %s", bind.toBindString())
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab svc load %s %s", service.Name, options)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// In the future we'll remove the dedicated install once the synchronous load feature in hab-sup is
|
||||||
|
// available. Until then we install here to provide output and a noisy failure mechanism because
|
||||||
|
// if you install with the pkg load, it occurs asynchronously and fails quietly.
|
||||||
|
func (p *provisioner) linuxInstallHabitatPackage(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
|
||||||
|
var options string
|
||||||
|
|
||||||
|
if service.Channel != "" {
|
||||||
|
options += fmt.Sprintf(" --channel %s", service.Channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if service.URL != "" {
|
||||||
|
options += fmt.Sprintf(" --url %s", service.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("hab pkg install %s %s", service.Name, options)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provisioner) uploadServiceGroupKey(o terraform.UIOutput, comm communicator.Communicator, key string) error {
|
||||||
|
keyName := strings.Split(key, "\n")[1]
|
||||||
|
o.Output("Uploading service group key: " + keyName)
|
||||||
|
keyFileName := fmt.Sprintf("%s.box.key", keyName)
|
||||||
|
destPath := path.Join("/hab/cache/keys", keyFileName)
|
||||||
|
keyContent := strings.NewReader(key)
|
||||||
|
if p.UseSudo {
|
||||||
|
tempPath := path.Join("/tmp", keyFileName)
|
||||||
|
if err := comm.Upload(tempPath, keyContent); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.runCommand(o, comm, p.linuxGetCommand(fmt.Sprintf("mv %s %s", tempPath, destPath)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return comm.Upload(destPath, keyContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provisioner) uploadUserTOML(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
|
||||||
|
// Create the hab svc directory to lay down the user.toml before loading the service
|
||||||
|
o.Output("Uploading user.toml for service: " + service.Name)
|
||||||
|
destDir := fmt.Sprintf("/hab/user/%s/config", service.getPackageName(service.Name))
|
||||||
|
command := p.linuxGetCommand(fmt.Sprintf("mkdir -p %s", destDir))
|
||||||
|
if err := p.runCommand(o, comm, command); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
userToml := strings.NewReader(service.UserTOML)
|
||||||
|
|
||||||
|
if p.UseSudo {
|
||||||
|
if err := comm.Upload(fmt.Sprintf("/tmp/user-%s.toml", service.getServiceNameChecksum()), userToml); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
command = p.linuxGetCommand(fmt.Sprintf("mv /tmp/user-%s.toml %s/user.toml", service.getServiceNameChecksum(), destDir))
|
||||||
|
return p.runCommand(o, comm, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
return comm.Upload(path.Join(destDir, "user.toml"), userToml)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provisioner) linuxGetCommand(command string) string {
|
||||||
|
// Always set HAB_NONINTERACTIVE & HAB_NOCOLORING
|
||||||
|
env := fmt.Sprintf("env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true")
|
||||||
|
|
||||||
|
// Set builder auth token
|
||||||
|
if p.BuilderAuthToken != "" {
|
||||||
|
env += fmt.Sprintf(" HAB_AUTH_TOKEN=%s", p.BuilderAuthToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.UseSudo {
|
||||||
|
command = fmt.Sprintf("%s sudo -E /bin/bash -c '%s'", env, command)
|
||||||
|
} else {
|
||||||
|
command = fmt.Sprintf("%s /bin/bash -c '%s'", env, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
return command
|
||||||
|
}
|
|
@ -0,0 +1,348 @@
|
||||||
|
package habitat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/communicator"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const linuxDefaultSystemdUnitFileContents = `[Unit]
|
||||||
|
Description=Habitat Supervisor
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/bin/hab sup run --peer host1 --peer 1.2.3.4 --auto-update
|
||||||
|
Restart=on-failure
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target`
|
||||||
|
|
||||||
|
const linuxCustomSystemdUnitFileContents = `[Unit]
|
||||||
|
Description=Habitat Supervisor
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/bin/hab sup run --listen-ctl 192.168.0.1:8443 --listen-gossip 192.168.10.1:9443 --listen-http 192.168.20.1:8080 --peer host1 --peer host2 --peer 1.2.3.4 --peer 5.6.7.8 --peer foo.example.com
|
||||||
|
Restart=on-failure
|
||||||
|
Environment="HAB_SUP_GATEWAY_AUTH_TOKEN=ea7-beef"
|
||||||
|
Environment="HAB_AUTH_TOKEN=dead-beef"
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target`
|
||||||
|
|
||||||
|
func TestLinuxProvisioner_linuxInstallHabitat(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
Config map[string]interface{}
|
||||||
|
Commands map[string]bool
|
||||||
|
}{
|
||||||
|
"Installation with sudo": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.79.1",
|
||||||
|
"auto_update": true,
|
||||||
|
"use_sudo": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'curl --silent -L0 https://raw.githubusercontent.com/habitat-sh/habitat/master/components/hab/install.sh > install.sh'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'bash ./install.sh -v 0.79.1'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab install core/busybox'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab pkg exec core/busybox adduser -D -g \"\" hab'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'rm -f install.sh'": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Installation without sudo": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.79.1",
|
||||||
|
"auto_update": true,
|
||||||
|
"use_sudo": false,
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true /bin/bash -c 'curl --silent -L0 https://raw.githubusercontent.com/habitat-sh/habitat/master/components/hab/install.sh > install.sh'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true /bin/bash -c 'bash ./install.sh -v 0.79.1'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true /bin/bash -c 'hab install core/busybox'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true /bin/bash -c 'hab pkg exec core/busybox adduser -D -g \"\" hab'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true /bin/bash -c 'rm -f install.sh'": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Installation with Habitat license acceptance": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.81.0",
|
||||||
|
"accept_license": true,
|
||||||
|
"auto_update": true,
|
||||||
|
"use_sudo": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'curl --silent -L0 https://raw.githubusercontent.com/habitat-sh/habitat/master/components/hab/install.sh > install.sh'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'bash ./install.sh -v 0.81.0'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab install core/busybox'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab pkg exec core/busybox adduser -D -g \"\" hab'": true,
|
||||||
|
"env HAB_LICENSE=accept sudo -E /bin/bash -c 'hab -V'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'rm -f install.sh'": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := new(terraform.MockUIOutput)
|
||||||
|
c := new(communicator.MockCommunicator)
|
||||||
|
|
||||||
|
for k, tc := range cases {
|
||||||
|
c.Commands = tc.Commands
|
||||||
|
|
||||||
|
p, err := decodeConfig(
|
||||||
|
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.linuxInstallHabitat(o, c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test %q failed: %v", k, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinuxProvisioner_linuxStartHabitat(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
Config map[string]interface{}
|
||||||
|
Commands map[string]bool
|
||||||
|
Uploads map[string]string
|
||||||
|
}{
|
||||||
|
"Start systemd Habitat with sudo": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.79.1",
|
||||||
|
"auto_update": true,
|
||||||
|
"use_sudo": true,
|
||||||
|
"service_name": "hab-sup",
|
||||||
|
"peer": "--peer host1",
|
||||||
|
"peers": []interface{}{"1.2.3.4"},
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab install core/hab-sup/0.79.1'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'systemctl enable hab-sup && systemctl start hab-sup'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'mv /tmp/hab-sup.service /etc/systemd/system/hab-sup.service'": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Uploads: map[string]string{
|
||||||
|
"/tmp/hab-sup.service": linuxDefaultSystemdUnitFileContents,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Start systemd Habitat without sudo": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.79.1",
|
||||||
|
"auto_update": true,
|
||||||
|
"use_sudo": false,
|
||||||
|
"service_name": "hab-sup",
|
||||||
|
"peer": "--peer host1",
|
||||||
|
"peers": []interface{}{"1.2.3.4"},
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true /bin/bash -c 'hab install core/hab-sup/0.79.1'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true /bin/bash -c 'systemctl enable hab-sup && systemctl start hab-sup'": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Uploads: map[string]string{
|
||||||
|
"/etc/systemd/system/hab-sup.service": linuxDefaultSystemdUnitFileContents,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Start unmanaged Habitat with sudo": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.81.0",
|
||||||
|
"license": "accept-no-persist",
|
||||||
|
"auto_update": true,
|
||||||
|
"use_sudo": true,
|
||||||
|
"service_type": "unmanaged",
|
||||||
|
"peer": "--peer host1",
|
||||||
|
"peers": []interface{}{"1.2.3.4"},
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab install core/hab-sup/0.81.0'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'mkdir -p /hab/sup/default && chmod o+w /hab/sup/default'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c '(setsid hab sup run --peer host1 --peer 1.2.3.4 --auto-update > /hab/sup/default/sup.log 2>&1 <&1 &) ; sleep 1'": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Uploads: map[string]string{
|
||||||
|
"/etc/systemd/system/hab-sup.service": linuxDefaultSystemdUnitFileContents,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Start Habitat with custom config": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.79.1",
|
||||||
|
"auto_update": false,
|
||||||
|
"use_sudo": true,
|
||||||
|
"service_name": "hab-sup",
|
||||||
|
"peer": "--peer host1 --peer host2",
|
||||||
|
"peers": []interface{}{"1.2.3.4", "5.6.7.8", "foo.example.com"},
|
||||||
|
"listen_ctl": "192.168.0.1:8443",
|
||||||
|
"listen_gossip": "192.168.10.1:9443",
|
||||||
|
"listen_http": "192.168.20.1:8080",
|
||||||
|
"builder_auth_token": "dead-beef",
|
||||||
|
"gateway_auth_token": "ea7-beef",
|
||||||
|
"ctl_secret": "bad-beef",
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true HAB_AUTH_TOKEN=dead-beef sudo -E /bin/bash -c 'hab install core/hab-sup/0.79.1'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true HAB_AUTH_TOKEN=dead-beef sudo -E /bin/bash -c 'systemctl enable hab-sup && systemctl start hab-sup'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true HAB_AUTH_TOKEN=dead-beef sudo -E /bin/bash -c 'mv /tmp/hab-sup.service /etc/systemd/system/hab-sup.service'": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Uploads: map[string]string{
|
||||||
|
"/tmp/hab-sup.service": linuxCustomSystemdUnitFileContents,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := new(terraform.MockUIOutput)
|
||||||
|
c := new(communicator.MockCommunicator)
|
||||||
|
|
||||||
|
for k, tc := range cases {
|
||||||
|
c.Commands = tc.Commands
|
||||||
|
c.Uploads = tc.Uploads
|
||||||
|
|
||||||
|
p, err := decodeConfig(
|
||||||
|
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.linuxStartHabitat(o, c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test %q failed: %v", k, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinuxProvisioner_linuxUploadRingKey(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
Config map[string]interface{}
|
||||||
|
Commands map[string]bool
|
||||||
|
}{
|
||||||
|
"Upload ring key": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.79.1",
|
||||||
|
"auto_update": true,
|
||||||
|
"use_sudo": true,
|
||||||
|
"service_name": "hab-sup",
|
||||||
|
"peers": []interface{}{"1.2.3.4"},
|
||||||
|
"ring_key": "test-ring",
|
||||||
|
"ring_key_content": "dead-beef",
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'echo -e \"dead-beef\" | hab ring key import'": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := new(terraform.MockUIOutput)
|
||||||
|
c := new(communicator.MockCommunicator)
|
||||||
|
|
||||||
|
for k, tc := range cases {
|
||||||
|
c.Commands = tc.Commands
|
||||||
|
|
||||||
|
p, err := decodeConfig(
|
||||||
|
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.linuxUploadRingKey(o, c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Test %q failed: %v", k, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLinuxProvisioner_linuxStartHabitatService(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
Config map[string]interface{}
|
||||||
|
Commands map[string]bool
|
||||||
|
Uploads map[string]string
|
||||||
|
}{
|
||||||
|
"Start Habitat service with sudo": {
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"version": "0.79.1",
|
||||||
|
"auto_update": false,
|
||||||
|
"use_sudo": true,
|
||||||
|
"service_name": "hab-sup",
|
||||||
|
"peers": []interface{}{"1.2.3.4"},
|
||||||
|
"ring_key": "test-ring",
|
||||||
|
"ring_key_content": "dead-beef",
|
||||||
|
"service": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "core/foo",
|
||||||
|
"topology": "standalone",
|
||||||
|
"strategy": "none",
|
||||||
|
"channel": "stable",
|
||||||
|
"user_toml": "[config]\nlisten = 0.0.0.0:8080",
|
||||||
|
"bind": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"alias": "backend",
|
||||||
|
"service": "bar",
|
||||||
|
"group": "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "core/bar",
|
||||||
|
"topology": "standalone",
|
||||||
|
"strategy": "rolling",
|
||||||
|
"channel": "staging",
|
||||||
|
"user_toml": "[config]\nlisten = 0.0.0.0:443",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Commands: map[string]bool{
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab pkg install core/foo --channel stable'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'mkdir -p /hab/user/foo/config'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'mv /tmp/user-a5b83ec1b302d109f41852ae17379f75c36dff9bc598aae76b6f7c9cd425fd76.toml /hab/user/foo/config/user.toml'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab svc load core/foo --topology standalone --strategy none --channel stable --bind backend:bar.default'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab pkg install core/bar --channel staging'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'mkdir -p /hab/user/bar/config'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'mv /tmp/user-6466ae3283ae1bd4737b00367bc676c6465b25682169ea5f7da222f3f078a5bf.toml /hab/user/bar/config/user.toml'": true,
|
||||||
|
"env HAB_NONINTERACTIVE=true HAB_NOCOLORING=true sudo -E /bin/bash -c 'hab svc load core/bar --topology standalone --strategy rolling --channel staging'": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
Uploads: map[string]string{
|
||||||
|
"/tmp/user-a5b83ec1b302d109f41852ae17379f75c36dff9bc598aae76b6f7c9cd425fd76.toml": "[config]\nlisten = 0.0.0.0:8080",
|
||||||
|
"/tmp/user-6466ae3283ae1bd4737b00367bc676c6465b25682169ea5f7da222f3f078a5bf.toml": "[config]\nlisten = 0.0.0.0:443",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
o := new(terraform.MockUIOutput)
|
||||||
|
c := new(communicator.MockCommunicator)
|
||||||
|
|
||||||
|
for k, tc := range cases {
|
||||||
|
c.Commands = tc.Commands
|
||||||
|
c.Uploads = tc.Uploads
|
||||||
|
|
||||||
|
p, err := decodeConfig(
|
||||||
|
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
for _, s := range p.Services {
|
||||||
|
err = p.linuxStartHabitatService(o, c, s)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
for _, e := range errs {
|
||||||
|
t.Logf("Test %q failed: %v", k, e)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,55 +1,38 @@
|
||||||
package habitat
|
package habitat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
|
||||||
|
|
||||||
version "github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/terraform/communicator"
|
"github.com/hashicorp/terraform/communicator"
|
||||||
"github.com/hashicorp/terraform/communicator/remote"
|
"github.com/hashicorp/terraform/communicator/remote"
|
||||||
|
"github.com/hashicorp/terraform/configs/hcl2shim"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/helper/validation"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
linereader "github.com/mitchellh/go-linereader"
|
"github.com/mitchellh/go-linereader"
|
||||||
)
|
)
|
||||||
|
|
||||||
const installURL = "https://raw.githubusercontent.com/habitat-sh/habitat/master/components/hab/install.sh"
|
|
||||||
const systemdUnit = `
|
|
||||||
[Unit]
|
|
||||||
Description=Habitat Supervisor
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStart=/bin/hab sup run {{ .SupOptions }}
|
|
||||||
Restart=on-failure
|
|
||||||
{{ if .BuilderAuthToken -}}
|
|
||||||
Environment="HAB_AUTH_TOKEN={{ .BuilderAuthToken }}"
|
|
||||||
{{ end -}}
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target
|
|
||||||
`
|
|
||||||
|
|
||||||
var serviceTypes = map[string]bool{"unmanaged": true, "systemd": true}
|
|
||||||
var updateStrategies = map[string]bool{"at-once": true, "rolling": true, "none": true}
|
|
||||||
var topologies = map[string]bool{"leader": true, "standalone": true}
|
|
||||||
|
|
||||||
type provisionFn func(terraform.UIOutput, communicator.Communicator) error
|
|
||||||
|
|
||||||
type provisioner struct {
|
type provisioner struct {
|
||||||
Version string
|
Version string
|
||||||
|
AutoUpdate bool
|
||||||
|
HttpDisable bool
|
||||||
Services []Service
|
Services []Service
|
||||||
PermanentPeer bool
|
PermanentPeer bool
|
||||||
|
ListenCtl string
|
||||||
ListenGossip string
|
ListenGossip string
|
||||||
ListenHTTP string
|
ListenHTTP string
|
||||||
Peer string
|
Peer string
|
||||||
|
Peers []string
|
||||||
RingKey string
|
RingKey string
|
||||||
RingKeyContent string
|
RingKeyContent string
|
||||||
|
CtlSecret string
|
||||||
SkipInstall bool
|
SkipInstall bool
|
||||||
UseSudo bool
|
UseSudo bool
|
||||||
ServiceType string
|
ServiceType string
|
||||||
|
@ -57,13 +40,24 @@ type provisioner struct {
|
||||||
URL string
|
URL string
|
||||||
Channel string
|
Channel string
|
||||||
Events string
|
Events string
|
||||||
OverrideName string
|
|
||||||
Organization string
|
Organization string
|
||||||
|
GatewayAuthToken string
|
||||||
BuilderAuthToken string
|
BuilderAuthToken string
|
||||||
SupOptions string
|
SupOptions string
|
||||||
AcceptLicense bool
|
AcceptLicense bool
|
||||||
|
|
||||||
|
installHabitat provisionFn
|
||||||
|
startHabitat provisionFn
|
||||||
|
uploadRingKey provisionFn
|
||||||
|
uploadCtlSecret provisionFn
|
||||||
|
startHabitatService provisionServiceFn
|
||||||
|
|
||||||
|
osType string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type provisionFn func(terraform.UIOutput, communicator.Communicator) error
|
||||||
|
type provisionServiceFn func(terraform.UIOutput, communicator.Communicator, Service) error
|
||||||
|
|
||||||
func Provisioner() terraform.ResourceProvisioner {
|
func Provisioner() terraform.ResourceProvisioner {
|
||||||
return &schema.Provisioner{
|
return &schema.Provisioner{
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
|
@ -71,14 +65,30 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
"auto_update": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
},
|
||||||
|
"http_disable": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
},
|
||||||
"peer": &schema.Schema{
|
"peer": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"service_type": &schema.Schema{
|
"peers": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeList,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: "systemd",
|
},
|
||||||
|
"service_type": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "systemd",
|
||||||
|
ValidateFunc: validation.StringInSlice([]string{"systemd", "unmanaged"}, false),
|
||||||
},
|
},
|
||||||
"service_name": &schema.Schema{
|
"service_name": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
|
@ -99,6 +109,10 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Default: false,
|
Default: false,
|
||||||
},
|
},
|
||||||
|
"listen_ctl": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
"listen_gossip": &schema.Schema{
|
"listen_gossip": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
@ -115,9 +129,25 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
"ctl_secret": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
"url": &schema.Schema{
|
"url": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) {
|
||||||
|
u, err := url.Parse(val.(string))
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("invalid URL specified for %q: %v", key, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme == "" {
|
||||||
|
errs = append(errs, fmt.Errorf("invalid URL specified for %q (scheme must be specified)", key))
|
||||||
|
}
|
||||||
|
|
||||||
|
return warns, errs
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"channel": &schema.Schema{
|
"channel": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
|
@ -127,11 +157,11 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"override_name": &schema.Schema{
|
"organization": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"organization": &schema.Schema{
|
"gateway_auth_token": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
@ -173,19 +203,20 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"topology": &schema.Schema{
|
"topology": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
ValidateFunc: validation.StringInSlice([]string{"leader", "standalone"}, false),
|
||||||
},
|
},
|
||||||
"user_toml": &schema.Schema{
|
"user_toml": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"strategy": &schema.Schema{
|
"strategy": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
ValidateFunc: validation.StringInSlice([]string{"none", "rolling", "at-once"}, false),
|
||||||
},
|
},
|
||||||
"channel": &schema.Schema{
|
"channel": &schema.Schema{
|
||||||
|
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
|
@ -196,6 +227,18 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||||
"url": &schema.Schema{
|
"url": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) {
|
||||||
|
u, err := url.Parse(val.(string))
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("invalid URL specified for %q: %v", key, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Scheme == "" {
|
||||||
|
errs = append(errs, fmt.Errorf("invalid URL specified for %q (scheme must be specified)", key))
|
||||||
|
}
|
||||||
|
|
||||||
|
return warns, errs
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"application": &schema.Schema{
|
"application": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
|
@ -205,10 +248,6 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
},
|
},
|
||||||
"override_name": &schema.Schema{
|
|
||||||
Type: schema.TypeString,
|
|
||||||
Optional: true,
|
|
||||||
},
|
|
||||||
"service_key": &schema.Schema{
|
"service_key": &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
@ -233,6 +272,30 @@ func applyFn(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Automatically determine the OS type
|
||||||
|
switch t := s.Ephemeral.ConnInfo["type"]; t {
|
||||||
|
case "ssh", "":
|
||||||
|
p.osType = "linux"
|
||||||
|
case "winrm":
|
||||||
|
p.osType = "windows"
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported connection type: %s", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch p.osType {
|
||||||
|
case "linux":
|
||||||
|
p.installHabitat = p.linuxInstallHabitat
|
||||||
|
p.uploadRingKey = p.linuxUploadRingKey
|
||||||
|
p.uploadCtlSecret = p.linuxUploadCtlSecret
|
||||||
|
p.startHabitat = p.linuxStartHabitat
|
||||||
|
p.startHabitatService = p.linuxStartHabitatService
|
||||||
|
case "windows":
|
||||||
|
return fmt.Errorf("windows is not supported yet for the habitat provisioner")
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported os type: %s", p.osType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a new communicator
|
||||||
comm, err := communicator.New(s)
|
comm, err := communicator.New(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -241,6 +304,7 @@ func applyFn(ctx context.Context) error {
|
||||||
retryCtx, 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
|
||||||
err = communicator.Retry(retryCtx, func() error {
|
err = communicator.Retry(retryCtx, func() error {
|
||||||
return comm.Connect(o)
|
return comm.Connect(o)
|
||||||
})
|
})
|
||||||
|
@ -252,7 +316,7 @@ func applyFn(ctx context.Context) error {
|
||||||
|
|
||||||
if !p.SkipInstall {
|
if !p.SkipInstall {
|
||||||
o.Output("Installing habitat...")
|
o.Output("Installing habitat...")
|
||||||
if err := p.installHab(o, comm); err != nil {
|
if err := p.installHabitat(o, comm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -264,15 +328,22 @@ func applyFn(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.CtlSecret != "" {
|
||||||
|
o.Output("Uploading ctl secret...")
|
||||||
|
if err := p.uploadCtlSecret(o, comm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
o.Output("Starting the habitat supervisor...")
|
o.Output("Starting the habitat supervisor...")
|
||||||
if err := p.startHab(o, comm); err != nil {
|
if err := p.startHabitat(o, comm); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Services != nil {
|
if p.Services != nil {
|
||||||
for _, service := range p.Services {
|
for _, service := range p.Services {
|
||||||
o.Output("Starting service: " + service.Name)
|
o.Output("Starting service: " + service.Name)
|
||||||
if err := p.startHabService(o, comm, service); err != nil {
|
if err := p.startHabitatService(o, comm, service); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,17 +353,11 @@ func applyFn(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) {
|
func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) {
|
||||||
serviceType, ok := c.Get("service_type")
|
ringKeyContent, ok := c.Get("ring_key_content")
|
||||||
if ok {
|
if ok && ringKeyContent != "" && ringKeyContent != hcl2shim.UnknownVariableValue {
|
||||||
if !serviceTypes[serviceType.(string)] {
|
ringKey, ringOk := c.Get("ring_key")
|
||||||
es = append(es, errors.New(serviceType.(string)+" is not a valid service_type."))
|
if ringOk && ringKey == "" {
|
||||||
}
|
es = append(es, errors.New("if ring_key_content is specified, ring_key must be specified as well"))
|
||||||
}
|
|
||||||
|
|
||||||
builderURL, ok := c.Get("url")
|
|
||||||
if ok {
|
|
||||||
if _, err := url.ParseRequestURI(builderURL.(string)); err != nil {
|
|
||||||
es = append(es, errors.New(builderURL.(string)+" is not a valid URL."))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,30 +384,12 @@ func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) {
|
||||||
// Validate service level configs
|
// Validate service level configs
|
||||||
services, ok := c.Get("service")
|
services, ok := c.Get("service")
|
||||||
if ok {
|
if ok {
|
||||||
for i, svc := range services.([]interface{}) {
|
data, dataOk := services.(string)
|
||||||
service, ok := svc.(map[string]interface{})
|
if dataOk {
|
||||||
if !ok {
|
es = append(es, fmt.Errorf("service '%v': must be a block", data))
|
||||||
es = append(es, fmt.Errorf("service %d: must be a block", i))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
strategy, ok := service["strategy"].(string)
|
|
||||||
if ok && !updateStrategies[strategy] {
|
|
||||||
es = append(es, errors.New(strategy+" is not a valid update strategy."))
|
|
||||||
}
|
|
||||||
|
|
||||||
topology, ok := service["topology"].(string)
|
|
||||||
if ok && !topologies[topology] {
|
|
||||||
es = append(es, errors.New(topology+" is not a valid topology"))
|
|
||||||
}
|
|
||||||
|
|
||||||
builderURL, ok := service["url"].(string)
|
|
||||||
if ok {
|
|
||||||
if _, err := url.ParseRequestURI(builderURL); err != nil {
|
|
||||||
es = append(es, errors.New(builderURL+" is not a valid URL."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ws, es
|
return ws, es
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,20 +405,23 @@ type Service struct {
|
||||||
UserTOML string
|
UserTOML string
|
||||||
AppName string
|
AppName string
|
||||||
Environment string
|
Environment string
|
||||||
OverrideName string
|
|
||||||
ServiceGroupKey string
|
ServiceGroupKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) getPackageName(fullName string) string {
|
||||||
|
return strings.Split(fullName, "/")[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) getServiceNameChecksum() string {
|
||||||
|
return fmt.Sprintf("%x", sha256.Sum256([]byte(s.Name)))
|
||||||
|
}
|
||||||
|
|
||||||
type Bind struct {
|
type Bind struct {
|
||||||
Alias string
|
Alias string
|
||||||
Service string
|
Service string
|
||||||
Group string
|
Group string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) getPackageName(fullName string) string {
|
|
||||||
return strings.Split(fullName, "/")[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bind) toBindString() string {
|
func (b *Bind) toBindString() string {
|
||||||
return fmt.Sprintf("%s:%s.%s", b.Alias, b.Service, b.Group)
|
return fmt.Sprintf("%s:%s.%s", b.Alias, b.Service, b.Group)
|
||||||
}
|
}
|
||||||
|
@ -379,7 +429,10 @@ func (b *Bind) toBindString() string {
|
||||||
func decodeConfig(d *schema.ResourceData) (*provisioner, error) {
|
func decodeConfig(d *schema.ResourceData) (*provisioner, error) {
|
||||||
p := &provisioner{
|
p := &provisioner{
|
||||||
Version: d.Get("version").(string),
|
Version: d.Get("version").(string),
|
||||||
|
AutoUpdate: d.Get("auto_update").(bool),
|
||||||
|
HttpDisable: d.Get("http_disable").(bool),
|
||||||
Peer: d.Get("peer").(string),
|
Peer: d.Get("peer").(string),
|
||||||
|
Peers: getPeers(d.Get("peers").([]interface{})),
|
||||||
Services: getServices(d.Get("service").(*schema.Set).List()),
|
Services: getServices(d.Get("service").(*schema.Set).List()),
|
||||||
UseSudo: d.Get("use_sudo").(bool),
|
UseSudo: d.Get("use_sudo").(bool),
|
||||||
AcceptLicense: d.Get("accept_license").(bool),
|
AcceptLicense: d.Get("accept_license").(bool),
|
||||||
|
@ -387,20 +440,30 @@ func decodeConfig(d *schema.ResourceData) (*provisioner, error) {
|
||||||
ServiceName: d.Get("service_name").(string),
|
ServiceName: d.Get("service_name").(string),
|
||||||
RingKey: d.Get("ring_key").(string),
|
RingKey: d.Get("ring_key").(string),
|
||||||
RingKeyContent: d.Get("ring_key_content").(string),
|
RingKeyContent: d.Get("ring_key_content").(string),
|
||||||
|
CtlSecret: d.Get("ctl_secret").(string),
|
||||||
PermanentPeer: d.Get("permanent_peer").(bool),
|
PermanentPeer: d.Get("permanent_peer").(bool),
|
||||||
|
ListenCtl: d.Get("listen_ctl").(string),
|
||||||
ListenGossip: d.Get("listen_gossip").(string),
|
ListenGossip: d.Get("listen_gossip").(string),
|
||||||
ListenHTTP: d.Get("listen_http").(string),
|
ListenHTTP: d.Get("listen_http").(string),
|
||||||
URL: d.Get("url").(string),
|
URL: d.Get("url").(string),
|
||||||
Channel: d.Get("channel").(string),
|
Channel: d.Get("channel").(string),
|
||||||
Events: d.Get("events").(string),
|
Events: d.Get("events").(string),
|
||||||
OverrideName: d.Get("override_name").(string),
|
|
||||||
Organization: d.Get("organization").(string),
|
Organization: d.Get("organization").(string),
|
||||||
BuilderAuthToken: d.Get("builder_auth_token").(string),
|
BuilderAuthToken: d.Get("builder_auth_token").(string),
|
||||||
|
GatewayAuthToken: d.Get("gateway_auth_token").(string),
|
||||||
}
|
}
|
||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPeers(v []interface{}) []string {
|
||||||
|
peers := make([]string, 0, len(v))
|
||||||
|
for _, rawPeerData := range v {
|
||||||
|
peers = append(peers, rawPeerData.(string))
|
||||||
|
}
|
||||||
|
return peers
|
||||||
|
}
|
||||||
|
|
||||||
func getServices(v []interface{}) []Service {
|
func getServices(v []interface{}) []Service {
|
||||||
services := make([]Service, 0, len(v))
|
services := make([]Service, 0, len(v))
|
||||||
for _, rawServiceData := range v {
|
for _, rawServiceData := range v {
|
||||||
|
@ -413,7 +476,6 @@ func getServices(v []interface{}) []Service {
|
||||||
url := (serviceData["url"].(string))
|
url := (serviceData["url"].(string))
|
||||||
app := (serviceData["application"].(string))
|
app := (serviceData["application"].(string))
|
||||||
env := (serviceData["environment"].(string))
|
env := (serviceData["environment"].(string))
|
||||||
override := (serviceData["override_name"].(string))
|
|
||||||
userToml := (serviceData["user_toml"].(string))
|
userToml := (serviceData["user_toml"].(string))
|
||||||
serviceGroupKey := (serviceData["service_key"].(string))
|
serviceGroupKey := (serviceData["service_key"].(string))
|
||||||
var bindStrings []string
|
var bindStrings []string
|
||||||
|
@ -438,7 +500,6 @@ func getServices(v []interface{}) []Service {
|
||||||
Binds: binds,
|
Binds: binds,
|
||||||
AppName: app,
|
AppName: app,
|
||||||
Environment: env,
|
Environment: env,
|
||||||
OverrideName: override,
|
|
||||||
ServiceGroupKey: serviceGroupKey,
|
ServiceGroupKey: serviceGroupKey,
|
||||||
}
|
}
|
||||||
services = append(services, service)
|
services = append(services, service)
|
||||||
|
@ -463,331 +524,6 @@ func getBinds(v []interface{}) []Bind {
|
||||||
return binds
|
return binds
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *provisioner) uploadRingKey(o terraform.UIOutput, comm communicator.Communicator) error {
|
|
||||||
command := fmt.Sprintf("echo '%s' | hab ring key import", p.RingKeyContent)
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("echo '%s' | sudo hab ring key import", p.RingKeyContent)
|
|
||||||
}
|
|
||||||
return p.runCommand(o, comm, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *provisioner) installHab(o terraform.UIOutput, comm communicator.Communicator) error {
|
|
||||||
// Build the install command
|
|
||||||
command := fmt.Sprintf("curl -L0 %s > install.sh", installURL)
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the install script
|
|
||||||
if p.Version == "" {
|
|
||||||
command = fmt.Sprintf("env HAB_NONINTERACTIVE=true bash ./install.sh ")
|
|
||||||
} else {
|
|
||||||
command = fmt.Sprintf("env HAB_NONINTERACTIVE=true bash ./install.sh -v %s", p.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo %s", command)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept the license
|
|
||||||
if p.AcceptLicense {
|
|
||||||
command = fmt.Sprintf("export HAB_LICENSE=accept; hab -V")
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo HAB_LICENSE=accept hab -V")
|
|
||||||
}
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.createHabUser(o, comm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.runCommand(o, comm, fmt.Sprintf("rm -f install.sh"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *provisioner) startHab(o terraform.UIOutput, comm communicator.Communicator) error {
|
|
||||||
// Install the supervisor first
|
|
||||||
var command string
|
|
||||||
if p.Version == "" {
|
|
||||||
command += fmt.Sprintf("hab install core/hab-sup")
|
|
||||||
} else {
|
|
||||||
command += fmt.Sprintf("hab install core/hab-sup/%s", p.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo -E %s", command)
|
|
||||||
}
|
|
||||||
|
|
||||||
command = fmt.Sprintf("env HAB_NONINTERACTIVE=true %s", command)
|
|
||||||
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build up sup options
|
|
||||||
options := ""
|
|
||||||
if p.PermanentPeer {
|
|
||||||
options += " -I"
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.ListenGossip != "" {
|
|
||||||
options += fmt.Sprintf(" --listen-gossip %s", p.ListenGossip)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.ListenHTTP != "" {
|
|
||||||
options += fmt.Sprintf(" --listen-http %s", p.ListenHTTP)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Peer != "" {
|
|
||||||
options += fmt.Sprintf(" --peer %s", p.Peer)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.RingKey != "" {
|
|
||||||
options += fmt.Sprintf(" --ring %s", p.RingKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.URL != "" {
|
|
||||||
options += fmt.Sprintf(" --url %s", p.URL)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Channel != "" {
|
|
||||||
options += fmt.Sprintf(" --channel %s", p.Channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Events != "" {
|
|
||||||
options += fmt.Sprintf(" --events %s", p.Events)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.OverrideName != "" {
|
|
||||||
options += fmt.Sprintf(" --override-name %s", p.OverrideName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Organization != "" {
|
|
||||||
options += fmt.Sprintf(" --org %s", p.Organization)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.SupOptions = options
|
|
||||||
|
|
||||||
switch p.ServiceType {
|
|
||||||
case "unmanaged":
|
|
||||||
return p.startHabUnmanaged(o, comm, options)
|
|
||||||
case "systemd":
|
|
||||||
return p.startHabSystemd(o, comm, options)
|
|
||||||
default:
|
|
||||||
return errors.New("Unsupported service type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *provisioner) startHabUnmanaged(o terraform.UIOutput, comm communicator.Communicator, options string) error {
|
|
||||||
// Create the sup directory for the log file
|
|
||||||
var command string
|
|
||||||
var token string
|
|
||||||
if p.UseSudo {
|
|
||||||
command = "sudo mkdir -p /hab/sup/default && sudo chmod o+w /hab/sup/default"
|
|
||||||
} else {
|
|
||||||
command = "mkdir -p /hab/sup/default && chmod o+w /hab/sup/default"
|
|
||||||
}
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.BuilderAuthToken != "" {
|
|
||||||
token = fmt.Sprintf("env HAB_AUTH_TOKEN=%s", p.BuilderAuthToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("(%s setsid sudo -E hab sup run %s > /hab/sup/default/sup.log 2>&1 &) ; sleep 1", token, options)
|
|
||||||
} else {
|
|
||||||
command = fmt.Sprintf("(%s setsid hab sup run %s > /hab/sup/default/sup.log 2>&1 <&1 &) ; sleep 1", token, options)
|
|
||||||
}
|
|
||||||
return p.runCommand(o, comm, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *provisioner) startHabSystemd(o terraform.UIOutput, comm communicator.Communicator, options string) error {
|
|
||||||
// Create a new template and parse the client config into it
|
|
||||||
unitString := template.Must(template.New("hab-supervisor.service").Parse(systemdUnit))
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
err := unitString.Execute(&buf, p)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error executing %s template: %s", "hab-supervisor.service", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var command string
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo echo '%s' | sudo tee /etc/systemd/system/%s.service > /dev/null", &buf, p.ServiceName)
|
|
||||||
} else {
|
|
||||||
command = fmt.Sprintf("echo '%s' | tee /etc/systemd/system/%s.service > /dev/null", &buf, p.ServiceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo systemctl enable hab-supervisor && sudo systemctl start hab-supervisor")
|
|
||||||
} else {
|
|
||||||
command = fmt.Sprintf("systemctl enable hab-supervisor && systemctl start hab-supervisor")
|
|
||||||
}
|
|
||||||
return p.runCommand(o, comm, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *provisioner) createHabUser(o terraform.UIOutput, comm communicator.Communicator) error {
|
|
||||||
addUser := false
|
|
||||||
// Install busybox to get us the user tools we need
|
|
||||||
command := fmt.Sprintf("env HAB_NONINTERACTIVE=true hab install core/busybox")
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo %s", command)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for existing hab user
|
|
||||||
command = fmt.Sprintf("hab pkg exec core/busybox id hab")
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo %s", command)
|
|
||||||
}
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
o.Output("No existing hab user detected, creating...")
|
|
||||||
addUser = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if addUser {
|
|
||||||
command = fmt.Sprintf("hab pkg exec core/busybox adduser -D -g \"\" hab")
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo %s", command)
|
|
||||||
}
|
|
||||||
return p.runCommand(o, comm, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// In the future we'll remove the dedicated install once the synchronous load feature in hab-sup is
|
|
||||||
// available. Until then we install here to provide output and a noisy failure mechanism because
|
|
||||||
// if you install with the pkg load, it occurs asynchronously and fails quietly.
|
|
||||||
func (p *provisioner) installHabPackage(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
|
|
||||||
var command string
|
|
||||||
options := ""
|
|
||||||
if service.Channel != "" {
|
|
||||||
options += fmt.Sprintf(" --channel %s", service.Channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
if service.URL != "" {
|
|
||||||
options += fmt.Sprintf(" --url %s", service.URL)
|
|
||||||
}
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("env HAB_NONINTERACTIVE=true sudo -E hab pkg install %s %s", service.Name, options)
|
|
||||||
} else {
|
|
||||||
command = fmt.Sprintf("env HAB_NONINTERACTIVE=true hab pkg install %s %s", service.Name, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.BuilderAuthToken != "" {
|
|
||||||
command = fmt.Sprintf("env HAB_AUTH_TOKEN=%s %s", p.BuilderAuthToken, command)
|
|
||||||
}
|
|
||||||
return p.runCommand(o, comm, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *provisioner) startHabService(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
|
|
||||||
var command string
|
|
||||||
if err := p.installHabPackage(o, comm, service); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := p.uploadUserTOML(o, comm, service); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload service group key
|
|
||||||
if service.ServiceGroupKey != "" {
|
|
||||||
p.uploadServiceGroupKey(o, comm, service.ServiceGroupKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
options := ""
|
|
||||||
if service.Topology != "" {
|
|
||||||
options += fmt.Sprintf(" --topology %s", service.Topology)
|
|
||||||
}
|
|
||||||
|
|
||||||
if service.Strategy != "" {
|
|
||||||
options += fmt.Sprintf(" --strategy %s", service.Strategy)
|
|
||||||
}
|
|
||||||
|
|
||||||
if service.Channel != "" {
|
|
||||||
options += fmt.Sprintf(" --channel %s", service.Channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
if service.URL != "" {
|
|
||||||
options += fmt.Sprintf(" --url %s", service.URL)
|
|
||||||
}
|
|
||||||
|
|
||||||
if service.Group != "" {
|
|
||||||
options += fmt.Sprintf(" --group %s", service.Group)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, bind := range service.Binds {
|
|
||||||
options += fmt.Sprintf(" --bind %s", bind.toBindString())
|
|
||||||
}
|
|
||||||
command = fmt.Sprintf("hab svc load %s %s", service.Name, options)
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo -E %s", command)
|
|
||||||
}
|
|
||||||
if p.BuilderAuthToken != "" {
|
|
||||||
command = fmt.Sprintf("env HAB_AUTH_TOKEN=%s %s", p.BuilderAuthToken, command)
|
|
||||||
}
|
|
||||||
return p.runCommand(o, comm, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *provisioner) uploadServiceGroupKey(o terraform.UIOutput, comm communicator.Communicator, key string) error {
|
|
||||||
keyName := strings.Split(key, "\n")[1]
|
|
||||||
o.Output("Uploading service group key: " + keyName)
|
|
||||||
keyFileName := fmt.Sprintf("%s.box.key", keyName)
|
|
||||||
destPath := path.Join("/hab/cache/keys", keyFileName)
|
|
||||||
keyContent := strings.NewReader(key)
|
|
||||||
if p.UseSudo {
|
|
||||||
tempPath := path.Join("/tmp", keyFileName)
|
|
||||||
if err := comm.Upload(tempPath, keyContent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
command := fmt.Sprintf("sudo mv %s %s", tempPath, destPath)
|
|
||||||
return p.runCommand(o, comm, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
return comm.Upload(destPath, keyContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *provisioner) uploadUserTOML(o terraform.UIOutput, comm communicator.Communicator, service Service) error {
|
|
||||||
// Create the hab svc directory to lay down the user.toml before loading the service
|
|
||||||
o.Output("Uploading user.toml for service: " + service.Name)
|
|
||||||
destDir := fmt.Sprintf("/hab/svc/%s", service.getPackageName(service.Name))
|
|
||||||
command := fmt.Sprintf("mkdir -p %s", destDir)
|
|
||||||
if p.UseSudo {
|
|
||||||
command = fmt.Sprintf("sudo %s", command)
|
|
||||||
}
|
|
||||||
if err := p.runCommand(o, comm, command); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
userToml := strings.NewReader(service.UserTOML)
|
|
||||||
|
|
||||||
if p.UseSudo {
|
|
||||||
if err := comm.Upload("/tmp/user.toml", userToml); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
command = fmt.Sprintf("sudo mv /tmp/user.toml %s", destDir)
|
|
||||||
return p.runCommand(o, comm, command)
|
|
||||||
}
|
|
||||||
|
|
||||||
return comm.Upload(path.Join(destDir, "user.toml"), userToml)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader) {
|
func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader) {
|
||||||
lr := linereader.New(r)
|
lr := linereader.New(r)
|
||||||
for line := range lr.Ch {
|
for line := range lr.Ch {
|
||||||
|
@ -811,7 +547,7 @@ func (p *provisioner) runCommand(o terraform.UIOutput, comm communicator.Communi
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := comm.Start(cmd); err != nil {
|
if err := comm.Start(cmd); err != nil {
|
||||||
return fmt.Errorf("Error executing command %q: %v", cmd.Command, err)
|
return fmt.Errorf("error executing command %q: %v", cmd.Command, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cmd.Wait(); err != nil {
|
if err := cmd.Wait(); err != nil {
|
||||||
|
@ -830,7 +566,7 @@ func getBindFromString(bind string) (Bind, error) {
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
if len(t) != 3 {
|
if len(t) != 3 {
|
||||||
return Bind{}, errors.New("Invalid bind specification: " + bind)
|
return Bind{}, errors.New("invalid bind specification: " + bind)
|
||||||
}
|
}
|
||||||
return Bind{Alias: t[0], Service: t[1], Group: t[2]}, nil
|
return Bind{Alias: t[0], Service: t[1], Group: t[2]}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ func TestProvisioner(t *testing.T) {
|
||||||
|
|
||||||
func TestResourceProvisioner_Validate_good(t *testing.T) {
|
func TestResourceProvisioner_Validate_good(t *testing.T) {
|
||||||
c := testConfig(t, map[string]interface{}{
|
c := testConfig(t, map[string]interface{}{
|
||||||
"peer": "1.2.3.4",
|
"peers": []interface{}{"1.2.3.4"},
|
||||||
"version": "0.32.0",
|
"version": "0.32.0",
|
||||||
"service_type": "systemd",
|
"service_type": "systemd",
|
||||||
"accept_license": false,
|
"accept_license": false,
|
||||||
|
@ -37,15 +37,16 @@ func TestResourceProvisioner_Validate_good(t *testing.T) {
|
||||||
func TestResourceProvisioner_Validate_bad(t *testing.T) {
|
func TestResourceProvisioner_Validate_bad(t *testing.T) {
|
||||||
c := testConfig(t, map[string]interface{}{
|
c := testConfig(t, map[string]interface{}{
|
||||||
"service_type": "invalidtype",
|
"service_type": "invalidtype",
|
||||||
|
"url": "badurl",
|
||||||
})
|
})
|
||||||
|
|
||||||
warn, errs := Provisioner().Validate(c)
|
warn, errs := Provisioner().Validate(c)
|
||||||
if len(warn) > 0 {
|
if len(warn) > 0 {
|
||||||
t.Fatalf("Warnings: %v", warn)
|
t.Fatalf("Warnings: %v", warn)
|
||||||
}
|
}
|
||||||
//Two errors, one for service_type, other for missing required accept_license argument
|
// 3 errors, bad service_type, bad url, missing accept_license
|
||||||
if len(errs) != 2 {
|
if len(errs) != 3 {
|
||||||
t.Fatalf("Should have one errors, got %d", len(errs))
|
t.Fatalf("Should have three errors, got %d", len(errs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +68,21 @@ func TestResourceProvisioner_Validate_bad_service_config(t *testing.T) {
|
||||||
t.Fatalf("Warnings: %v", warn)
|
t.Fatalf("Warnings: %v", warn)
|
||||||
}
|
}
|
||||||
if len(errs) != 3 {
|
if len(errs) != 3 {
|
||||||
t.Fatalf("Should have three errors")
|
t.Fatalf("Should have three errors, got %d", len(errs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResourceProvisioner_Validate_bad_service_definition(t *testing.T) {
|
||||||
|
c := testConfig(t, map[string]interface{}{
|
||||||
|
"service": "core/vault",
|
||||||
|
})
|
||||||
|
|
||||||
|
warn, errs := Provisioner().Validate(c)
|
||||||
|
if len(warn) > 0 {
|
||||||
|
t.Fatalf("Warnings: %v", warn)
|
||||||
|
}
|
||||||
|
if len(errs) != 3 {
|
||||||
|
t.Fatalf("Should have three errors, got %d", len(errs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/terraform/configs/configupgrade"
|
"github.com/hashicorp/terraform/configs/configupgrade"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,9 +11,9 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/hashicorp/hcl2/hclwrite"
|
"github.com/hashicorp/hcl/v2/hclwrite"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
|
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcled"
|
"github.com/hashicorp/hcl/v2/hcled"
|
||||||
"github.com/hashicorp/hcl2/hclparse"
|
"github.com/hashicorp/hcl/v2/hclparse"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
"github.com/mitchellh/colorstring"
|
"github.com/mitchellh/colorstring"
|
||||||
wordwrap "github.com/mitchellh/go-wordwrap"
|
wordwrap "github.com/mitchellh/go-wordwrap"
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
|
@ -178,7 +178,11 @@ func (c *ImportCommand) Run(args []string) int {
|
||||||
// We assume the same module as the resource address here, which
|
// We assume the same module as the resource address here, which
|
||||||
// may get resolved to an inherited provider when we construct the
|
// may get resolved to an inherited provider when we construct the
|
||||||
// import graph inside ctx.Import, called below.
|
// import graph inside ctx.Import, called below.
|
||||||
providerAddr = resourceRelAddr.DefaultProviderConfig().Absolute(addr.Module)
|
if rc != nil && rc.ProviderConfigRef != nil {
|
||||||
|
providerAddr = rc.ProviderConfigAddr().Absolute(addr.Module)
|
||||||
|
} else {
|
||||||
|
providerAddr = resourceRelAddr.DefaultProviderConfig().Absolute(addr.Module)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for user-supplied plugin path
|
// Check for user-supplied plugin path
|
||||||
|
@ -336,9 +340,10 @@ Options:
|
||||||
|
|
||||||
-no-color If specified, output won't contain any color.
|
-no-color If specified, output won't contain any color.
|
||||||
|
|
||||||
-provider=provider Specific provider to use for import. This is used for
|
-provider=provider Deprecated: Override the provider configuration to use
|
||||||
specifying aliases, such as "aws.eu". Defaults to the
|
when importing the object. By default, Terraform uses the
|
||||||
normal provider prefix of the resource being imported.
|
provider specified in the configuration for the target
|
||||||
|
resource, and that is the best behavior in most cases.
|
||||||
|
|
||||||
-state=PATH Path to the source state file. Defaults to the configured
|
-state=PATH Path to the source state file. Defaults to the configured
|
||||||
backend, or "terraform.tfstate"
|
backend, or "terraform.tfstate"
|
||||||
|
|
|
@ -536,6 +536,99 @@ func TestImport_customProvider(t *testing.T) {
|
||||||
testStateOutput(t, statePath, testImportCustomProviderStr)
|
testStateOutput(t, statePath, testImportCustomProviderStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This tests behavior when the provider name does not match the implied
|
||||||
|
// provider name
|
||||||
|
func TestImport_providerNameMismatch(t *testing.T) {
|
||||||
|
defer testChdir(t, testFixturePath("import-provider-mismatch"))()
|
||||||
|
|
||||||
|
statePath := testTempFile(t)
|
||||||
|
|
||||||
|
p := testProvider()
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &ImportCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
testingOverrides: &testingOverrides{
|
||||||
|
ProviderResolver: providers.ResolverFixed(
|
||||||
|
map[string]providers.Factory{
|
||||||
|
"test-beta": providers.FactoryFixed(p),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
configured := false
|
||||||
|
p.ConfigureNewFn = func(req providers.ConfigureRequest) providers.ConfigureResponse {
|
||||||
|
configured = true
|
||||||
|
|
||||||
|
cfg := req.Config
|
||||||
|
if !cfg.Type().HasAttribute("foo") {
|
||||||
|
return providers.ConfigureResponse{
|
||||||
|
Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("configuration has no foo argument")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if got, want := cfg.GetAttr("foo"), cty.StringVal("baz"); !want.RawEquals(got) {
|
||||||
|
return providers.ConfigureResponse{
|
||||||
|
Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("foo argument is %#v, but want %#v", got, want)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return providers.ConfigureResponse{}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.ImportResourceStateFn = nil
|
||||||
|
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
||||||
|
ImportedResources: []providers.ImportedResource{
|
||||||
|
{
|
||||||
|
TypeName: "test_instance",
|
||||||
|
State: cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"id": cty.StringVal("yay"),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p.GetSchemaReturn = &terraform.ProviderSchema{
|
||||||
|
Provider: &configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"foo": {Type: cty.String, Optional: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ResourceTypes: map[string]*configschema.Block{
|
||||||
|
"test_instance": {
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-provider", "test-beta",
|
||||||
|
"-state", statePath,
|
||||||
|
"test_instance.foo",
|
||||||
|
"bar",
|
||||||
|
}
|
||||||
|
|
||||||
|
if code := c.Run(args); code != 0 {
|
||||||
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the test-beta provider was configured
|
||||||
|
if !configured {
|
||||||
|
t.Fatal("Configure should be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.ImportResourceStateCalled {
|
||||||
|
t.Fatal("ImportResourceState (provider 'test-beta') should be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.ReadResourceCalled {
|
||||||
|
t.Fatal("ReadResource (provider 'test-beta' should be called")
|
||||||
|
}
|
||||||
|
|
||||||
|
testStateOutput(t, statePath, testImportProviderMismatchStr)
|
||||||
|
}
|
||||||
func TestImport_allowMissingResourceConfig(t *testing.T) {
|
func TestImport_allowMissingResourceConfig(t *testing.T) {
|
||||||
defer testChdir(t, testFixturePath("import-missing-resource-config"))()
|
defer testChdir(t, testFixturePath("import-missing-resource-config"))()
|
||||||
|
|
||||||
|
@ -845,3 +938,9 @@ test_instance.foo:
|
||||||
ID = yay
|
ID = yay
|
||||||
provider = provider.test.alias
|
provider = provider.test.alias
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const testImportProviderMismatchStr = `
|
||||||
|
test_instance.foo:
|
||||||
|
ID = yay
|
||||||
|
provider = provider.test-beta
|
||||||
|
`
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/terraform-config-inspect/tfconfig"
|
"github.com/hashicorp/terraform-config-inspect/tfconfig"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
|
@ -3,8 +3,8 @@ package jsonconfig
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/lang"
|
"github.com/hashicorp/terraform/lang"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
@ -64,7 +64,7 @@ func marshalExpressions(body hcl.Body, schema *configschema.Block) expressions {
|
||||||
// need the low-level schema.
|
// need the low-level schema.
|
||||||
lowSchema := hcldec.ImpliedSchema(schema.DecoderSpec())
|
lowSchema := hcldec.ImpliedSchema(schema.DecoderSpec())
|
||||||
// (lowSchema is an hcl.BodySchema:
|
// (lowSchema is an hcl.BodySchema:
|
||||||
// https://godoc.org/github.com/hashicorp/hcl2/hcl#BodySchema )
|
// https://godoc.org/github.com/hashicorp/hcl/v2/hcl#BodySchema )
|
||||||
|
|
||||||
// Use the low-level schema with the body to decode one level We'll just
|
// Use the low-level schema with the body to decode one level We'll just
|
||||||
// ignore any additional content that's not covered by the schema, which
|
// ignore any additional content that's not covered by the schema, which
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/errwrap"
|
"github.com/hashicorp/errwrap"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/command/clistate"
|
"github.com/hashicorp/terraform/command/clistate"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/hashicorp/terraform-config-inspect/tfconfig"
|
"github.com/hashicorp/terraform-config-inspect/tfconfig"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/configs/configload"
|
"github.com/hashicorp/terraform/configs/configload"
|
||||||
|
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
hcljson "github.com/hashicorp/hcl2/hcl/json"
|
hcljson "github.com/hashicorp/hcl/v2/json"
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
provider "test-beta" {
|
||||||
|
foo = "baz"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "test_instance" "foo" {
|
||||||
|
provider = "test-beta"
|
||||||
|
}
|
|
@ -230,10 +230,10 @@ Usage: terraform validate [options] [dir]
|
||||||
configuration and not accessing any remote services such as remote state,
|
configuration and not accessing any remote services such as remote state,
|
||||||
provider APIs, etc.
|
provider APIs, etc.
|
||||||
|
|
||||||
Validate runs checks that verify whether a configuration is
|
Validate runs checks that verify whether a configuration is syntactically
|
||||||
internally-consistent, regardless of any provided variables or existing
|
valid and internally consistent, regardless of any provided variables or
|
||||||
state. It is thus primarily useful for general verification of reusable
|
existing state. It is thus primarily useful for general verification of
|
||||||
modules, including correctness of attribute names and value types.
|
reusable modules, including correctness of attribute names and value types.
|
||||||
|
|
||||||
It is safe to run this command automatically, for example as a post-save
|
It is safe to run this command automatically, for example as a post-save
|
||||||
check in a text editor or as a test step for a re-usable module in a CI
|
check in a text editor or as a test step for a re-usable module in a CI
|
||||||
|
@ -247,14 +247,16 @@ Usage: terraform validate [options] [dir]
|
||||||
If dir is not specified, then the current directory will be used.
|
If dir is not specified, then the current directory will be used.
|
||||||
|
|
||||||
To verify configuration in the context of a particular run (a particular
|
To verify configuration in the context of a particular run (a particular
|
||||||
target workspace, operation variables, etc), use the terraform plan
|
target workspace, input variable values, etc), use the 'terraform plan'
|
||||||
subcommand instead, which includes an implied validation check.
|
command instead, which includes an implied validation check.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
-json Produce output in a machine-readable JSON format, suitable for
|
-json Produce output in a machine-readable JSON format, suitable for
|
||||||
use in e.g. text editor integrations.
|
use in text editor integrations and other automated systems.
|
||||||
|
Always disables color.
|
||||||
|
|
||||||
|
-no-color If specified, output won't contain any color.
|
||||||
`
|
`
|
||||||
return strings.TrimSpace(helpText)
|
return strings.TrimSpace(helpText)
|
||||||
}
|
}
|
||||||
|
|
|
@ -235,6 +235,10 @@ func (c *Communicator) Connect(o terraform.UIOutput) (err error) {
|
||||||
// Start a keepalive goroutine to help maintain the connection for
|
// Start a keepalive goroutine to help maintain the connection for
|
||||||
// long-running commands.
|
// long-running commands.
|
||||||
log.Printf("[DEBUG] starting ssh KeepAlives")
|
log.Printf("[DEBUG] starting ssh KeepAlives")
|
||||||
|
|
||||||
|
// We want a local copy of the ssh client pointer, so that a reconnect
|
||||||
|
// doesn't race with the running keep-alive loop.
|
||||||
|
sshClient := c.client
|
||||||
go func() {
|
go func() {
|
||||||
defer cancelKeepAlive()
|
defer cancelKeepAlive()
|
||||||
// Along with the KeepAlives generating packets to keep the tcp
|
// Along with the KeepAlives generating packets to keep the tcp
|
||||||
|
@ -249,7 +253,7 @@ func (c *Communicator) Connect(o terraform.UIOutput) (err error) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-t.C:
|
case <-t.C:
|
||||||
_, _, err := c.client.SendRequest("keepalive@terraform.io", true, nil)
|
_, _, err := sshClient.SendRequest("keepalive@terraform.io", true, nil)
|
||||||
respCh <- err
|
respCh <- err
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
hcl2 "github.com/hashicorp/hcl2/hcl"
|
hcl2 "github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hil/ast"
|
"github.com/hashicorp/hil/ast"
|
||||||
"github.com/hashicorp/terraform/helper/hilmapstructure"
|
"github.com/hashicorp/terraform/helper/hilmapstructure"
|
||||||
"github.com/hashicorp/terraform/plugin/discovery"
|
"github.com/hashicorp/terraform/plugin/discovery"
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/hashicorp/hil/ast"
|
"github.com/hashicorp/hil/ast"
|
||||||
"github.com/hashicorp/terraform/configs/hcl2shim"
|
"github.com/hashicorp/terraform/configs/hcl2shim"
|
||||||
|
|
||||||
hcl2 "github.com/hashicorp/hcl2/hcl"
|
hcl2 "github.com/hashicorp/hcl/v2"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"github.com/zclconf/go-cty/cty/convert"
|
"github.com/zclconf/go-cty/cty/convert"
|
||||||
"github.com/zclconf/go-cty/cty/function"
|
"github.com/zclconf/go-cty/cty/function"
|
||||||
|
|
|
@ -3,8 +3,8 @@ package config
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
hcl2 "github.com/hashicorp/hcl2/hcl"
|
hcl2 "github.com/hashicorp/hcl/v2"
|
||||||
hcl2syntax "github.com/hashicorp/hcl2/hcl/hclsyntax"
|
hcl2syntax "github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
gohcl2 "github.com/hashicorp/hcl2/gohcl"
|
hcl2 "github.com/hashicorp/hcl/v2"
|
||||||
hcl2 "github.com/hashicorp/hcl2/hcl"
|
gohcl2 "github.com/hashicorp/hcl/v2/gohcl"
|
||||||
hcl2parse "github.com/hashicorp/hcl2/hclparse"
|
hcl2parse "github.com/hashicorp/hcl/v2/hclparse"
|
||||||
"github.com/hashicorp/terraform/configs/hcl2shim"
|
"github.com/hashicorp/terraform/configs/hcl2shim"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
gohcl2 "github.com/hashicorp/hcl2/gohcl"
|
hcl2 "github.com/hashicorp/hcl/v2"
|
||||||
hcl2 "github.com/hashicorp/hcl2/hcl"
|
gohcl2 "github.com/hashicorp/hcl/v2/gohcl"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHCL2ConfigurableConfigurable(t *testing.T) {
|
func TestHCL2ConfigurableConfigurable(t *testing.T) {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"github.com/zclconf/go-cty/cty/convert"
|
"github.com/zclconf/go-cty/cty/convert"
|
||||||
|
|
||||||
hcl2 "github.com/hashicorp/hcl2/hcl"
|
hcl2 "github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hil"
|
"github.com/hashicorp/hil"
|
||||||
"github.com/hashicorp/hil/ast"
|
"github.com/hashicorp/hil/ast"
|
||||||
"github.com/mitchellh/copystructure"
|
"github.com/mitchellh/copystructure"
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
hcl2 "github.com/hashicorp/hcl2/hcl"
|
hcl2 "github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hil/ast"
|
"github.com/hashicorp/hil/ast"
|
||||||
"github.com/hashicorp/terraform/configs/hcl2shim"
|
"github.com/hashicorp/terraform/configs/hcl2shim"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package configs
|
package configs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package configs
|
package configs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
version "github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
version "github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
|
||||||
version "github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuildConfig(t *testing.T) {
|
func TestBuildConfig(t *testing.T) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
version "github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
version "github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/internal/modsdir"
|
"github.com/hashicorp/terraform/internal/modsdir"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-test/deep"
|
"github.com/go-test/deep"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package configschema
|
package configschema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/hcl2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mapLabelNames = []string{"key"}
|
var mapLabelNames = []string{"key"}
|
||||||
|
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"github.com/apparentlymart/go-dump/dump"
|
"github.com/apparentlymart/go-dump/dump"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
"github.com/hashicorp/hcl2/hcltest"
|
"github.com/hashicorp/hcl/v2/hcltest"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package configschema
|
package configschema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/hcl2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/didyoumean"
|
"github.com/hashicorp/terraform/helper/didyoumean"
|
||||||
|
|
|
@ -3,8 +3,8 @@ package configschema
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ package configupgrade
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
hcl2 "github.com/hashicorp/hcl2/hcl"
|
hcl2 "github.com/hashicorp/hcl/v2"
|
||||||
hcl2syntax "github.com/hashicorp/hcl2/hcl/hclsyntax"
|
hcl2syntax "github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
@ -75,28 +75,27 @@ func (d analysisData) GetForEachAttr(addr addrs.ForEachAttr, rng tfdiags.SourceR
|
||||||
return cty.DynamicVal, nil
|
return cty.DynamicVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d analysisData) GetResourceInstance(instAddr addrs.ResourceInstance, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
func (d analysisData) GetResource(addr addrs.Resource, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
||||||
log.Printf("[TRACE] configupgrade: Determining type for %s", instAddr)
|
log.Printf("[TRACE] configupgrade: Determining type for %s", addr)
|
||||||
addr := instAddr.Resource
|
|
||||||
|
|
||||||
// Our analysis pass should've found a suitable schema for every resource
|
// Our analysis pass should've found a suitable schema for every resource
|
||||||
// type in the module.
|
// type in the module.
|
||||||
providerType, ok := d.an.ResourceProviderType[addr]
|
providerType, ok := d.an.ResourceProviderType[addr]
|
||||||
if !ok {
|
if !ok {
|
||||||
// Should not be possible, since analysis visits every resource block.
|
// Should not be possible, since analysis visits every resource block.
|
||||||
log.Printf("[TRACE] configupgrade: analysis.GetResourceInstance doesn't have a provider type for %s", addr)
|
log.Printf("[TRACE] configupgrade: analysis.GetResource doesn't have a provider type for %s", addr)
|
||||||
return cty.DynamicVal, nil
|
return cty.DynamicVal, nil
|
||||||
}
|
}
|
||||||
providerSchema, ok := d.an.ProviderSchemas[providerType]
|
providerSchema, ok := d.an.ProviderSchemas[providerType]
|
||||||
if !ok {
|
if !ok {
|
||||||
// Should not be possible, since analysis loads schema for every provider.
|
// Should not be possible, since analysis loads schema for every provider.
|
||||||
log.Printf("[TRACE] configupgrade: analysis.GetResourceInstance doesn't have a provider schema for for %q", providerType)
|
log.Printf("[TRACE] configupgrade: analysis.GetResource doesn't have a provider schema for for %q", providerType)
|
||||||
return cty.DynamicVal, nil
|
return cty.DynamicVal, nil
|
||||||
}
|
}
|
||||||
schema, _ := providerSchema.SchemaForResourceAddr(addr)
|
schema, _ := providerSchema.SchemaForResourceAddr(addr)
|
||||||
if schema == nil {
|
if schema == nil {
|
||||||
// Should not be possible, since analysis loads schema for every provider.
|
// Should not be possible, since analysis loads schema for every provider.
|
||||||
log.Printf("[TRACE] configupgrade: analysis.GetResourceInstance doesn't have a schema for for %s", addr)
|
log.Printf("[TRACE] configupgrade: analysis.GetResource doesn't have a schema for for %s", addr)
|
||||||
return cty.DynamicVal, nil
|
return cty.DynamicVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,19 +105,11 @@ func (d analysisData) GetResourceInstance(instAddr addrs.ResourceInstance, rng t
|
||||||
// return a list or a single object type depending on whether count is
|
// return a list or a single object type depending on whether count is
|
||||||
// set and whether an instance key is given in the address.
|
// set and whether an instance key is given in the address.
|
||||||
if d.an.ResourceHasCount[addr] {
|
if d.an.ResourceHasCount[addr] {
|
||||||
if instAddr.Key == addrs.NoKey {
|
log.Printf("[TRACE] configupgrade: %s refers to counted instance, so result is a list of %#v", addr, objTy)
|
||||||
log.Printf("[TRACE] configupgrade: %s refers to counted instance without a key, so result is a list of %#v", instAddr, objTy)
|
return cty.UnknownVal(cty.List(objTy)), nil
|
||||||
return cty.UnknownVal(cty.List(objTy)), nil
|
|
||||||
}
|
|
||||||
log.Printf("[TRACE] configupgrade: %s refers to counted instance with a key, so result is single object", instAddr)
|
|
||||||
return cty.UnknownVal(objTy), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if instAddr.Key != addrs.NoKey {
|
log.Printf("[TRACE] configupgrade: %s refers to non-counted instance, so result is single object", addr)
|
||||||
log.Printf("[TRACE] configupgrade: %s refers to non-counted instance with a key, which is invalid", instAddr)
|
|
||||||
return cty.DynamicVal, nil
|
|
||||||
}
|
|
||||||
log.Printf("[TRACE] configupgrade: %s refers to non-counted instance without a key, so result is single object", instAddr)
|
|
||||||
return cty.UnknownVal(objTy), nil
|
return cty.UnknownVal(objTy), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,8 @@ import (
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
hcl2syntax "github.com/hashicorp/hcl2/hcl/hclsyntax"
|
hcl2syntax "github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
|
|
||||||
version "github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMaybeAlreadyUpgraded(t *testing.T) {
|
func TestMaybeAlreadyUpgraded(t *testing.T) {
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
|
|
||||||
hcl2 "github.com/hashicorp/hcl2/hcl"
|
hcl2 "github.com/hashicorp/hcl/v2"
|
||||||
hcl2write "github.com/hashicorp/hcl2/hclwrite"
|
hcl2write "github.com/hashicorp/hcl/v2/hclwrite"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Upgrade takes some input module sources and produces a new ModuleSources
|
// Upgrade takes some input module sources and produces a new ModuleSources
|
||||||
|
|
|
@ -11,8 +11,8 @@ import (
|
||||||
|
|
||||||
hcl1ast "github.com/hashicorp/hcl/hcl/ast"
|
hcl1ast "github.com/hashicorp/hcl/hcl/ast"
|
||||||
hcl1token "github.com/hashicorp/hcl/hcl/token"
|
hcl1token "github.com/hashicorp/hcl/hcl/token"
|
||||||
hcl2 "github.com/hashicorp/hcl2/hcl"
|
hcl2 "github.com/hashicorp/hcl/v2"
|
||||||
hcl2syntax "github.com/hashicorp/hcl2/hcl/hclsyntax"
|
hcl2syntax "github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/lang/blocktoattr"
|
"github.com/hashicorp/terraform/lang/blocktoattr"
|
||||||
"github.com/hashicorp/terraform/registry/regsrc"
|
"github.com/hashicorp/terraform/registry/regsrc"
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
hcl2 "github.com/hashicorp/hcl2/hcl"
|
hcl2 "github.com/hashicorp/hcl/v2"
|
||||||
hcl2syntax "github.com/hashicorp/hcl2/hcl/hclsyntax"
|
hcl2syntax "github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
hcl1ast "github.com/hashicorp/hcl/hcl/ast"
|
hcl1ast "github.com/hashicorp/hcl/hcl/ast"
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
hcl1printer "github.com/hashicorp/hcl/hcl/printer"
|
hcl1printer "github.com/hashicorp/hcl/hcl/printer"
|
||||||
hcl1token "github.com/hashicorp/hcl/hcl/token"
|
hcl1token "github.com/hashicorp/hcl/hcl/token"
|
||||||
|
|
||||||
hcl2 "github.com/hashicorp/hcl2/hcl"
|
hcl2 "github.com/hashicorp/hcl/v2"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package configs
|
package configs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func decodeDependsOn(attr *hcl.Attribute) ([]hcl.Traversal, hcl.Diagnostics) {
|
func decodeDependsOn(attr *hcl.Attribute) ([]hcl.Traversal, hcl.Diagnostics) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package hcl2shim
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
hcl2 "github.com/hashicorp/hcl2/hcl"
|
hcl2 "github.com/hashicorp/hcl/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SingleAttrBody is a weird implementation of hcl2.Body that acts as if
|
// SingleAttrBody is a weird implementation of hcl2.Body that acts as if
|
||||||
|
|
|
@ -3,7 +3,7 @@ package configs
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,9 +3,9 @@ package configs
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/gohcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2/gohcl"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ModuleCall represents a "module" block in a module or file.
|
// ModuleCall represents a "module" block in a module or file.
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-test/deep"
|
"github.com/go-test/deep"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoadModuleCall(t *testing.T) {
|
func TestLoadModuleCall(t *testing.T) {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"github.com/zclconf/go-cty/cty/convert"
|
"github.com/zclconf/go-cty/cty/convert"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package configs
|
package configs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MergeBodies creates a new HCL body that contains a combination of the
|
// MergeBodies creates a new HCL body that contains a combination of the
|
||||||
|
|
|
@ -3,8 +3,8 @@ package configs
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/gohcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2/gohcl"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@ package configs
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/ext/typeexpr"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/gohcl"
|
"github.com/hashicorp/hcl/v2/ext/typeexpr"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2/gohcl"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"github.com/zclconf/go-cty/cty/convert"
|
"github.com/zclconf/go-cty/cty/convert"
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hclparse"
|
"github.com/hashicorp/hcl/v2/hclparse"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package configs
|
package configs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoadConfigFile reads the file at the given path and parses it as a config
|
// LoadConfigFile reads the file at the given path and parses it as a config
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoadConfigDir reads the .tf and .tf.json files in the given directory
|
// LoadConfigDir reads the .tf and .tf.json files in the given directory
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestParseLoadConfigFileSuccess is a simple test that just verifies that
|
// TestParseLoadConfigFileSuccess is a simple test that just verifies that
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package configs
|
package configs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,9 @@ package configs
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/gohcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2/gohcl"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,7 @@ package configs
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provisioner represents a "provisioner" block when used within a
|
// Provisioner represents a "provisioner" block when used within a
|
||||||
|
|
|
@ -3,9 +3,9 @@ package configs
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/gohcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2/gohcl"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,8 +3,8 @@ package configs
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package configs
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package configs
|
package configs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
)
|
)
|
||||||
|
|
||||||
// exprIsNativeQuotedString determines whether the given expression looks like
|
// exprIsNativeQuotedString determines whether the given expression looks like
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
version "github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"github.com/zclconf/go-cty/cty/convert"
|
"github.com/zclconf/go-cty/cty/convert"
|
||||||
)
|
)
|
||||||
|
|
|
@ -124,13 +124,13 @@ representing the entire configuration.
|
||||||
|
|
||||||
Terraform expects configuration files written in the Terraform language, which
|
Terraform expects configuration files written in the Terraform language, which
|
||||||
is a DSL built on top of
|
is a DSL built on top of
|
||||||
[HCL](https://github.com/hashicorp/hcl2). Some parts of the configuration
|
[HCL](https://github.com/hashicorp/hcl). Some parts of the configuration
|
||||||
cannot be interpreted until we build and walk the graph, since they depend
|
cannot be interpreted until we build and walk the graph, since they depend
|
||||||
on the outcome of other parts of the configuration, and so these parts of
|
on the outcome of other parts of the configuration, and so these parts of
|
||||||
the configuration remain represented as the low-level HCL types
|
the configuration remain represented as the low-level HCL types
|
||||||
[hcl.Body](https://godoc.org/github.com/hashicorp/hcl2/hcl#Body)
|
[hcl.Body](https://godoc.org/github.com/hashicorp/hcl/v2/hcl#Body)
|
||||||
and
|
and
|
||||||
[hcl.Expression](https://godoc.org/github.com/hashicorp/hcl2/hcl#Expression),
|
[hcl.Expression](https://godoc.org/github.com/hashicorp/hcl/v2/hcl#Expression),
|
||||||
allowing Terraform to interpret them at a more appropriate time.
|
allowing Terraform to interpret them at a more appropriate time.
|
||||||
|
|
||||||
## State Manager
|
## State Manager
|
||||||
|
|
6
go.mod
6
go.mod
|
@ -64,11 +64,11 @@ require (
|
||||||
github.com/hashicorp/go-retryablehttp v0.5.2
|
github.com/hashicorp/go-retryablehttp v0.5.2
|
||||||
github.com/hashicorp/go-rootcerts v1.0.0
|
github.com/hashicorp/go-rootcerts v1.0.0
|
||||||
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 // indirect
|
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 // indirect
|
||||||
github.com/hashicorp/go-tfe v0.3.16
|
github.com/hashicorp/go-tfe v0.3.23
|
||||||
github.com/hashicorp/go-uuid v1.0.1
|
github.com/hashicorp/go-uuid v1.0.1
|
||||||
github.com/hashicorp/go-version v1.1.0
|
github.com/hashicorp/go-version v1.1.0
|
||||||
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f
|
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f
|
||||||
github.com/hashicorp/hcl2 v0.0.0-20190821123243-0c888d1241f6
|
github.com/hashicorp/hcl/v2 v2.0.0
|
||||||
github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590
|
github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590
|
||||||
github.com/hashicorp/logutils v1.0.0
|
github.com/hashicorp/logutils v1.0.0
|
||||||
github.com/hashicorp/memberlist v0.1.0 // indirect
|
github.com/hashicorp/memberlist v0.1.0 // indirect
|
||||||
|
@ -119,7 +119,7 @@ require (
|
||||||
github.com/xanzy/ssh-agent v0.2.1
|
github.com/xanzy/ssh-agent v0.2.1
|
||||||
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 // indirect
|
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 // indirect
|
||||||
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557
|
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557
|
||||||
github.com/zclconf/go-cty v1.0.1-0.20190708163926-19588f92a98f
|
github.com/zclconf/go-cty v1.1.0
|
||||||
github.com/zclconf/go-cty-yaml v1.0.1
|
github.com/zclconf/go-cty-yaml v1.0.1
|
||||||
go.uber.org/atomic v1.3.2 // indirect
|
go.uber.org/atomic v1.3.2 // indirect
|
||||||
go.uber.org/multierr v1.1.0 // indirect
|
go.uber.org/multierr v1.1.0 // indirect
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -198,8 +198,8 @@ github.com/hashicorp/go-slug v0.3.0 h1:L0c+AvH/J64iMNF4VqRaRku2DMTEuHioPVS7kMjWI
|
||||||
github.com/hashicorp/go-slug v0.3.0/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8=
|
github.com/hashicorp/go-slug v0.3.0/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8=
|
||||||
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 h1:7YOlAIO2YWnJZkQp7B5eFykaIY7C9JndqAFQyVV5BhM=
|
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 h1:7YOlAIO2YWnJZkQp7B5eFykaIY7C9JndqAFQyVV5BhM=
|
||||||
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||||
github.com/hashicorp/go-tfe v0.3.16 h1:GS2yv580p0co4j3FBVaC6Zahd9mxdCGehhJ0qqzFMH0=
|
github.com/hashicorp/go-tfe v0.3.23 h1:kd9hlFQvGubNF/CpF7T5AP/xU8uLUq8ANbI5xRDVSms=
|
||||||
github.com/hashicorp/go-tfe v0.3.16/go.mod h1:SuPHR+OcxvzBZNye7nGPfwZTEyd3rWPfLVbCgyZPezM=
|
github.com/hashicorp/go-tfe v0.3.23/go.mod h1:SuPHR+OcxvzBZNye7nGPfwZTEyd3rWPfLVbCgyZPezM=
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
@ -210,6 +210,8 @@ github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f h1:UdxlrJz4JOnY8W+DbLISwf2B8WXEolNRA8BGCwI9jws=
|
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f h1:UdxlrJz4JOnY8W+DbLISwf2B8WXEolNRA8BGCwI9jws=
|
||||||
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||||
|
github.com/hashicorp/hcl/v2 v2.0.0 h1:efQznTz+ydmQXq3BOnRa3AXzvCeTq1P4dKj/z5GLlY8=
|
||||||
|
github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90=
|
||||||
github.com/hashicorp/hcl2 v0.0.0-20190821123243-0c888d1241f6 h1:JImQpEeUQ+0DPFMaWzLA0GdUNPaUlCXLpfiqkSZBUfc=
|
github.com/hashicorp/hcl2 v0.0.0-20190821123243-0c888d1241f6 h1:JImQpEeUQ+0DPFMaWzLA0GdUNPaUlCXLpfiqkSZBUfc=
|
||||||
github.com/hashicorp/hcl2 v0.0.0-20190821123243-0c888d1241f6/go.mod h1:Cxv+IJLuBiEhQ7pBYGEuORa0nr4U994pE8mYLuFd7v0=
|
github.com/hashicorp/hcl2 v0.0.0-20190821123243-0c888d1241f6/go.mod h1:Cxv+IJLuBiEhQ7pBYGEuORa0nr4U994pE8mYLuFd7v0=
|
||||||
github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590 h1:2yzhWGdgQUWZUCNK+AoO35V+HTsgEmcM4J9IkArh7PI=
|
github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590 h1:2yzhWGdgQUWZUCNK+AoO35V+HTsgEmcM4J9IkArh7PI=
|
||||||
|
@ -388,8 +390,8 @@ github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q
|
||||||
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557 h1:Jpn2j6wHkC9wJv5iMfJhKqrZJx3TahFx+7sbZ7zQdxs=
|
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557 h1:Jpn2j6wHkC9wJv5iMfJhKqrZJx3TahFx+7sbZ7zQdxs=
|
||||||
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
|
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
|
||||||
github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||||
github.com/zclconf/go-cty v1.0.1-0.20190708163926-19588f92a98f h1:sq2p8SN6ji66CFEQFIWLlD/gFmGtr5hBrOzv5nLlGfA=
|
github.com/zclconf/go-cty v1.1.0 h1:uJwc9HiBOCpoKIObTQaLR+tsEXx1HBHnOsOOpcdhZgw=
|
||||||
github.com/zclconf/go-cty v1.0.1-0.20190708163926-19588f92a98f/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||||
github.com/zclconf/go-cty-yaml v1.0.1 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpURKNF8=
|
github.com/zclconf/go-cty-yaml v1.0.1 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpURKNF8=
|
||||||
github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
|
github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue