mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Merge branch 'main' into update-path-cwd
This commit is contained in:
commit
d7377ca141
8
.github/workflows/checks.yml
vendored
8
.github/workflows/checks.yml
vendored
@ -141,6 +141,8 @@ jobs:
|
||||
steps:
|
||||
- name: "Fetch source code"
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # We need to do comparisons against the main branch.
|
||||
|
||||
- name: Determine Go version
|
||||
id: go
|
||||
@ -178,9 +180,13 @@ jobs:
|
||||
restore-keys: |
|
||||
protobuf-tools-
|
||||
|
||||
- name: Install CI tooling
|
||||
run: |
|
||||
go install golang.org/x/tools/cmd/goimports@v0.1.11
|
||||
|
||||
- name: "Code consistency checks"
|
||||
run: |
|
||||
make fmtcheck generate staticcheck exhaustive protobuf
|
||||
make fmtcheck importscheck generate staticcheck exhaustive protobuf
|
||||
if [[ -n "$(git status --porcelain)" ]]; then
|
||||
echo >&2 "ERROR: Generated files are inconsistent. Run 'make generate' and 'make protobuf' locally and then commit the updated files."
|
||||
git >&2 status --porcelain
|
||||
|
51
CHANGELOG.md
51
CHANGELOG.md
@ -1,26 +1,65 @@
|
||||
## 1.3.0 (Unreleased)
|
||||
|
||||
UPGRADE NOTES:
|
||||
NEW FEATURES:
|
||||
|
||||
* Module variable type constraints now support an `optional()` modifier for object attribute types. Optional attributes may be omitted from the variable value, and will be replaced by a default value (or `null` if no default is specified). For example:
|
||||
* **Optional attributes for object type constraints:** When declaring an input variable whose type constraint includes an object type, you can now declare individual attributes as optional, and specify a default value to use if the caller doesn't set it. For example:
|
||||
|
||||
```terraform
|
||||
variable "with_optional_attribute" {
|
||||
type = object({
|
||||
a = string # a required attribute
|
||||
b = optional(string) # an optional attribute
|
||||
c = optional(number, 127) # an optional attribute with default value
|
||||
c = optional(number, 127) # an optional attribute with a default value
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Assigning `{ a = "foo" }` to this variable will result in the value `{ a = "foo", b = null, c = 127 }`.
|
||||
Assigning `{ a = "foo" }` to this variable will result in the value `{ a = "foo", b = null, c = 127 }`.
|
||||
|
||||
This functionality was introduced as an experiment in Terraform 0.14. This release removes the experimental `defaults` function. ([#31154](https://github.com/hashicorp/terraform/issues/31154))
|
||||
* Added functions: `startswith` and `endswith` allow you to check whether a given string has a specified prefix or suffix. ([#31220](https://github.com/hashicorp/terraform/issues/31220))
|
||||
|
||||
UPGRADE NOTES:
|
||||
|
||||
* `terraform show -json`: Output changes now include more detail about the unknown-ness of the planned value. Previously, a planned output would be marked as either fully known or partially unknown, with the `after_unknown` field having value `false` or `true` respectively. Now outputs correctly expose the full structure of unknownness for complex values, allowing consumers of the JSON output format to determine which values in a collection are known only after apply.
|
||||
* `terraform import`: The `-allow-missing-config` has been removed, and at least an empty configuration block must exist to import a resource.
|
||||
|
||||
Consumers of the JSON output format expecting on the `after_unknown` field to be only `false` or `true` should be updated to support [the change representation](https://www.terraform.io/internals/json-format#change-representation) described in the documentation, and as was already used for resource changes. ([#31235](https://github.com/hashicorp/terraform/issues/31235))
|
||||
|
||||
ENHANCEMENTS:
|
||||
|
||||
* config: Optional attributes for object type constraints, as described under new features above. ([#31154](https://github.com/hashicorp/terraform/issues/31154))
|
||||
* `terraform fmt` now accepts multiple target paths, allowing formatting of several individual files at once. [GH-28191]
|
||||
* When reporting an error message related to a function call, Terraform will now include contextual information about the signature of the function that was being called, as an aid to understanding why the call might have failed. ([#31299](https://github.com/hashicorp/terraform/issues/31299))
|
||||
* When reporting an error or warning message that isn't caused by values being unknown or marked as sensitive, Terraform will no longer mention any values having those characteristics in the contextual information presented alongside the error. Terraform will still return this information for the small subset of error messages that are specifically about unknown values or sensitive values being invalid in certain contexts. ([#31299](https://github.com/hashicorp/terraform/issues/31299))
|
||||
* The Terraform CLI now calls `PlanResourceChange` for compatible providers when destroying resource instances. ([#31179](https://github.com/hashicorp/terraform/issues/31179))
|
||||
* The AzureRM Backend now only supports MSAL (and Microsoft Graph) and no longer makes use of ADAL (and Azure Active Directory Graph) for authentication ([#31070](https://github.com/hashicorp/terraform/issues/31070))
|
||||
* The COS backend now supports global acceleration. ([#31425](https://github.com/hashicorp/terraform/issues/31425))
|
||||
* providercache: include host in provider installation error ([#31524](https://github.com/hashicorp/terraform/issues/31524))
|
||||
* refactoring: `moved` blocks can now be used to move resources to and from external modules [GH-31556]
|
||||
|
||||
BUG FIXES:
|
||||
|
||||
* Made `terraform output` CLI help documentation consistent with web-based documentation ([#29354](https://github.com/hashicorp/terraform/issues/29354))
|
||||
* config: Terraform was not previously evaluating preconditions and postconditions during the apply phase for resource instances that didn't have any changes pending, which was incorrect because the outcome of a condition can potentially be affected by changes to _other_ objects in the configuration. Terraform will now always check the conditions for every resource instance included in a plan during the apply phase, even for resource instances that have "no-op" changes. This means that some failures that would previously have been detected only by a subsequent run will now be detected during the same run that caused them, thereby giving the feedback at the appropriate time. ([#31491](https://github.com/hashicorp/terraform/issues/31491))
|
||||
* `terraform show -json`: Fixed missing unknown markers in the encoding of partially unknown tuples and sets. ([#31236](https://github.com/hashicorp/terraform/issues/31236))
|
||||
* `terraform output` CLI help documentation is now more consistent with web-based documentation. ([#29354](https://github.com/hashicorp/terraform/issues/29354))
|
||||
* getproviders: account for occasionally missing Host header in errors ([#31542](https://github.com/hashicorp/terraform/issues/31542))
|
||||
* core: Do not create "delete" changes for nonexistent outputs ([#31471](https://github.com/hashicorp/terraform/issues/31471))
|
||||
* configload: validate implied provider names in submodules to avoid crash ([#31573](https://github.com/hashicorp/terraform/issues/31573))
|
||||
* core: `import` fails when resources or modules are expanded with for each, or input from data sources is required [GH-31283]
|
||||
|
||||
EXPERIMENTS:
|
||||
|
||||
* This release concludes the `module_variable_optional_attrs` experiment, which started in Terraform v0.14.0. The final design of the optional attributes feature is similar to the experimental form in the previous releases, but with two major differences:
|
||||
* The `optional` function-like modifier for declaring an optional attribute now accepts an optional second argument for specifying a default value to use when the attribute isn't set by the caller. If not specified, the default value is a null value of the appropriate type as before.
|
||||
* The built-in `defaults` function, previously used to meet the use-case of replacing null values with default values, will not graduate to stable and has been removed. Use the second argument of `optional` inline in your type constraint to declare default values instead.
|
||||
|
||||
If you have any experimental modules that were participating in this experiment, you will need to remove the experiment opt-in and adopt the new syntax for declaring default values in order to migrate your existing module to the stablized version of this feature. If you are writing a shared module for others to use, we recommend declaring that your module requires Terraform v1.3.0 or later to give specific feedback when using the new feature on older Terraform versions, in place of the previous declaration to use the experimental form of this feature:
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
required_version = ">= 1.3.0"
|
||||
}
|
||||
```
|
||||
|
||||
## Previous Releases
|
||||
|
||||
|
5
Makefile
5
Makefile
@ -35,6 +35,9 @@ protobuf:
|
||||
fmtcheck:
|
||||
@sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'"
|
||||
|
||||
importscheck:
|
||||
@sh -c "'$(CURDIR)/scripts/goimportscheck.sh'"
|
||||
|
||||
staticcheck:
|
||||
@sh -c "'$(CURDIR)/scripts/staticcheck.sh'"
|
||||
|
||||
@ -63,4 +66,4 @@ website/build-local:
|
||||
# under parallel conditions.
|
||||
.NOTPARALLEL:
|
||||
|
||||
.PHONY: fmtcheck generate protobuf website website-test staticcheck website/local website/build-local
|
||||
.PHONY: fmtcheck importscheck generate protobuf website website-test staticcheck website/local website/build-local
|
||||
|
26
README.md
26
README.md
@ -1,5 +1,4 @@
|
||||
Terraform
|
||||
=========
|
||||
# Terraform
|
||||
|
||||
- Website: https://www.terraform.io
|
||||
- Forums: [HashiCorp Discuss](https://discuss.hashicorp.com/c/terraform-core)
|
||||
@ -15,32 +14,35 @@ The key features of Terraform are:
|
||||
|
||||
- **Infrastructure as Code**: Infrastructure is described using a high-level configuration syntax. This allows a blueprint of your datacenter to be versioned and treated as you would any other code. Additionally, infrastructure can be shared and re-used.
|
||||
|
||||
- **Execution Plans**: Terraform has a "planning" step where it generates an *execution plan*. The execution plan shows what Terraform will do when you call apply. This lets you avoid any surprises when Terraform manipulates infrastructure.
|
||||
- **Execution Plans**: Terraform has a "planning" step where it generates an execution plan. The execution plan shows what Terraform will do when you call apply. This lets you avoid any surprises when Terraform manipulates infrastructure.
|
||||
|
||||
- **Resource Graph**: Terraform builds a graph of all your resources, and parallelizes the creation and modification of any non-dependent resources. Because of this, Terraform builds infrastructure as efficiently as possible, and operators get insight into dependencies in their infrastructure.
|
||||
|
||||
- **Change Automation**: Complex changesets can be applied to your infrastructure with minimal human interaction. With the previously mentioned execution plan and resource graph, you know exactly what Terraform will change and in what order, avoiding many possible human errors.
|
||||
|
||||
For more information, see the [introduction section](https://www.terraform.io/intro) of the Terraform website.
|
||||
For more information, refer to the [What is Terraform?](https://www.terraform.io/intro) page on the Terraform website.
|
||||
|
||||
## Getting Started & Documentation
|
||||
|
||||
Getting Started & Documentation
|
||||
-------------------------------
|
||||
Documentation is available on the [Terraform website](https://www.terraform.io):
|
||||
- [Intro](https://www.terraform.io/intro/index.html)
|
||||
- [Docs](https://www.terraform.io/docs/index.html)
|
||||
|
||||
- [Introduction](https://www.terraform.io/intro)
|
||||
- [Documentation](https://www.terraform.io/docs)
|
||||
|
||||
If you're new to Terraform and want to get started creating infrastructure, please check out our [Getting Started guides](https://learn.hashicorp.com/terraform#getting-started) on HashiCorp's learning platform. There are also [additional guides](https://learn.hashicorp.com/terraform#operations-and-development) to continue your learning.
|
||||
|
||||
Show off your Terraform knowledge by passing a certification exam. Visit the [certification page](https://www.hashicorp.com/certification/) for information about exams and find [study materials](https://learn.hashicorp.com/terraform/certification/terraform-associate) on HashiCorp's learning platform.
|
||||
|
||||
Developing Terraform
|
||||
--------------------
|
||||
## Developing Terraform
|
||||
|
||||
This repository contains only Terraform core, which includes the command line interface and the main graph engine. Providers are implemented as plugins, and Terraform can automatically download providers that are published on [the Terraform Registry](https://registry.terraform.io). HashiCorp develops some providers, and others are developed by other organizations. For more information, see [Extending Terraform](https://www.terraform.io/docs/extend/index.html).
|
||||
|
||||
To learn more about compiling Terraform and contributing suggested changes, please refer to [the contributing guide](.github/CONTRIBUTING.md).
|
||||
- To learn more about compiling Terraform and contributing suggested changes, refer to [the contributing guide](.github/CONTRIBUTING.md).
|
||||
|
||||
To learn more about how we handle bug reports, please read the [bug triage guide](./BUGPROCESS.md).
|
||||
- To learn more about how we handle bug reports, refer to the [bug triage guide](./BUGPROCESS.md).
|
||||
|
||||
- To learn how to contribute to the Terraform documentation in this repository, refer to the [Terraform Documentation README](/website/README.md).
|
||||
|
||||
## License
|
||||
|
||||
[Mozilla Public License v2.0](https://github.com/hashicorp/terraform/blob/main/LICENSE)
|
||||
|
@ -100,6 +100,8 @@ func initCommands(
|
||||
ProviderSource: providerSrc,
|
||||
ProviderDevOverrides: providerDevOverrides,
|
||||
UnmanagedProviders: unmanagedProviders,
|
||||
|
||||
AllowExperimentalFeatures: ExperimentsAllowed(),
|
||||
}
|
||||
|
||||
// The command list is included in the terraform -help
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Terraform Core Resource Destruction Notes
|
||||
|
||||
This document intends to describe some of the details and complications
|
||||
involved in the destructions of resources. It covers the ordering defined for
|
||||
involved in the destruction of resources. It covers the ordering defined for
|
||||
related create and destroy operations, as well as changes to the lifecycle
|
||||
ordering imposed by `create_before_destroy`. It is not intended to enumerate
|
||||
all possible combinations of dependency ordering, only to outline the basics
|
||||
@ -356,6 +356,6 @@ Order of operations:
|
||||
1. `A` is destroyed
|
||||
|
||||
This also demonstrates why `create_before_destroy` cannot be overridden when
|
||||
it is inherited; changing the behaviour here isn't possible without removing
|
||||
it is inherited; changing the behavior here isn't possible without removing
|
||||
the initial reason for `create_before_destroy`; otherwise cycles are always
|
||||
introduced into the graph.
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 243 KiB After Width: | Height: | Size: 194 KiB |
@ -9,7 +9,7 @@ There are also some historical exceptions to this rule, which we hope to
|
||||
supplement with plan-and-apply-based equivalents over time.
|
||||
|
||||
This document describes the default planning behavior of Terraform in the
|
||||
absense of any special instructions, and also describes the three main
|
||||
absence of any special instructions, and also describes the three main
|
||||
design approaches we can choose from when modelling non-default behaviors that
|
||||
require additional information from outside of Terraform Core.
|
||||
|
||||
@ -148,7 +148,7 @@ main sub-categories:
|
||||
top-level block which refers to other resources using the typical address
|
||||
syntax.
|
||||
|
||||
The following is a non-exhastive list of existing examples of
|
||||
The following is a non-exhaustive list of existing examples of
|
||||
configuration-driven behaviors, selected to illustrate some different variations
|
||||
that might be useful inspiration for new designs:
|
||||
|
||||
@ -210,7 +210,7 @@ protocol does not talk about the action types explicitly, and instead only
|
||||
implies them via other content of the request and response, with Terraform Core
|
||||
making the final decision about how to react to that information.
|
||||
|
||||
The following is a non-exhastive list of existing examples of
|
||||
The following is a non-exhaustive list of existing examples of
|
||||
provider-driven behaviors, selected to illustrate some different variations
|
||||
that might be useful inspiration for new designs:
|
||||
|
||||
@ -258,7 +258,7 @@ most appropriate way to handle a particular use-case, because the need for the
|
||||
behavior originates in some process happening outside of the scope of any
|
||||
particular Terraform module or provider.
|
||||
|
||||
The following is a non-exhastive list of existing examples of
|
||||
The following is a non-exhaustive list of existing examples of
|
||||
single-run behaviors, selected to illustrate some different variations
|
||||
that might be useful inspiration for new designs:
|
||||
|
||||
|
381
docs/plugin-protocol/tfplugin5.3.proto
Normal file
381
docs/plugin-protocol/tfplugin5.3.proto
Normal file
@ -0,0 +1,381 @@
|
||||
// Terraform Plugin RPC protocol version 5.3
|
||||
//
|
||||
// This file defines version 5.3 of the RPC protocol. To implement a plugin
|
||||
// against this protocol, copy this definition into your own codebase and
|
||||
// use protoc to generate stubs for your target language.
|
||||
//
|
||||
// This file will not be updated. Any minor versions of protocol 5 to follow
|
||||
// should copy this file and modify the copy while maintaing backwards
|
||||
// compatibility. Breaking changes, if any are required, will come
|
||||
// in a subsequent major version with its own separate proto definition.
|
||||
//
|
||||
// Note that only the proto files included in a release tag of Terraform are
|
||||
// official protocol releases. Proto files taken from other commits may include
|
||||
// incomplete changes or features that did not make it into a final release.
|
||||
// In all reasonable cases, plugin developers should take the proto file from
|
||||
// the tag of the most recent release of Terraform, and not from the main
|
||||
// branch or any other development branch.
|
||||
//
|
||||
syntax = "proto3";
|
||||
option go_package = "github.com/hashicorp/terraform/internal/tfplugin5";
|
||||
|
||||
package tfplugin5;
|
||||
|
||||
// DynamicValue is an opaque encoding of terraform data, with the field name
|
||||
// indicating the encoding scheme used.
|
||||
message DynamicValue {
|
||||
bytes msgpack = 1;
|
||||
bytes json = 2;
|
||||
}
|
||||
|
||||
message Diagnostic {
|
||||
enum Severity {
|
||||
INVALID = 0;
|
||||
ERROR = 1;
|
||||
WARNING = 2;
|
||||
}
|
||||
Severity severity = 1;
|
||||
string summary = 2;
|
||||
string detail = 3;
|
||||
AttributePath attribute = 4;
|
||||
}
|
||||
|
||||
message AttributePath {
|
||||
message Step {
|
||||
oneof selector {
|
||||
// Set "attribute_name" to represent looking up an attribute
|
||||
// in the current object value.
|
||||
string attribute_name = 1;
|
||||
// Set "element_key_*" to represent looking up an element in
|
||||
// an indexable collection type.
|
||||
string element_key_string = 2;
|
||||
int64 element_key_int = 3;
|
||||
}
|
||||
}
|
||||
repeated Step steps = 1;
|
||||
}
|
||||
|
||||
message Stop {
|
||||
message Request {
|
||||
}
|
||||
message Response {
|
||||
string Error = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// RawState holds the stored state for a resource to be upgraded by the
|
||||
// provider. It can be in one of two formats, the current json encoded format
|
||||
// in bytes, or the legacy flatmap format as a map of strings.
|
||||
message RawState {
|
||||
bytes json = 1;
|
||||
map<string, string> flatmap = 2;
|
||||
}
|
||||
|
||||
enum StringKind {
|
||||
PLAIN = 0;
|
||||
MARKDOWN = 1;
|
||||
}
|
||||
|
||||
// Schema is the configuration schema for a Resource, Provider, or Provisioner.
|
||||
message Schema {
|
||||
message Block {
|
||||
int64 version = 1;
|
||||
repeated Attribute attributes = 2;
|
||||
repeated NestedBlock block_types = 3;
|
||||
string description = 4;
|
||||
StringKind description_kind = 5;
|
||||
bool deprecated = 6;
|
||||
}
|
||||
|
||||
message Attribute {
|
||||
string name = 1;
|
||||
bytes type = 2;
|
||||
string description = 3;
|
||||
bool required = 4;
|
||||
bool optional = 5;
|
||||
bool computed = 6;
|
||||
bool sensitive = 7;
|
||||
StringKind description_kind = 8;
|
||||
bool deprecated = 9;
|
||||
}
|
||||
|
||||
message NestedBlock {
|
||||
enum NestingMode {
|
||||
INVALID = 0;
|
||||
SINGLE = 1;
|
||||
LIST = 2;
|
||||
SET = 3;
|
||||
MAP = 4;
|
||||
GROUP = 5;
|
||||
}
|
||||
|
||||
string type_name = 1;
|
||||
Block block = 2;
|
||||
NestingMode nesting = 3;
|
||||
int64 min_items = 4;
|
||||
int64 max_items = 5;
|
||||
}
|
||||
|
||||
// The version of the schema.
|
||||
// Schemas are versioned, so that providers can upgrade a saved resource
|
||||
// state when the schema is changed.
|
||||
int64 version = 1;
|
||||
|
||||
// Block is the top level configuration block for this schema.
|
||||
Block block = 2;
|
||||
}
|
||||
|
||||
service Provider {
|
||||
//////// Information about what a provider supports/expects
|
||||
rpc GetSchema(GetProviderSchema.Request) returns (GetProviderSchema.Response);
|
||||
rpc PrepareProviderConfig(PrepareProviderConfig.Request) returns (PrepareProviderConfig.Response);
|
||||
rpc ValidateResourceTypeConfig(ValidateResourceTypeConfig.Request) returns (ValidateResourceTypeConfig.Response);
|
||||
rpc ValidateDataSourceConfig(ValidateDataSourceConfig.Request) returns (ValidateDataSourceConfig.Response);
|
||||
rpc UpgradeResourceState(UpgradeResourceState.Request) returns (UpgradeResourceState.Response);
|
||||
|
||||
//////// One-time initialization, called before other functions below
|
||||
rpc Configure(Configure.Request) returns (Configure.Response);
|
||||
|
||||
//////// Managed Resource Lifecycle
|
||||
rpc ReadResource(ReadResource.Request) returns (ReadResource.Response);
|
||||
rpc PlanResourceChange(PlanResourceChange.Request) returns (PlanResourceChange.Response);
|
||||
rpc ApplyResourceChange(ApplyResourceChange.Request) returns (ApplyResourceChange.Response);
|
||||
rpc ImportResourceState(ImportResourceState.Request) returns (ImportResourceState.Response);
|
||||
|
||||
rpc ReadDataSource(ReadDataSource.Request) returns (ReadDataSource.Response);
|
||||
|
||||
//////// Graceful Shutdown
|
||||
rpc Stop(Stop.Request) returns (Stop.Response);
|
||||
}
|
||||
|
||||
message GetProviderSchema {
|
||||
message Request {
|
||||
}
|
||||
message Response {
|
||||
Schema provider = 1;
|
||||
map<string, Schema> resource_schemas = 2;
|
||||
map<string, Schema> data_source_schemas = 3;
|
||||
repeated Diagnostic diagnostics = 4;
|
||||
Schema provider_meta = 5;
|
||||
ServerCapabilities server_capabilities = 6;
|
||||
}
|
||||
|
||||
|
||||
// ServerCapabilities allows providers to communicate extra information
|
||||
// regarding supported protocol features. This is used to indicate
|
||||
// availability of certain forward-compatible changes which may be optional
|
||||
// in a major protocol version, but cannot be tested for directly.
|
||||
message ServerCapabilities {
|
||||
// The plan_destroy capability signals that a provider expects a call
|
||||
// to PlanResourceChange when a resource is going to be destroyed.
|
||||
bool plan_destroy = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message PrepareProviderConfig {
|
||||
message Request {
|
||||
DynamicValue config = 1;
|
||||
}
|
||||
message Response {
|
||||
DynamicValue prepared_config = 1;
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message UpgradeResourceState {
|
||||
message Request {
|
||||
string type_name = 1;
|
||||
|
||||
// version is the schema_version number recorded in the state file
|
||||
int64 version = 2;
|
||||
|
||||
// raw_state is the raw states as stored for the resource. Core does
|
||||
// not have access to the schema of prior_version, so it's the
|
||||
// provider's responsibility to interpret this value using the
|
||||
// appropriate older schema. The raw_state will be the json encoded
|
||||
// state, or a legacy flat-mapped format.
|
||||
RawState raw_state = 3;
|
||||
}
|
||||
message Response {
|
||||
// new_state is a msgpack-encoded data structure that, when interpreted with
|
||||
// the _current_ schema for this resource type, is functionally equivalent to
|
||||
// that which was given in prior_state_raw.
|
||||
DynamicValue upgraded_state = 1;
|
||||
|
||||
// diagnostics describes any errors encountered during migration that could not
|
||||
// be safely resolved, and warnings about any possibly-risky assumptions made
|
||||
// in the upgrade process.
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message ValidateResourceTypeConfig {
|
||||
message Request {
|
||||
string type_name = 1;
|
||||
DynamicValue config = 2;
|
||||
}
|
||||
message Response {
|
||||
repeated Diagnostic diagnostics = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message ValidateDataSourceConfig {
|
||||
message Request {
|
||||
string type_name = 1;
|
||||
DynamicValue config = 2;
|
||||
}
|
||||
message Response {
|
||||
repeated Diagnostic diagnostics = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message Configure {
|
||||
message Request {
|
||||
string terraform_version = 1;
|
||||
DynamicValue config = 2;
|
||||
}
|
||||
message Response {
|
||||
repeated Diagnostic diagnostics = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message ReadResource {
|
||||
message Request {
|
||||
string type_name = 1;
|
||||
DynamicValue current_state = 2;
|
||||
bytes private = 3;
|
||||
DynamicValue provider_meta = 4;
|
||||
}
|
||||
message Response {
|
||||
DynamicValue new_state = 1;
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
bytes private = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message PlanResourceChange {
|
||||
message Request {
|
||||
string type_name = 1;
|
||||
DynamicValue prior_state = 2;
|
||||
DynamicValue proposed_new_state = 3;
|
||||
DynamicValue config = 4;
|
||||
bytes prior_private = 5;
|
||||
DynamicValue provider_meta = 6;
|
||||
}
|
||||
|
||||
message Response {
|
||||
DynamicValue planned_state = 1;
|
||||
repeated AttributePath requires_replace = 2;
|
||||
bytes planned_private = 3;
|
||||
repeated Diagnostic diagnostics = 4;
|
||||
|
||||
|
||||
// This may be set only by the helper/schema "SDK" in the main Terraform
|
||||
// repository, to request that Terraform Core >=0.12 permit additional
|
||||
// inconsistencies that can result from the legacy SDK type system
|
||||
// and its imprecise mapping to the >=0.12 type system.
|
||||
// The change in behavior implied by this flag makes sense only for the
|
||||
// specific details of the legacy SDK type system, and are not a general
|
||||
// mechanism to avoid proper type handling in providers.
|
||||
//
|
||||
// ==== DO NOT USE THIS ====
|
||||
// ==== THIS MUST BE LEFT UNSET IN ALL OTHER SDKS ====
|
||||
// ==== DO NOT USE THIS ====
|
||||
bool legacy_type_system = 5;
|
||||
}
|
||||
}
|
||||
|
||||
message ApplyResourceChange {
|
||||
message Request {
|
||||
string type_name = 1;
|
||||
DynamicValue prior_state = 2;
|
||||
DynamicValue planned_state = 3;
|
||||
DynamicValue config = 4;
|
||||
bytes planned_private = 5;
|
||||
DynamicValue provider_meta = 6;
|
||||
}
|
||||
message Response {
|
||||
DynamicValue new_state = 1;
|
||||
bytes private = 2;
|
||||
repeated Diagnostic diagnostics = 3;
|
||||
|
||||
// This may be set only by the helper/schema "SDK" in the main Terraform
|
||||
// repository, to request that Terraform Core >=0.12 permit additional
|
||||
// inconsistencies that can result from the legacy SDK type system
|
||||
// and its imprecise mapping to the >=0.12 type system.
|
||||
// The change in behavior implied by this flag makes sense only for the
|
||||
// specific details of the legacy SDK type system, and are not a general
|
||||
// mechanism to avoid proper type handling in providers.
|
||||
//
|
||||
// ==== DO NOT USE THIS ====
|
||||
// ==== THIS MUST BE LEFT UNSET IN ALL OTHER SDKS ====
|
||||
// ==== DO NOT USE THIS ====
|
||||
bool legacy_type_system = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message ImportResourceState {
|
||||
message Request {
|
||||
string type_name = 1;
|
||||
string id = 2;
|
||||
}
|
||||
|
||||
message ImportedResource {
|
||||
string type_name = 1;
|
||||
DynamicValue state = 2;
|
||||
bytes private = 3;
|
||||
}
|
||||
|
||||
message Response {
|
||||
repeated ImportedResource imported_resources = 1;
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message ReadDataSource {
|
||||
message Request {
|
||||
string type_name = 1;
|
||||
DynamicValue config = 2;
|
||||
DynamicValue provider_meta = 3;
|
||||
}
|
||||
message Response {
|
||||
DynamicValue state = 1;
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
}
|
||||
}
|
||||
|
||||
service Provisioner {
|
||||
rpc GetSchema(GetProvisionerSchema.Request) returns (GetProvisionerSchema.Response);
|
||||
rpc ValidateProvisionerConfig(ValidateProvisionerConfig.Request) returns (ValidateProvisionerConfig.Response);
|
||||
rpc ProvisionResource(ProvisionResource.Request) returns (stream ProvisionResource.Response);
|
||||
rpc Stop(Stop.Request) returns (Stop.Response);
|
||||
}
|
||||
|
||||
message GetProvisionerSchema {
|
||||
message Request {
|
||||
}
|
||||
message Response {
|
||||
Schema provisioner = 1;
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message ValidateProvisionerConfig {
|
||||
message Request {
|
||||
DynamicValue config = 1;
|
||||
}
|
||||
message Response {
|
||||
repeated Diagnostic diagnostics = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message ProvisionResource {
|
||||
message Request {
|
||||
DynamicValue config = 1;
|
||||
DynamicValue connection = 2;
|
||||
}
|
||||
message Response {
|
||||
string output = 1;
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
}
|
||||
}
|
362
docs/plugin-protocol/tfplugin6.3.proto
Normal file
362
docs/plugin-protocol/tfplugin6.3.proto
Normal file
@ -0,0 +1,362 @@
|
||||
// Terraform Plugin RPC protocol version 6.3
|
||||
//
|
||||
// This file defines version 6.3 of the RPC protocol. To implement a plugin
|
||||
// against this protocol, copy this definition into your own codebase and
|
||||
// use protoc to generate stubs for your target language.
|
||||
//
|
||||
// This file will not be updated. Any minor versions of protocol 6 to follow
|
||||
// should copy this file and modify the copy while maintaing backwards
|
||||
// compatibility. Breaking changes, if any are required, will come
|
||||
// in a subsequent major version with its own separate proto definition.
|
||||
//
|
||||
// Note that only the proto files included in a release tag of Terraform are
|
||||
// official protocol releases. Proto files taken from other commits may include
|
||||
// incomplete changes or features that did not make it into a final release.
|
||||
// In all reasonable cases, plugin developers should take the proto file from
|
||||
// the tag of the most recent release of Terraform, and not from the main
|
||||
// branch or any other development branch.
|
||||
//
|
||||
syntax = "proto3";
|
||||
option go_package = "github.com/hashicorp/terraform/internal/tfplugin6";
|
||||
|
||||
package tfplugin6;
|
||||
|
||||
// DynamicValue is an opaque encoding of terraform data, with the field name
|
||||
// indicating the encoding scheme used.
|
||||
message DynamicValue {
|
||||
bytes msgpack = 1;
|
||||
bytes json = 2;
|
||||
}
|
||||
|
||||
message Diagnostic {
|
||||
enum Severity {
|
||||
INVALID = 0;
|
||||
ERROR = 1;
|
||||
WARNING = 2;
|
||||
}
|
||||
Severity severity = 1;
|
||||
string summary = 2;
|
||||
string detail = 3;
|
||||
AttributePath attribute = 4;
|
||||
}
|
||||
|
||||
message AttributePath {
|
||||
message Step {
|
||||
oneof selector {
|
||||
// Set "attribute_name" to represent looking up an attribute
|
||||
// in the current object value.
|
||||
string attribute_name = 1;
|
||||
// Set "element_key_*" to represent looking up an element in
|
||||
// an indexable collection type.
|
||||
string element_key_string = 2;
|
||||
int64 element_key_int = 3;
|
||||
}
|
||||
}
|
||||
repeated Step steps = 1;
|
||||
}
|
||||
|
||||
message StopProvider {
|
||||
message Request {
|
||||
}
|
||||
message Response {
|
||||
string Error = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// RawState holds the stored state for a resource to be upgraded by the
|
||||
// provider. It can be in one of two formats, the current json encoded format
|
||||
// in bytes, or the legacy flatmap format as a map of strings.
|
||||
message RawState {
|
||||
bytes json = 1;
|
||||
map<string, string> flatmap = 2;
|
||||
}
|
||||
|
||||
enum StringKind {
|
||||
PLAIN = 0;
|
||||
MARKDOWN = 1;
|
||||
}
|
||||
|
||||
// Schema is the configuration schema for a Resource or Provider.
|
||||
message Schema {
|
||||
message Block {
|
||||
int64 version = 1;
|
||||
repeated Attribute attributes = 2;
|
||||
repeated NestedBlock block_types = 3;
|
||||
string description = 4;
|
||||
StringKind description_kind = 5;
|
||||
bool deprecated = 6;
|
||||
}
|
||||
|
||||
message Attribute {
|
||||
string name = 1;
|
||||
bytes type = 2;
|
||||
Object nested_type = 10;
|
||||
string description = 3;
|
||||
bool required = 4;
|
||||
bool optional = 5;
|
||||
bool computed = 6;
|
||||
bool sensitive = 7;
|
||||
StringKind description_kind = 8;
|
||||
bool deprecated = 9;
|
||||
}
|
||||
|
||||
message NestedBlock {
|
||||
enum NestingMode {
|
||||
INVALID = 0;
|
||||
SINGLE = 1;
|
||||
LIST = 2;
|
||||
SET = 3;
|
||||
MAP = 4;
|
||||
GROUP = 5;
|
||||
}
|
||||
|
||||
string type_name = 1;
|
||||
Block block = 2;
|
||||
NestingMode nesting = 3;
|
||||
int64 min_items = 4;
|
||||
int64 max_items = 5;
|
||||
}
|
||||
|
||||
message Object {
|
||||
enum NestingMode {
|
||||
INVALID = 0;
|
||||
SINGLE = 1;
|
||||
LIST = 2;
|
||||
SET = 3;
|
||||
MAP = 4;
|
||||
}
|
||||
|
||||
repeated Attribute attributes = 1;
|
||||
NestingMode nesting = 3;
|
||||
|
||||
// MinItems and MaxItems were never used in the protocol, and have no
|
||||
// effect on validation.
|
||||
int64 min_items = 4 [deprecated = true];
|
||||
int64 max_items = 5 [deprecated = true];
|
||||
}
|
||||
|
||||
// The version of the schema.
|
||||
// Schemas are versioned, so that providers can upgrade a saved resource
|
||||
// state when the schema is changed.
|
||||
int64 version = 1;
|
||||
|
||||
// Block is the top level configuration block for this schema.
|
||||
Block block = 2;
|
||||
}
|
||||
|
||||
service Provider {
|
||||
//////// Information about what a provider supports/expects
|
||||
rpc GetProviderSchema(GetProviderSchema.Request) returns (GetProviderSchema.Response);
|
||||
rpc ValidateProviderConfig(ValidateProviderConfig.Request) returns (ValidateProviderConfig.Response);
|
||||
rpc ValidateResourceConfig(ValidateResourceConfig.Request) returns (ValidateResourceConfig.Response);
|
||||
rpc ValidateDataResourceConfig(ValidateDataResourceConfig.Request) returns (ValidateDataResourceConfig.Response);
|
||||
rpc UpgradeResourceState(UpgradeResourceState.Request) returns (UpgradeResourceState.Response);
|
||||
|
||||
//////// One-time initialization, called before other functions below
|
||||
rpc ConfigureProvider(ConfigureProvider.Request) returns (ConfigureProvider.Response);
|
||||
|
||||
//////// Managed Resource Lifecycle
|
||||
rpc ReadResource(ReadResource.Request) returns (ReadResource.Response);
|
||||
rpc PlanResourceChange(PlanResourceChange.Request) returns (PlanResourceChange.Response);
|
||||
rpc ApplyResourceChange(ApplyResourceChange.Request) returns (ApplyResourceChange.Response);
|
||||
rpc ImportResourceState(ImportResourceState.Request) returns (ImportResourceState.Response);
|
||||
|
||||
rpc ReadDataSource(ReadDataSource.Request) returns (ReadDataSource.Response);
|
||||
|
||||
//////// Graceful Shutdown
|
||||
rpc StopProvider(StopProvider.Request) returns (StopProvider.Response);
|
||||
}
|
||||
|
||||
message GetProviderSchema {
|
||||
message Request {
|
||||
}
|
||||
message Response {
|
||||
Schema provider = 1;
|
||||
map<string, Schema> resource_schemas = 2;
|
||||
map<string, Schema> data_source_schemas = 3;
|
||||
repeated Diagnostic diagnostics = 4;
|
||||
Schema provider_meta = 5;
|
||||
ServerCapabilities server_capabilities = 6;
|
||||
}
|
||||
|
||||
|
||||
// ServerCapabilities allows providers to communicate extra information
|
||||
// regarding supported protocol features. This is used to indicate
|
||||
// availability of certain forward-compatible changes which may be optional
|
||||
// in a major protocol version, but cannot be tested for directly.
|
||||
message ServerCapabilities {
|
||||
// The plan_destroy capability signals that a provider expects a call
|
||||
// to PlanResourceChange when a resource is going to be destroyed.
|
||||
bool plan_destroy = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message ValidateProviderConfig {
|
||||
message Request {
|
||||
DynamicValue config = 1;
|
||||
}
|
||||
message Response {
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message UpgradeResourceState {
|
||||
message Request {
|
||||
string type_name = 1;
|
||||
|
||||
// version is the schema_version number recorded in the state file
|
||||
int64 version = 2;
|
||||
|
||||
// raw_state is the raw states as stored for the resource. Core does
|
||||
// not have access to the schema of prior_version, so it's the
|
||||
// provider's responsibility to interpret this value using the
|
||||
// appropriate older schema. The raw_state will be the json encoded
|
||||
// state, or a legacy flat-mapped format.
|
||||
RawState raw_state = 3;
|
||||
}
|
||||
message Response {
|
||||
// new_state is a msgpack-encoded data structure that, when interpreted with
|
||||
// the _current_ schema for this resource type, is functionally equivalent to
|
||||
// that which was given in prior_state_raw.
|
||||
DynamicValue upgraded_state = 1;
|
||||
|
||||
// diagnostics describes any errors encountered during migration that could not
|
||||
// be safely resolved, and warnings about any possibly-risky assumptions made
|
||||
// in the upgrade process.
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message ValidateResourceConfig {
|
||||
message Request {
|
||||
string type_name = 1;
|
||||
DynamicValue config = 2;
|
||||
}
|
||||
message Response {
|
||||
repeated Diagnostic diagnostics = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message ValidateDataResourceConfig {
|
||||
message Request {
|
||||
string type_name = 1;
|
||||
DynamicValue config = 2;
|
||||
}
|
||||
message Response {
|
||||
repeated Diagnostic diagnostics = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message ConfigureProvider {
|
||||
message Request {
|
||||
string terraform_version = 1;
|
||||
DynamicValue config = 2;
|
||||
}
|
||||
message Response {
|
||||
repeated Diagnostic diagnostics = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message ReadResource {
|
||||
message Request {
|
||||
string type_name = 1;
|
||||
DynamicValue current_state = 2;
|
||||
bytes private = 3;
|
||||
DynamicValue provider_meta = 4;
|
||||
}
|
||||
message Response {
|
||||
DynamicValue new_state = 1;
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
bytes private = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message PlanResourceChange {
|
||||
message Request {
|
||||
string type_name = 1;
|
||||
DynamicValue prior_state = 2;
|
||||
DynamicValue proposed_new_state = 3;
|
||||
DynamicValue config = 4;
|
||||
bytes prior_private = 5;
|
||||
DynamicValue provider_meta = 6;
|
||||
}
|
||||
|
||||
message Response {
|
||||
DynamicValue planned_state = 1;
|
||||
repeated AttributePath requires_replace = 2;
|
||||
bytes planned_private = 3;
|
||||
repeated Diagnostic diagnostics = 4;
|
||||
|
||||
// This may be set only by the helper/schema "SDK" in the main Terraform
|
||||
// repository, to request that Terraform Core >=0.12 permit additional
|
||||
// inconsistencies that can result from the legacy SDK type system
|
||||
// and its imprecise mapping to the >=0.12 type system.
|
||||
// The change in behavior implied by this flag makes sense only for the
|
||||
// specific details of the legacy SDK type system, and are not a general
|
||||
// mechanism to avoid proper type handling in providers.
|
||||
//
|
||||
// ==== DO NOT USE THIS ====
|
||||
// ==== THIS MUST BE LEFT UNSET IN ALL OTHER SDKS ====
|
||||
// ==== DO NOT USE THIS ====
|
||||
bool legacy_type_system = 5;
|
||||
}
|
||||
}
|
||||
|
||||
message ApplyResourceChange {
|
||||
message Request {
|
||||
string type_name = 1;
|
||||
DynamicValue prior_state = 2;
|
||||
DynamicValue planned_state = 3;
|
||||
DynamicValue config = 4;
|
||||
bytes planned_private = 5;
|
||||
DynamicValue provider_meta = 6;
|
||||
}
|
||||
message Response {
|
||||
DynamicValue new_state = 1;
|
||||
bytes private = 2;
|
||||
repeated Diagnostic diagnostics = 3;
|
||||
|
||||
// This may be set only by the helper/schema "SDK" in the main Terraform
|
||||
// repository, to request that Terraform Core >=0.12 permit additional
|
||||
// inconsistencies that can result from the legacy SDK type system
|
||||
// and its imprecise mapping to the >=0.12 type system.
|
||||
// The change in behavior implied by this flag makes sense only for the
|
||||
// specific details of the legacy SDK type system, and are not a general
|
||||
// mechanism to avoid proper type handling in providers.
|
||||
//
|
||||
// ==== DO NOT USE THIS ====
|
||||
// ==== THIS MUST BE LEFT UNSET IN ALL OTHER SDKS ====
|
||||
// ==== DO NOT USE THIS ====
|
||||
bool legacy_type_system = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message ImportResourceState {
|
||||
message Request {
|
||||
string type_name = 1;
|
||||
string id = 2;
|
||||
}
|
||||
|
||||
message ImportedResource {
|
||||
string type_name = 1;
|
||||
DynamicValue state = 2;
|
||||
bytes private = 3;
|
||||
}
|
||||
|
||||
message Response {
|
||||
repeated ImportedResource imported_resources = 1;
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message ReadDataSource {
|
||||
message Request {
|
||||
string type_name = 1;
|
||||
DynamicValue config = 2;
|
||||
DynamicValue provider_meta = 3;
|
||||
}
|
||||
message Response {
|
||||
DynamicValue state = 1;
|
||||
repeated Diagnostic diagnostics = 2;
|
||||
}
|
||||
}
|
@ -3,45 +3,94 @@
|
||||
This document describes the relationships between the different operations
|
||||
called on a Terraform Provider to handle a change to a resource instance.
|
||||
|
||||

|
||||

|
||||
|
||||
The process includes several different artifacts that are all objects
|
||||
conforming to the schema of the resource type in question, representing
|
||||
different subsets of the instance for different purposes:
|
||||
The resource instance operations all both consume and produce objects that
|
||||
conform to the schema of the selected resource type.
|
||||
|
||||
* **Configuration**: Contains only values from the configuration, including
|
||||
unknown values in any case where the argument value is derived from an
|
||||
unknown result on another resource. Any attributes not set directly in the
|
||||
configuration are null.
|
||||
The overall goal of this process is to take a **Configuration** and a
|
||||
**Previous Run State**, merge them together using resource-type-specific
|
||||
planning logic to produce a **Planned State**, and then change the remote
|
||||
system to match that planned state before finally producing the **New State**
|
||||
that will be saved in order to become the **Previous Run State** for the next
|
||||
operation.
|
||||
|
||||
* **Prior State**: The full object produced by a previous apply operation, or
|
||||
null if the instance is being created for the first time.
|
||||
The various object values used in different parts of this process are:
|
||||
|
||||
* **Proposed New State**: Terraform Core merges the non-null values from
|
||||
the configuration with any computed attribute results in the prior state
|
||||
to produce a combined object that includes both, to avoid each provider
|
||||
having to re-implement that merging logic. Will be null when planning a
|
||||
delete operation.
|
||||
* **Configuration**: Represents the values the user wrote in the configuration,
|
||||
after any automatic type conversions to match the resource type schema.
|
||||
|
||||
* **Planned New State**: An approximation of the result the provider expects
|
||||
to produce when applying the requested change. This is usually derived from
|
||||
the proposed new state by inserting default attribute values in place of
|
||||
null values and overriding any computed attribute values that are expected
|
||||
to change as a result of the apply operation. May include unknown values
|
||||
for attributes whose results cannot be predicted until apply. Will be null
|
||||
when planning a delete operation.
|
||||
Any attributes not defined by the user appear as null in the configuration
|
||||
object. If an argument value is derived from an unknown result of another
|
||||
resource instance, its value in the configuration object could also be
|
||||
unknown.
|
||||
|
||||
* **New State**: The actual result of applying the change, with any unknown
|
||||
values from the planned new state replaced with final result values. This
|
||||
value will be used as the input to plan the next operation.
|
||||
* **Prior State**: The provider's representation of the current state of the
|
||||
remote object at the time of the most recent read.
|
||||
|
||||
The remaining sections describe the three provider API functions that are
|
||||
* **Proposed New State**: Terraform Core uses some built-in logic to perform
|
||||
an initial basic merger of the **Configuration** and the **Prior State**
|
||||
which a provider may use as a starting point for its planning operation.
|
||||
|
||||
The built-in logic primarily deals with the expected behavior for attributes
|
||||
marked in the schema as both "optional" _and_ "computed", which means that
|
||||
the user may either set it or may leave it unset to allow the provider
|
||||
to choose a value instead.
|
||||
|
||||
Terraform Core therefore constructs the proposed new state by taking the
|
||||
attribute value from Configuration if it is non-null, and then using the
|
||||
Prior State as a fallback otherwise, thereby helping a provider to
|
||||
preserve its previously-chosen value for the attribute where appropriate.
|
||||
|
||||
* **Initial Planned State** and **Final Planned State** are both descriptions
|
||||
of what the associated remote object ought to look like after completing
|
||||
the planned action.
|
||||
|
||||
There will often be parts of the object that the provider isn't yet able to
|
||||
predict, either because they will be decided by the remote system during
|
||||
the apply step or because they are derived from configuration values from
|
||||
other resource instances that are themselves not yet known. The provider
|
||||
must mark these by including unknown values in the state objects.
|
||||
|
||||
The distinction between the _Initial_ and _Final_ planned states is that
|
||||
the initial one is created during Terraform Core's planning phase based
|
||||
on a possibly-incomplete configuration, whereas the final one is created
|
||||
during the apply step once all of the dependencies have already been
|
||||
updated and so the configuration should then be wholly known.
|
||||
|
||||
* **New State** is a representation of the result of whatever modifications
|
||||
were made to the remote system by the provider during the apply step.
|
||||
|
||||
The new state must always be wholly known, because it represents the
|
||||
actual state of the system, rather than a hypothetical future state.
|
||||
|
||||
* **Previous Run State** is the same object as the **New State** from
|
||||
the previous run of Terraform. This is exactly what the provider most
|
||||
recently returned, and so it will not take into account any changes that
|
||||
may have been made outside of Terraform in the meantime, and it may conform
|
||||
to an earlier version of the resource type schema and therefore be
|
||||
incompatible with the _current_ schema.
|
||||
|
||||
* **Upgraded State** is derived from **Previous Run State** by using some
|
||||
provider-specified logic to upgrade the existing data to the latest schema.
|
||||
However, it still represents the remote system as it was at the end of the
|
||||
last run, and so still doesn't take into account any changes that may have
|
||||
been made outside of Terraform.
|
||||
|
||||
* The **Import ID** and **Import Stub State** are both details of the special
|
||||
process of importing pre-existing objects into a Terraform state, and so
|
||||
we'll wait to discuss those in a later section on importing.
|
||||
|
||||
|
||||
## Provider Protocol API Functions
|
||||
|
||||
The following sections describe the three provider API functions that are
|
||||
called to plan and apply a change, including the expectations Terraform Core
|
||||
enforces for each.
|
||||
|
||||
For historical reasons, the original Terraform SDK is exempt from error
|
||||
messages produced when the assumptions are violated, but violating them will
|
||||
often cause downstream errors nonetheless, because Terraform's workflow
|
||||
messages produced when certain assumptions are violated, but violating them
|
||||
will often cause downstream errors nonetheless, because Terraform's workflow
|
||||
depends on these contracts being met.
|
||||
|
||||
The following section uses the word "attribute" to refer to the named
|
||||
@ -49,47 +98,46 @@ attributes described in the resource type schema. A schema may also include
|
||||
nested blocks, which contain their _own_ set of attributes; the constraints
|
||||
apply recursively to these nested attributes too.
|
||||
|
||||
Nested blocks are a configuration-only construct and so the number of blocks
|
||||
cannot be changed on the fly during planning or during apply: each block
|
||||
represented in the configuration must have a corresponding nested object in
|
||||
the planned new state and new state, or an error will be returned.
|
||||
The following are the function names used in provider protocol version 6.
|
||||
Protocol version 5 has the same set of operations but uses some
|
||||
marginally-different names for them, because we used protocol version 6 as an
|
||||
opportunity to tidy up some names that had been awkward before.
|
||||
|
||||
If a provider wishes to report about new instances of the sub-object type
|
||||
represented by nested blocks that are created implicitly during the apply
|
||||
operation -- for example, if a compute instance gets a default network
|
||||
interface created when none are explicitly specified -- this must be done via
|
||||
separate `Computed` attributes alongside the nested blocks, which could for
|
||||
example be a list or map of objects that includes a mixture of the objects
|
||||
described by the nested blocks in the configuration and any additional objects
|
||||
created by the remote system.
|
||||
### ValidateResourceConfig
|
||||
|
||||
## ValidateResourceTypeConfig
|
||||
`ValidateResourceConfig` takes the **Configuration** object alone, and
|
||||
may return error or warning diagnostics in response to its attribute values.
|
||||
|
||||
`ValidateResourceTypeConfig` is the provider's opportunity to perform any
|
||||
custom validation of the configuration that cannot be represented in the schema
|
||||
alone.
|
||||
`ValidateResourceConfig` is the provider's opportunity to apply custom
|
||||
validation rules to the schema, allowing for constraints that could not be
|
||||
expressed via schema alone.
|
||||
|
||||
In principle the provider can require any constraint it sees fit here, though
|
||||
in practice it should avoid reporting errors when values are unknown (so that
|
||||
the operation can proceed and determine those values downstream) and if
|
||||
it intends to apply default values during `PlanResourceChange` then it must
|
||||
tolerate those attributes being null at validation time, because validation
|
||||
happens before planning.
|
||||
In principle a provider can make any rule it wants here, although in practice
|
||||
providers should typically avoid reporting errors for values that are unknown.
|
||||
Terraform Core will call this function multiple times at different phases
|
||||
of evaluation, and guarantees to _eventually_ call with a wholly-known
|
||||
configuration so that the provider will have an opportunity to belatedly catch
|
||||
problems related to values that are initially unknown during planning.
|
||||
|
||||
A provider should repeat similar validation logic at the start of
|
||||
`PlanResourceChange`, in order to catch any new
|
||||
values that have switched from unknown to known along the way during the
|
||||
overall plan/apply flow.
|
||||
If a provider intends to choose a default value for a particular
|
||||
optional+computed attribute when left as null in the configuration, the
|
||||
provider _must_ tolerate that attribute being unknown in the configuration in
|
||||
order to get an opportunity to choose the default value during the later
|
||||
plan or apply phase.
|
||||
|
||||
## PlanResourceChange
|
||||
The validation step does not produce a new object itself and so it cannot
|
||||
modify the user's supplied configuration.
|
||||
|
||||
### PlanResourceChange
|
||||
|
||||
The purpose of `PlanResourceChange` is to predict the approximate effect of
|
||||
a subsequent apply operation, allowing Terraform to render the plan for the
|
||||
user and to propagate any predictable results downstream through expressions
|
||||
in the configuration.
|
||||
user and to propagate the predictable subset of results downstream through
|
||||
expressions in the configuration.
|
||||
|
||||
The _planned new state_ returned from the provider must meet the following
|
||||
constraints:
|
||||
This operation can base its decision on any combination of **Configuration**,
|
||||
**Prior State**, and **Proposed New State**, as long as its result fits the
|
||||
following constraints:
|
||||
|
||||
* Any attribute that was non-null in the configuration must either preserve
|
||||
the exact configuration value or return the corresponding attribute value
|
||||
@ -107,44 +155,216 @@ constraints:
|
||||
changed. Set an attribute to an unknown value to indicate that its final
|
||||
result will be determined during `ApplyResourceChange`.
|
||||
|
||||
`PlanResourceChange` is actually called twice for each resource type.
|
||||
It will be called first during the planning phase before Terraform prints out
|
||||
the diff to the user for confirmation. If the user accepts the plan, then
|
||||
`PlanResourceChange` will be called _again_ during the apply phase with any
|
||||
unknown values from configuration filled in with their final results from
|
||||
upstream resources. The second planned new state is compared with the first
|
||||
and must meet the following additional constraints along with those listed
|
||||
above:
|
||||
`PlanResourceChange` is actually called twice per run for each resource type.
|
||||
|
||||
* Any attribute that had a known value in the first planned new state must
|
||||
have an identical value in the second.
|
||||
The first call is during the planning phase, before Terraform prints out a
|
||||
diff to the user for confirmation. Because no changes at all have been applied
|
||||
at that point, the given **Configuration** may contain unknown values as
|
||||
placeholders for the results of expressions that derive from unknown values
|
||||
of other resource instances. The result of this initial call is the
|
||||
**Initial Planned State**.
|
||||
|
||||
* Any attribute that had an unknown value in the first planned new state may
|
||||
either remain unknown in the second or take on any known value of the
|
||||
expected type.
|
||||
If the user accepts the plan, Terraform will call `PlanResourceChange` a
|
||||
second time during the apply step, and that call is guaranteed to have a
|
||||
wholly-known **Configuration** with any values from upstream dependencies
|
||||
taken into account already. The result of this second call is the
|
||||
**Final Planned State**.
|
||||
|
||||
It is the second planned new state that is finally provided to
|
||||
`ApplyResourceChange`, as described in the following section.
|
||||
Terraform Core compares the final with the initial planned state, enforcing
|
||||
the following additional constraints along with those listed above:
|
||||
|
||||
## ApplyResourceChange
|
||||
* Any attribute that had a known value in the **Initial Planned State** must
|
||||
have an identical value in the **Final Planned State**.
|
||||
|
||||
* Any attribute that had an unknown value in the **Initial Planned State** may
|
||||
either remain unknown in the second _or_ take on any known value that
|
||||
conforms to the unknown value's type constraint.
|
||||
|
||||
The **Final Planned State** is what passes to `ApplyResourceChange`, as
|
||||
described in the following section.
|
||||
|
||||
### ApplyResourceChange
|
||||
|
||||
The `ApplyResourceChange` function is responsible for making calls into the
|
||||
remote system to make remote objects match the planned new state. During that
|
||||
operation, it should determine final values for any attributes that were left
|
||||
unknown in the planned new state, thus producing a wholly-known _new state_
|
||||
object.
|
||||
remote system to make remote objects match the **Final Planned State**. During
|
||||
that operation, the provider should decide on final values for any attributes
|
||||
that were left unknown in the **Final Planned State**, and thus produce the
|
||||
**New State** object.
|
||||
|
||||
`ApplyResourceChange` also receives the prior state so that it can use it
|
||||
`ApplyResourceChange` also receives the **Prior State** so that it can use it
|
||||
to potentially implement more "surgical" changes to particular parts of
|
||||
the remote objects by detecting portions that are unchanged, in cases where the
|
||||
remote API supports partial-update operations.
|
||||
|
||||
The new state object returned from the provider must meet the following
|
||||
The **New State** object returned from the provider must meet the following
|
||||
constraints:
|
||||
|
||||
* Any attribute that had a known value in the planned new state must have an
|
||||
identical value in the new state.
|
||||
* Any attribute that had a known value in the **Final Planned State** must have
|
||||
an identical value in the new state. In particular, if the remote API
|
||||
returned a different serialization of the same value then the provider must
|
||||
preserve the form the user wrote in the configuration, and _must not_ return
|
||||
the normalized form produced by the provider.
|
||||
|
||||
* Any attribute that had an unknown value in the planned new state must take
|
||||
on a known value of the expected type in the new state. No unknown values
|
||||
are allowed in the new state.
|
||||
* Any attribute that had an unknown value in the **Final Planned State** must
|
||||
take on a known value whose type conforms to the type constraint of the
|
||||
unknown value. No unknown values are permitted in the **New State**.
|
||||
|
||||
After calling `ApplyResourceChange` for each resource instance in the plan,
|
||||
and dealing with any other bookkeeping to return the results to the user,
|
||||
a single Terraform run is complete. Terraform Core saves the **New State**
|
||||
in a state snapshot for the entire configuration, so it'll be preserved for
|
||||
use on the next run.
|
||||
|
||||
When the user subsequently runs Terraform again, the **New State** becomes
|
||||
the **Previous Run State** verbatim, and passes into `UpgradeResourceState`.
|
||||
|
||||
### UpgradeResourceState
|
||||
|
||||
Because the state values for a particular resource instance persist in a
|
||||
saved state snapshot from one run to the next, Terraform Core must deal with
|
||||
the possibility that the user has upgraded to a newer version of the provider
|
||||
since the last run, and that the new provider version has an incompatible
|
||||
schema for the relevant resource type.
|
||||
|
||||
Terraform Core therefore begins by calling `UpgradeResourceState` and passing
|
||||
the **Previous Run State** in a _raw_ form, which in current protocol versions
|
||||
is the raw JSON data structure as was stored in the state snapshot. Terraform
|
||||
Core doesn't have access to the previous schema versions for a provider's
|
||||
resource types, so the provider itself must handle the data decoding in this
|
||||
upgrade function.
|
||||
|
||||
The provider can then use whatever logic is appropriate to update the shape
|
||||
of the data to conform to the current schema for the resource type. Although
|
||||
Terraform Core has no way to enforce it, a provider should only change the
|
||||
shape of the data structure and should _not_ change the meaning of the data.
|
||||
In particular, it should not try to update the state data to capture any
|
||||
changes made to the corresponding remote object outside of Terraform.
|
||||
|
||||
This function then returns the **Upgraded State**, which captures the same
|
||||
information as the **Previous Run State** but does so in a way that conforms
|
||||
to the current version of the resource type schema, which therefore allows
|
||||
Terraform Core to interact with the data fully for subsequent steps.
|
||||
|
||||
### ReadResource
|
||||
|
||||
Although Terraform typically expects to have exclusive control over any remote
|
||||
object that is bound to a resource instance, in practice users may make changes
|
||||
to those objects outside of Terraform, causing Terraform's records of the
|
||||
object to become stale.
|
||||
|
||||
The `ReadResource` function asks the provider to make a best effort to detect
|
||||
any such external changes and describe them so that Terraform Core can use
|
||||
an up-to-date **Prior State** as the input to the next `PlanResourceChange`
|
||||
call.
|
||||
|
||||
This is always a best effort operation because there are various reasons why
|
||||
a provider might not be able to detect certain changes. For example:
|
||||
* Some remote objects have write-only attributes, which means that there is
|
||||
no way to determine what value is currently stored in the remote system.
|
||||
* There may be new features of the underlying API which the current provider
|
||||
version doesn't know how to ask about.
|
||||
|
||||
Terraform Core expects a provider to carefully distinguish between the
|
||||
following two situations for each attribute:
|
||||
* **Normalization**: the remote API has returned some data in a different form
|
||||
than was recorded in the **Previous Run State**, but the meaning is unchanged.
|
||||
|
||||
In this case, the provider should return the exact value from the
|
||||
**Previous Run State**, thereby preserving the value as it was written by
|
||||
the user in the configuration and thus avoiding unwanted cascading changes to
|
||||
elsewhere in the configuration.
|
||||
* **Drift**: the remote API returned data that is materially different from
|
||||
what was recorded in the **Previous Run State**, meaning that the remote
|
||||
system's behavior no longer matches what the configuration previously
|
||||
requested.
|
||||
|
||||
In this case, the provider should return the value from the remote system,
|
||||
thereby discarding the value from the **Previous Run State**. When a
|
||||
provider does this, Terraform _may_ report it to the user as a change
|
||||
made outside of Terraform, if Terraform Core determined that the detected
|
||||
change was a possible cause of another planned action for a downstream
|
||||
resource instance.
|
||||
|
||||
This operation returns the **Prior State** to use for the next call to
|
||||
`PlanResourceChange`, thus completing the circle and beginning this process
|
||||
over again.
|
||||
|
||||
## Handling of Nested Blocks in Configuration
|
||||
|
||||
Nested blocks are a configuration-only construct and so the number of blocks
|
||||
cannot be changed on the fly during planning or during apply: each block
|
||||
represented in the configuration must have a corresponding nested object in
|
||||
the planned new state and new state, or Terraform Core will raise an error.
|
||||
|
||||
If a provider wishes to report about new instances of the sub-object type
|
||||
represented by nested blocks that are created implicitly during the apply
|
||||
operation -- for example, if a compute instance gets a default network
|
||||
interface created when none are explicitly specified -- this must be done via
|
||||
separate "computed" attributes alongside the nested blocks. This could be list
|
||||
or map of objects that includes a mixture of the objects described by the
|
||||
nested blocks in the configuration and any additional objects created implicitly
|
||||
by the remote system.
|
||||
|
||||
Provider protocol version 6 introduced the new idea of structural-typed
|
||||
attributes, which are a hybrid of attribute-style syntax but nested-block-style
|
||||
interpretation. For providers that use structural-typed attributes, they must
|
||||
follow the same rules as for a nested block type of the same nesting mode.
|
||||
|
||||
## Import Behavior
|
||||
|
||||
The main resource instance change lifecycle is concerned with objects whose
|
||||
entire lifecycle is driven through Terraform, including the initial creation
|
||||
of the object.
|
||||
|
||||
As an aid to those who are adopting Terraform as a replacement for existing
|
||||
processes or software, Terraform also supports adopting pre-existing objects
|
||||
to bring them under Terraform's management without needing to recreate them
|
||||
first.
|
||||
|
||||
When using this facility, the user provides the address of the resource
|
||||
instance they wish to bind the existing object to, and a string representation
|
||||
of the identifier of the existing object to be imported in a syntax defined
|
||||
by the provider on a per-resource-type basis, which we'll call the
|
||||
**Import ID**.
|
||||
|
||||
The import process trades the user's **Import ID** for a special
|
||||
**Import Stub State**, which behaves as a placeholder for the
|
||||
**Previous Run State** pretending as if a previous Terraform run is what had
|
||||
created the object.
|
||||
|
||||
### ImportResourceState
|
||||
|
||||
The `ImportResourceState` operation takes the user's given **Import ID** and
|
||||
uses it to verify that the given object exists and, if so, to retrieve enough
|
||||
data about it to produce the **Import Stub State**.
|
||||
|
||||
Terraform Core will always pass the returned **Import Stub State** to the
|
||||
normal `ReadResource` operation after `ImportResourceState` returns it, so
|
||||
in practice the provider may populate only the minimal subset of attributes
|
||||
that `ReadResource` will need to do its work, letting the normal function
|
||||
deal with populating the rest of the data to match what is currently set in
|
||||
the remote system.
|
||||
|
||||
For the same reasons that `ReadResource` is only a _best effort_ at detecting
|
||||
changes outside of Terraform, a provider may not be able to fully support
|
||||
importing for all resource types. In that case, the provider developer must
|
||||
choose between the following options:
|
||||
|
||||
* Perform only a partial import: the provider may choose to leave certain
|
||||
attributes set to `null` in the **Prior State** after both
|
||||
`ImportResourceState` and the subsequent `ReadResource` have completed.
|
||||
|
||||
In this case, the user can provide the missing value in the configuration
|
||||
and thus cause the next `PlanResourceChange` to plan to update that value
|
||||
to match the configuration. The provider's `PlanResourceChange` function
|
||||
must be ready to deal with the attribute being `null` in the
|
||||
**Prior State** and handle that appropriately.
|
||||
* Return an error explaining why importing isn't possible.
|
||||
|
||||
This is a last resort because of course it will then leave the user unable
|
||||
to bring the existing object under Terraform's management. However, if a
|
||||
particular object's design doesn't suit importing then it can be a better
|
||||
user experience to be clear and honest that the user must replace the object
|
||||
as part of adopting Terraform, rather than to perform an import that will
|
||||
leave the object in a situation where Terraform cannot meaningfully manage
|
||||
it.
|
||||
|
23
experiments.go
Normal file
23
experiments.go
Normal file
@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
// experimentsAllowed can be set to any non-empty string using Go linker
|
||||
// arguments in order to enable the use of experimental features for a
|
||||
// particular Terraform build:
|
||||
// go install -ldflags="-X 'main.experimentsAllowed=yes'"
|
||||
//
|
||||
// By default this variable is initialized as empty, in which case
|
||||
// experimental features are not available.
|
||||
//
|
||||
// The Terraform release process should arrange for this variable to be
|
||||
// set for alpha releases and development snapshots, but _not_ for
|
||||
// betas, release candidates, or final releases.
|
||||
//
|
||||
// (NOTE: Some experimental features predate the rule that experiments
|
||||
// are available only for alpha/dev builds, and so intentionally do not
|
||||
// make use of this setting to avoid retracting a previously-documented
|
||||
// open experiment.)
|
||||
var experimentsAllowed string
|
||||
|
||||
func ExperimentsAllowed() bool {
|
||||
return experimentsAllowed != ""
|
||||
}
|
52
go.mod
52
go.mod
@ -24,8 +24,7 @@ require (
|
||||
github.com/dylanmei/winrmtest v0.0.0-20210303004826-fbc9ae56efb6
|
||||
github.com/go-test/deep v1.0.3
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/google/go-cmp v0.5.6
|
||||
github.com/google/go-cmp v0.5.8
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/gophercloud/gophercloud v0.10.1-0.20200424014253-c3bfe50899e5
|
||||
github.com/gophercloud/utils v0.0.0-20200423144003-7c72efc7435d
|
||||
@ -36,17 +35,18 @@ require (
|
||||
github.com/hashicorp/go-azure-helpers v0.31.1
|
||||
github.com/hashicorp/go-checkpoint v0.5.0
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2
|
||||
github.com/hashicorp/go-getter v1.6.1
|
||||
github.com/hashicorp/go-getter v1.6.2
|
||||
github.com/hashicorp/go-hclog v0.15.0
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-plugin v1.4.3
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0
|
||||
github.com/hashicorp/go-tfe v1.0.0
|
||||
github.com/hashicorp/go-uuid v1.0.2
|
||||
github.com/hashicorp/go-version v1.3.0
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1
|
||||
github.com/hashicorp/go-tfe v1.6.0
|
||||
github.com/hashicorp/go-uuid v1.0.3
|
||||
github.com/hashicorp/go-version v1.6.0
|
||||
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f
|
||||
github.com/hashicorp/hcl/v2 v2.12.0
|
||||
github.com/hashicorp/hcl/v2 v2.13.0
|
||||
github.com/hashicorp/terraform-config-inspect v0.0.0-20210209133302-4fd17a0faac2
|
||||
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c
|
||||
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734
|
||||
github.com/jmespath/go-jmespath v0.4.0
|
||||
github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926
|
||||
@ -81,17 +81,16 @@ require (
|
||||
github.com/zclconf/go-cty v1.10.0
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b
|
||||
github.com/zclconf/go-cty-yaml v1.0.2
|
||||
go.etcd.io/etcd v0.5.0-alpha.5.0.20210428180535-15715dcf1ace
|
||||
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
|
||||
golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
|
||||
golang.org/x/text v0.3.7
|
||||
golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a
|
||||
golang.org/x/tools v0.1.11
|
||||
google.golang.org/api v0.44.0-impersonate-preview
|
||||
google.golang.org/grpc v1.36.1
|
||||
google.golang.org/grpc v1.47.0
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
|
||||
google.golang.org/protobuf v1.27.1
|
||||
honnef.co/go/tools v0.3.0
|
||||
@ -126,7 +125,6 @@ require (
|
||||
github.com/armon/go-radix v1.0.0 // indirect
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/coreos/go-semver v0.2.0 // indirect
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d // indirect
|
||||
github.com/creack/pty v1.1.18 // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
@ -137,6 +135,7 @@ require (
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
|
||||
@ -145,7 +144,7 @@ require (
|
||||
github.com/hashicorp/go-msgpack v0.5.4 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
||||
github.com/hashicorp/go-slug v0.8.0 // indirect
|
||||
github.com/hashicorp/go-slug v0.9.1 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.1 // indirect
|
||||
github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d // indirect
|
||||
github.com/hashicorp/serf v0.9.5 // indirect
|
||||
@ -171,24 +170,21 @@ require (
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.1.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.8 // indirect
|
||||
github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect
|
||||
github.com/vmihailenco/tagparser v0.1.1 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.uber.org/atomic v1.3.2 // indirect
|
||||
go.uber.org/multierr v1.1.0 // indirect
|
||||
go.uber.org/zap v1.10.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect
|
||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/klog/v2 v2.30.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
|
||||
@ -196,14 +192,4 @@ require (
|
||||
sigs.k8s.io/yaml v1.2.0 // indirect
|
||||
)
|
||||
|
||||
replace google.golang.org/grpc v1.36.1 => google.golang.org/grpc v1.27.1
|
||||
|
||||
replace github.com/golang/mock v1.5.0 => github.com/golang/mock v1.4.4
|
||||
|
||||
// github.com/dgrijalva/jwt-go is no longer maintained but is an indirect
|
||||
// dependency of the old etcdv2 backend, and so we need to keep this working
|
||||
// until that backend is removed. github.com/golang-jwt/jwt/v3 is a drop-in
|
||||
// replacement that includes a fix for CVE-2020-26160.
|
||||
replace github.com/dgrijalva/jwt-go => github.com/golang-jwt/jwt v3.2.1+incompatible
|
||||
|
||||
go 1.17
|
||||
go 1.18
|
||||
|
233
go.sum
233
go.sum
@ -11,7 +11,6 @@ cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.61.0/go.mod h1:XukKJg4Y7QsUu0Hxg3qQKUWR4VuWivmyMK2+rUyxAqw=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
@ -87,47 +86,35 @@ github.com/ChrisTrenkamp/goxpath v0.0.0-20190607011252-c5096ec8773d h1:W1diKnDQk
|
||||
github.com/ChrisTrenkamp/goxpath v0.0.0-20190607011252-c5096ec8773d/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4=
|
||||
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
|
||||
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/Masterminds/sprig/v3 v3.2.0 h1:P1ekkbuU73Ui/wS0nK1HOM37hh4xdfZo485UPf8rc+Y=
|
||||
github.com/Masterminds/sprig/v3 v3.2.0/go.mod h1:tWhwTbUTndesPNeF0C900vKoq283u6zp4APT9vaF3SI=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||
github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU=
|
||||
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
|
||||
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14=
|
||||
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw=
|
||||
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1501 h1:Ij3S0pNUMgHlhx3Ew8g9RNrt59EKhHYdMODGtFXJfSc=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1501/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190103054945-8205d1f41e70 h1:FrF4uxA24DF3ARNXVbUin3wa5fDLaB1Cy8mKks/LRz4=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190103054945-8205d1f41e70/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/aliyun/aliyun-tablestore-go-sdk v4.1.2+incompatible h1:ABQ7FF+IxSFHDMOTtjCfmMDMHiCq6EsAoCV/9sFinaM=
|
||||
github.com/aliyun/aliyun-tablestore-go-sdk v4.1.2+incompatible/go.mod h1:LDQHRZylxvcg8H7wBIDfvO5g/cy4/sz1iucBlc2l3Jw=
|
||||
github.com/andybalholm/crlf v0.0.0-20171020200849-670099aa064f/go.mod h1:k8feO4+kXDxro6ErPXBRTJ/ro2mf0SsFG8s7doP9kJE=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/antchfx/xmlquery v1.3.5 h1:I7TuBRqsnfFuL11ruavGm911Awx9IqSdiU6W/ztSmVw=
|
||||
github.com/antchfx/xmlquery v1.3.5/go.mod h1:64w0Xesg2sTaawIdNqMB+7qaW/bSqkQm+ssPaCMWNnc=
|
||||
github.com/antchfx/xpath v1.1.10 h1:cJ0pOvEdN/WvYXxvRrzQH9x5QWKpzHacYO8qzCcDYAg=
|
||||
github.com/antchfx/xpath v1.1.10/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
|
||||
github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
|
||||
github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
|
||||
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
|
||||
@ -135,9 +122,7 @@ github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFU
|
||||
github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
|
||||
github.com/apparentlymart/go-shquot v0.0.1 h1:MGV8lwxF4zw75lN7e0MGs7o6AFYn7L6AZaExUpLh0Mo=
|
||||
github.com/apparentlymart/go-shquot v0.0.1/go.mod h1:lw58XsE5IgUXZ9h0cxnypdx31p9mPFIVEQ9P3c7MlrU=
|
||||
github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0=
|
||||
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
|
||||
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
|
||||
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
|
||||
github.com/apparentlymart/go-userdirs v0.0.0-20200915174352-b0c018a67c13 h1:JtuelWqyixKApmXm3qghhZ7O96P6NKpyrlSIe8Rwnhw=
|
||||
@ -152,18 +137,13 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
|
||||
github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.42.35 h1:N4N9buNs4YlosI9N0+WYrq8cIZwdgv34yRbxzZlTvFs=
|
||||
github.com/aws/aws-sdk-go v1.42.35/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc=
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
|
||||
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
|
||||
@ -171,6 +151,7 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
|
||||
github.com/bmatcuk/doublestar v1.1.5 h1:2bNwBOmhyFEFcoB3tGvTD5xanq+4kyOZlB8wFYbMjkk=
|
||||
github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
@ -182,48 +163,44 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d h1:t5Wuyh53qYyg9eqn4BbnlIT+vmhyww0TatL+zT3uWgI=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dylanmei/iso8601 v0.1.0 h1:812NGQDBcqquTfH5Yeo7lwR0nzx/cKdsmf3qMjPURUI=
|
||||
github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ=
|
||||
github.com/dylanmei/winrmtest v0.0.0-20210303004826-fbc9ae56efb6 h1:zWydSUQBJApHwpQ4guHi+mGyQN/8yN6xbKWdDtL3ZNM=
|
||||
github.com/dylanmei/winrmtest v0.0.0-20210303004826-fbc9ae56efb6/go.mod h1:6BLLhzn1VEiJ4veuAGhINBTrBlV889Wd+aU4auxKOww=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
@ -231,17 +208,9 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
||||
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
|
||||
github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE=
|
||||
@ -251,26 +220,19 @@ github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34
|
||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
|
||||
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
|
||||
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@ -283,6 +245,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@ -319,8 +282,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
@ -344,7 +308,6 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||
@ -361,15 +324,9 @@ github.com/gophercloud/gophercloud v0.10.1-0.20200424014253-c3bfe50899e5/go.mod
|
||||
github.com/gophercloud/utils v0.0.0-20200423144003-7c72efc7435d h1:fduaPzWwIfvOMLuHk2Al3GZH0XbUqG8MbElPop+Igzs=
|
||||
github.com/gophercloud/utils v0.0.0-20200423144003-7c72efc7435d/go.mod h1:ehWUbLQJPqS0Ep+CxeD559hsm9pthPXadJNKwZkp43w=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/aws-sdk-go-base v0.7.1 h1:7s/aR3hFn74tYPVihzDyZe7y/+BorN70rr9ZvpV3j3o=
|
||||
github.com/hashicorp/aws-sdk-go-base v0.7.1/go.mod h1:2fRjWDv3jJBeN6mVWFHV6hFTNeFBx2gpDLQaZNxUVAY=
|
||||
github.com/hashicorp/consul/api v1.9.1 h1:SngrdG2L62qqLsUz85qcPhFZ78rPf8tcD5qjMgs6MME=
|
||||
@ -388,11 +345,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs=
|
||||
github.com/hashicorp/go-getter v1.5.3/go.mod h1:BrrV/1clo8cCYu6mxvboYg+KutTiFnXjMEgDD8+i7ZI=
|
||||
github.com/hashicorp/go-getter v1.6.1 h1:NASsgP4q6tL94WH6nJxKWj8As2H/2kop/bB1d8JMyRY=
|
||||
github.com/hashicorp/go-getter v1.6.1/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6pZHn4tv6vbwA=
|
||||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||
github.com/hashicorp/go-getter v1.6.2 h1:7jX7xcB+uVCliddZgeKyNxv0xoT7qL5KDtH7rU4IqIk=
|
||||
github.com/hashicorp/go-getter v1.6.2/go.mod h1:IZCrswsZPeWv9IkVnLElzRU/gz/QPi6pZHn4tv6vbwA=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
@ -407,41 +361,41 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0=
|
||||
github.com/hashicorp/go-plugin v1.4.1/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
|
||||
github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM=
|
||||
github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
|
||||
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
|
||||
github.com/hashicorp/go-slug v0.8.0 h1:h7AGtXVAI/cJ/Wwa/JQQaftQnWQmZbAzkzgZeZVVmLw=
|
||||
github.com/hashicorp/go-slug v0.8.0/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4=
|
||||
github.com/hashicorp/go-slug v0.9.1 h1:gYNVJ3t0jAWx8AT2eYZci3Xd7NBHyjayW9AR1DU4ki0=
|
||||
github.com/hashicorp/go-slug v0.9.1/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu41RKLH301v4=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-tfe v1.0.0 h1:CmwoHrOs7WJfD/yEmVjJ65+dyKeVRrgvRHBLVSQQ6Ks=
|
||||
github.com/hashicorp/go-tfe v1.0.0/go.mod h1:tJF/OlAXzVbmjiimAPLplSLgwg6kZDUOy0MzHuMwvF4=
|
||||
github.com/hashicorp/go-tfe v1.6.0 h1:lRfyTVLBP1njo2wShE9FimALzVZBfOqMGNuBdsor38w=
|
||||
github.com/hashicorp/go-tfe v1.6.0/go.mod h1:E8a90lC4kjU5Lc2c0D+SnWhUuyuoCIVm4Ewzv3jCD3A=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw=
|
||||
github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
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/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||
github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90=
|
||||
github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8=
|
||||
github.com/hashicorp/hcl/v2 v2.12.0 h1:PsYxySWpMD4KPaoJLnsHwtK5Qptvj/4Q6s0t4sUxZf4=
|
||||
github.com/hashicorp/hcl/v2 v2.12.0/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
|
||||
github.com/hashicorp/hcl/v2 v2.13.0 h1:0Apadu1w6M11dyGFxWnmhhcMjkbAiKCv7G1r/2QgCNc=
|
||||
github.com/hashicorp/hcl/v2 v2.13.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0=
|
||||
github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d h1:9ARUJJ1VVynB176G1HCwleORqCaXm/Vx0uUi0dL26I0=
|
||||
github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d/go.mod h1:Yog5+CPEM3c99L1CL2CFCYoSzgWm5vTU58idbRUaLik=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
@ -452,10 +406,8 @@ github.com/hashicorp/serf v0.9.5 h1:EBWvyu9tcRszt3Bxp3KNssBMP1KuHWyO51lz9+786iM=
|
||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||
github.com/hashicorp/terraform-config-inspect v0.0.0-20210209133302-4fd17a0faac2 h1:l+bLFvHjqtgNQwWxwrFX9PemGAAO2P1AGZM7zlMNvCs=
|
||||
github.com/hashicorp/terraform-config-inspect v0.0.0-20210209133302-4fd17a0faac2/go.mod h1:Z0Nnk4+3Cy89smEbrq+sl1bxc9198gIP4I7wcQF6Kqs=
|
||||
github.com/hashicorp/terraform-exec v0.14.0/go.mod h1:qrAASDq28KZiMPDnQ02sFS9udcqEkRly002EA2izXTA=
|
||||
github.com/hashicorp/terraform-json v0.12.0/go.mod h1:pmbq9o4EuL43db5+0ogX10Yofv1nozM+wskr/bGFJpI=
|
||||
github.com/hashicorp/terraform-plugin-go v0.4.0/go.mod h1:7u/6nt6vaiwcWE2GuJKbJwNlDFnf5n95xKw4hqIVr58=
|
||||
github.com/hashicorp/terraform-plugin-sdk/v2 v2.8.0/go.mod h1:6KbP09YzlB++S6XSUKYl83WyoHVN4MgeoCbPRsdfCtA=
|
||||
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c h1:D8aRO6+mTqHfLsK/BC3j5OAoogv1WLRWzY1AaTo3rBg=
|
||||
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c/go.mod h1:Wn3Na71knbXc1G8Lh+yu/dQWWJeFQEpDeJMtWMtlmNI=
|
||||
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0=
|
||||
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg=
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
@ -471,9 +423,6 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
|
||||
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
@ -483,29 +432,21 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926 h1:kie3qOosvRKqwij2HGzXWffwpXvcqfPPXRUw8I4F/mg=
|
||||
github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.11.2 h1:MiK62aErc3gIiVEtyzKfeOHgW7atJb5g/KNX5m3c2nQ=
|
||||
github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
@ -533,7 +474,6 @@ github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 h1:2ZKn+w/BJeL
|
||||
github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
|
||||
github.com/masterzen/winrm v0.0.0-20200615185753-c42b5136ff88 h1:cxuVcCvCLD9yYDbRCWw0jSgh1oT6P6mv3aJDKK5o7X4=
|
||||
github.com/masterzen/winrm v0.0.0-20200615185753-c42b5136ff88/go.mod h1:a2HXwefeat3evJHxFXSayvRHpYEPJYtErl4uIzfaUqY=
|
||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
||||
@ -545,17 +485,13 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-shellwords v1.0.4 h1:xmZZyxuP+bYKAKkA9ABYXVNJ+G/Wf3R8d8vAP3LDJJk=
|
||||
github.com/mattn/go-shellwords v1.0.4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||
github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4=
|
||||
github.com/mitchellh/cli v1.1.4 h1:qj8czE26AU4PbiaPXK5uVmMSM+V5BYsFBiM9HhGRLUA=
|
||||
github.com/mitchellh/cli v1.1.4/go.mod h1:vTLESy5mRhKOs9KDp0/RATawxP1UqBmdrpVRMnpcvKQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
@ -590,7 +526,6 @@ github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0Gq
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
@ -598,19 +533,16 @@ github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISe
|
||||
github.com/mozillazg/go-httpheader v0.3.0 h1:3brX5z8HTH+0RrNA1362Rc3HsaxyWEKtGY45YrhuINM=
|
||||
github.com/mozillazg/go-httpheader v0.3.0/go.mod h1:PuT8h0pw6efvp8ZeUec1Rs7dwjK08bt6gKSReGMqtdA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nishanths/exhaustive v0.7.11 h1:xV/WU3Vdwh5BUH4N06JNUznb6d5zhRPOnlgCrpNYNKA=
|
||||
github.com/nishanths/exhaustive v0.7.11/go.mod h1:gX+MP7DWMKJmNa1HfMozK+u04hQd3na9i0hyqf3/dOI=
|
||||
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
@ -627,7 +559,6 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/pkg/browser v0.0.0-20201207095918-0426ae3fba23 h1:dofHuld+js7eKSemxqTVIo8yRlpRw+H1SdpzZxWruBc=
|
||||
github.com/pkg/browser v0.0.0-20201207095918-0426ae3fba23/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@ -636,45 +567,24 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
@ -688,8 +598,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.232 h1:kwsWbh4rEw42ZDe9/812ebhbwNZxlQyZ2sTmxBOKhN4=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.232/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
@ -698,25 +608,17 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag v1.0.233 h1:5Tbi+jy
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag v1.0.233/go.mod h1:sX14+NSvMjOhNFaMtP2aDy6Bss8PyFXij21gpY6+DAs=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.29 h1:uwRBzc70Wgtc5iQQCowqecfRT0OpCXUOZzodZHOOEDs=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.29/go.mod h1:4E4+bQ2gBVJcgEC9Cufwylio4mXOct2iu05WjgEBx1o=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966 h1:j6JEOq5QWFker+d7mFQYOhjTZonQ7YkLTHm56dbn+yM=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tombuildsstuff/giovanni v0.15.1 h1:CVRaLOJ7C/eercCrKIsarfJ4SZoGMdBL9Q2deFDUXco=
|
||||
github.com/tombuildsstuff/giovanni v0.15.1/go.mod h1:0TZugJPEtqzPlMpuJHYfXY6Dq2uLPrXf98D2XQSxNbA=
|
||||
github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U=
|
||||
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
|
||||
github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY=
|
||||
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
|
||||
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
||||
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
|
||||
github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
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/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@ -725,23 +627,15 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||
github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||
github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
|
||||
github.com/zclconf/go-cty v1.8.4/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
|
||||
github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0=
|
||||
github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI=
|
||||
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
|
||||
github.com/zclconf/go-cty-yaml v1.0.2 h1:dNyg4QLTrv2IfJpm7Wtxi55ed5gLGOlPrZ6kMd51hY0=
|
||||
github.com/zclconf/go-cty-yaml v1.0.2/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
|
||||
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/etcd v0.5.0-alpha.5.0.20210428180535-15715dcf1ace h1:wOZR+AfzmQYNRqx1F+LL9TX8vBVzSbndRoc0tr/Bp4k=
|
||||
go.etcd.io/etcd v0.5.0-alpha.5.0.20210428180535-15715dcf1ace/go.mod h1:q+i20RPAmay+xq8LJ3VMOhXCNk4YCk3V7QP91meFavw=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
@ -750,15 +644,8 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@ -772,8 +659,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
@ -789,7 +674,6 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e h1:qyrTQ++p1afMkO4DPEeLGq/3oTsdlvdH4vqZUBWzUKM=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
@ -805,8 +689,9 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
@ -818,16 +703,14 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@ -865,10 +748,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
|
||||
@ -900,16 +781,12 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -918,7 +795,6 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -956,16 +832,13 @@ golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e h1:w36l2Uw3dRan1K3TyXriXvY+6T56GNmlKGcqiQUJDfM=
|
||||
golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@ -981,14 +854,12 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
|
||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@ -1030,7 +901,6 @@ golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roY
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
@ -1042,10 +912,11 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a h1:ofrrl6c6NG5/IOSx/R1cyiQxxjqlur0h/TvbUhkH0II=
|
||||
golang.org/x/tools v0.1.11-0.20220316014157-77aa08bb151a/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
|
||||
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -1106,10 +977,10 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
@ -1123,8 +994,9 @@ google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1 h1:E7wSQBXkH3T3diucK+9Z1kjn4+/9tNG7lZLr75oOhh8=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
@ -1133,18 +1005,21 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
|
||||
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
@ -1161,14 +1036,12 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
@ -1176,13 +1049,11 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@ -1191,8 +1062,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@ -1226,6 +1098,5 @@ sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNza
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
|
@ -2,6 +2,8 @@ package addrs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
@ -72,9 +74,10 @@ func (k StringKey) instanceKeySigil() {
|
||||
}
|
||||
|
||||
func (k StringKey) String() string {
|
||||
// FIXME: This isn't _quite_ right because Go's quoted string syntax is
|
||||
// slightly different than HCL's, but we'll accept it for now.
|
||||
return fmt.Sprintf("[%q]", string(k))
|
||||
// We use HCL's quoting syntax here so that we can in principle parse
|
||||
// an address constructed by this package as if it were an HCL
|
||||
// traversal, even if the string contains HCL's own metacharacters.
|
||||
return fmt.Sprintf("[%s]", toHCLQuotedString(string(k)))
|
||||
}
|
||||
|
||||
func (k StringKey) Value() cty.Value {
|
||||
@ -133,3 +136,56 @@ const (
|
||||
IntKeyType InstanceKeyType = 'I'
|
||||
StringKeyType InstanceKeyType = 'S'
|
||||
)
|
||||
|
||||
// toHCLQuotedString is a helper which formats the given string in a way that
|
||||
// HCL's expression parser would treat as a quoted string template.
|
||||
//
|
||||
// This includes:
|
||||
// - Adding quote marks at the start and the end.
|
||||
// - Using backslash escapes as needed for characters that cannot be represented directly.
|
||||
// - Escaping anything that would be treated as a template interpolation or control sequence.
|
||||
func toHCLQuotedString(s string) string {
|
||||
// This is an adaptation of a similar function inside the hclwrite package,
|
||||
// inlined here because hclwrite's version generates HCL tokens but we
|
||||
// only need normal strings.
|
||||
if len(s) == 0 {
|
||||
return `""`
|
||||
}
|
||||
var buf strings.Builder
|
||||
buf.WriteByte('"')
|
||||
for i, r := range s {
|
||||
switch r {
|
||||
case '\n':
|
||||
buf.WriteString(`\n`)
|
||||
case '\r':
|
||||
buf.WriteString(`\r`)
|
||||
case '\t':
|
||||
buf.WriteString(`\t`)
|
||||
case '"':
|
||||
buf.WriteString(`\"`)
|
||||
case '\\':
|
||||
buf.WriteString(`\\`)
|
||||
case '$', '%':
|
||||
buf.WriteRune(r)
|
||||
remain := s[i+1:]
|
||||
if len(remain) > 0 && remain[0] == '{' {
|
||||
// Double up our template introducer symbol to escape it.
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
default:
|
||||
if !unicode.IsPrint(r) {
|
||||
var fmted string
|
||||
if r < 65536 {
|
||||
fmted = fmt.Sprintf("\\u%04x", r)
|
||||
} else {
|
||||
fmted = fmt.Sprintf("\\U%08x", r)
|
||||
}
|
||||
buf.WriteString(fmted)
|
||||
} else {
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
buf.WriteByte('"')
|
||||
return buf.String()
|
||||
}
|
||||
|
75
internal/addrs/instance_key_test.go
Normal file
75
internal/addrs/instance_key_test.go
Normal file
@ -0,0 +1,75 @@
|
||||
package addrs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInstanceKeyString(t *testing.T) {
|
||||
tests := []struct {
|
||||
Key InstanceKey
|
||||
Want string
|
||||
}{
|
||||
{
|
||||
IntKey(0),
|
||||
`[0]`,
|
||||
},
|
||||
{
|
||||
IntKey(5),
|
||||
`[5]`,
|
||||
},
|
||||
{
|
||||
StringKey(""),
|
||||
`[""]`,
|
||||
},
|
||||
{
|
||||
StringKey("hi"),
|
||||
`["hi"]`,
|
||||
},
|
||||
{
|
||||
StringKey("0"),
|
||||
`["0"]`, // intentionally distinct from IntKey(0)
|
||||
},
|
||||
{
|
||||
// Quotes must be escaped
|
||||
StringKey(`"`),
|
||||
`["\""]`,
|
||||
},
|
||||
{
|
||||
// Escape sequences must themselves be escaped
|
||||
StringKey(`\r\n`),
|
||||
`["\\r\\n"]`,
|
||||
},
|
||||
{
|
||||
// Template interpolation sequences "${" must be escaped.
|
||||
StringKey(`${hello}`),
|
||||
`["$${hello}"]`,
|
||||
},
|
||||
{
|
||||
// Template control sequences "%{" must be escaped.
|
||||
StringKey(`%{ for something in something }%{ endfor }`),
|
||||
`["%%{ for something in something }%%{ endfor }"]`,
|
||||
},
|
||||
{
|
||||
// Dollar signs that aren't followed by { are not interpolation sequences
|
||||
StringKey(`$hello`),
|
||||
`["$hello"]`,
|
||||
},
|
||||
{
|
||||
// Percent signs that aren't followed by { are not control sequences
|
||||
StringKey(`%hello`),
|
||||
`["%hello"]`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
testName := fmt.Sprintf("%#v", test.Key)
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
got := test.Key.String()
|
||||
want := test.Want
|
||||
if got != want {
|
||||
t.Errorf("wrong result\nreciever: %s\ngot: %s\nwant: %s", testName, got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
128
internal/addrs/map.go
Normal file
128
internal/addrs/map.go
Normal file
@ -0,0 +1,128 @@
|
||||
package addrs
|
||||
|
||||
// Map represents a mapping whose keys are address types that implement
|
||||
// UniqueKeyer.
|
||||
//
|
||||
// Since not all address types are comparable in the Go language sense, this
|
||||
// type cannot work with the typical Go map access syntax, and so instead has
|
||||
// a method-based syntax. Use this type only for situations where the key
|
||||
// type isn't guaranteed to always be a valid key for a standard Go map.
|
||||
type Map[K UniqueKeyer, V any] struct {
|
||||
// Elems is the internal data structure of the map.
|
||||
//
|
||||
// This is exported to allow for comparisons during tests and other similar
|
||||
// careful read operations, but callers MUST NOT modify this map directly.
|
||||
// Use only the methods of Map to modify the contents of this structure,
|
||||
// to ensure that it remains correct and consistent.
|
||||
Elems map[UniqueKey]MapElem[K, V]
|
||||
}
|
||||
|
||||
type MapElem[K UniqueKeyer, V any] struct {
|
||||
Key K
|
||||
Value V
|
||||
}
|
||||
|
||||
func MakeMap[K UniqueKeyer, V any](initialElems ...MapElem[K, V]) Map[K, V] {
|
||||
inner := make(map[UniqueKey]MapElem[K, V], len(initialElems))
|
||||
ret := Map[K, V]{inner}
|
||||
for _, elem := range initialElems {
|
||||
ret.Put(elem.Key, elem.Value)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func MakeMapElem[K UniqueKeyer, V any](key K, value V) MapElem[K, V] {
|
||||
return MapElem[K, V]{key, value}
|
||||
}
|
||||
|
||||
// Put inserts a new element into the map, or replaces an existing element
|
||||
// which has an equivalent key.
|
||||
func (m Map[K, V]) Put(key K, value V) {
|
||||
realKey := key.UniqueKey()
|
||||
m.Elems[realKey] = MapElem[K, V]{key, value}
|
||||
}
|
||||
|
||||
// PutElement is like Put but takes the key and value from the given MapElement
|
||||
// structure instead of as individual arguments.
|
||||
func (m Map[K, V]) PutElement(elem MapElem[K, V]) {
|
||||
m.Put(elem.Key, elem.Value)
|
||||
}
|
||||
|
||||
// Remove deletes the element with the given key from the map, or does nothing
|
||||
// if there is no such element.
|
||||
func (m Map[K, V]) Remove(key K) {
|
||||
realKey := key.UniqueKey()
|
||||
delete(m.Elems, realKey)
|
||||
}
|
||||
|
||||
// Get returns the value of the element with the given key, or the zero value
|
||||
// of V if there is no such element.
|
||||
func (m Map[K, V]) Get(key K) V {
|
||||
realKey := key.UniqueKey()
|
||||
return m.Elems[realKey].Value
|
||||
}
|
||||
|
||||
// GetOk is like Get but additionally returns a flag for whether there was an
|
||||
// element with the given key present in the map.
|
||||
func (m Map[K, V]) GetOk(key K) (V, bool) {
|
||||
realKey := key.UniqueKey()
|
||||
elem, ok := m.Elems[realKey]
|
||||
return elem.Value, ok
|
||||
}
|
||||
|
||||
// Has returns true if and only if there is an element in the map which has the
|
||||
// given key.
|
||||
func (m Map[K, V]) Has(key K) bool {
|
||||
realKey := key.UniqueKey()
|
||||
_, ok := m.Elems[realKey]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the map.
|
||||
func (m Map[K, V]) Len() int {
|
||||
return len(m.Elems)
|
||||
}
|
||||
|
||||
// Elements returns a slice containing a snapshot of the current elements of
|
||||
// the map, in an unpredictable order.
|
||||
func (m Map[K, V]) Elements() []MapElem[K, V] {
|
||||
if len(m.Elems) == 0 {
|
||||
return nil
|
||||
}
|
||||
ret := make([]MapElem[K, V], 0, len(m.Elems))
|
||||
for _, elem := range m.Elems {
|
||||
ret = append(ret, elem)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Keys returns a Set[K] containing a snapshot of the current keys of elements
|
||||
// of the map.
|
||||
func (m Map[K, V]) Keys() Set[K] {
|
||||
if len(m.Elems) == 0 {
|
||||
return nil
|
||||
}
|
||||
ret := make(Set[K], len(m.Elems))
|
||||
|
||||
// We mess with the internals of Set here, rather than going through its
|
||||
// public interface, because that means we can avoid re-calling UniqueKey
|
||||
// on all of the elements when we know that our own Put method would have
|
||||
// already done the same thing.
|
||||
for realKey, elem := range m.Elems {
|
||||
ret[realKey] = elem.Key
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Values returns a slice containing a snapshot of the current values of
|
||||
// elements of the map, in an unpredictable order.
|
||||
func (m Map[K, V]) Values() []V {
|
||||
if len(m.Elems) == 0 {
|
||||
return nil
|
||||
}
|
||||
ret := make([]V, 0, len(m.Elems))
|
||||
for _, elem := range m.Elems {
|
||||
ret = append(ret, elem.Value)
|
||||
}
|
||||
return ret
|
||||
}
|
83
internal/addrs/map_test.go
Normal file
83
internal/addrs/map_test.go
Normal file
@ -0,0 +1,83 @@
|
||||
package addrs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMap(t *testing.T) {
|
||||
variableName := InputVariable{Name: "name"}
|
||||
localHello := LocalValue{Name: "hello"}
|
||||
pathModule := PathAttr{Name: "module"}
|
||||
moduleBeep := ModuleCall{Name: "beep"}
|
||||
eachKey := ForEachAttr{Name: "key"} // intentionally not in the map
|
||||
|
||||
m := MakeMap(
|
||||
MakeMapElem[Referenceable](variableName, "Aisling"),
|
||||
)
|
||||
|
||||
m.Put(localHello, "hello")
|
||||
m.Put(pathModule, "boop")
|
||||
m.Put(moduleBeep, "unrealistic")
|
||||
|
||||
keySet := m.Keys()
|
||||
if want := variableName; !m.Has(want) {
|
||||
t.Errorf("map does not include %s", want)
|
||||
}
|
||||
if want := variableName; !keySet.Has(want) {
|
||||
t.Errorf("key set does not include %s", want)
|
||||
}
|
||||
if want := localHello; !m.Has(want) {
|
||||
t.Errorf("map does not include %s", want)
|
||||
}
|
||||
if want := localHello; !keySet.Has(want) {
|
||||
t.Errorf("key set does not include %s", want)
|
||||
}
|
||||
if want := pathModule; !keySet.Has(want) {
|
||||
t.Errorf("key set does not include %s", want)
|
||||
}
|
||||
if want := moduleBeep; !keySet.Has(want) {
|
||||
t.Errorf("key set does not include %s", want)
|
||||
}
|
||||
if doNotWant := eachKey; m.Has(doNotWant) {
|
||||
t.Errorf("map includes rogue element %s", doNotWant)
|
||||
}
|
||||
if doNotWant := eachKey; keySet.Has(doNotWant) {
|
||||
t.Errorf("key set includes rogue element %s", doNotWant)
|
||||
}
|
||||
|
||||
if got, want := m.Get(variableName), "Aisling"; got != want {
|
||||
t.Errorf("unexpected value %q for %s; want %q", got, variableName, want)
|
||||
}
|
||||
if got, want := m.Get(localHello), "hello"; got != want {
|
||||
t.Errorf("unexpected value %q for %s; want %q", got, localHello, want)
|
||||
}
|
||||
if got, want := m.Get(pathModule), "boop"; got != want {
|
||||
t.Errorf("unexpected value %q for %s; want %q", got, pathModule, want)
|
||||
}
|
||||
if got, want := m.Get(moduleBeep), "unrealistic"; got != want {
|
||||
t.Errorf("unexpected value %q for %s; want %q", got, moduleBeep, want)
|
||||
}
|
||||
if got, want := m.Get(eachKey), ""; got != want {
|
||||
// eachKey isn't in the map, so Get returns the zero value of string
|
||||
t.Errorf("unexpected value %q for %s; want %q", got, eachKey, want)
|
||||
}
|
||||
|
||||
if v, ok := m.GetOk(variableName); v != "Aisling" || !ok {
|
||||
t.Errorf("GetOk for %q returned incorrect result (%q, %#v)", variableName, v, ok)
|
||||
}
|
||||
if v, ok := m.GetOk(eachKey); v != "" || ok {
|
||||
t.Errorf("GetOk for %q returned incorrect result (%q, %#v)", eachKey, v, ok)
|
||||
}
|
||||
|
||||
m.Remove(moduleBeep)
|
||||
if doNotWant := moduleBeep; m.Has(doNotWant) {
|
||||
t.Errorf("map still includes %s after removing it", doNotWant)
|
||||
}
|
||||
if want := moduleBeep; !keySet.Has(want) {
|
||||
t.Errorf("key set no longer includes %s after removing it from the map; key set is supposed to be a snapshot at the time of call", want)
|
||||
}
|
||||
keySet = m.Keys()
|
||||
if doNotWant := moduleBeep; keySet.Has(doNotWant) {
|
||||
t.Errorf("key set still includes %s after a second call after removing it from the map", doNotWant)
|
||||
}
|
||||
}
|
@ -33,11 +33,22 @@ func (m Module) String() string {
|
||||
if len(m) == 0 {
|
||||
return ""
|
||||
}
|
||||
var steps []string
|
||||
for _, s := range m {
|
||||
steps = append(steps, "module", s)
|
||||
// Calculate necessary space.
|
||||
l := 0
|
||||
for _, step := range m {
|
||||
l += len(step)
|
||||
}
|
||||
return strings.Join(steps, ".")
|
||||
buf := strings.Builder{}
|
||||
// 8 is len(".module.") which separates entries.
|
||||
buf.Grow(l + len(m)*8)
|
||||
sep := ""
|
||||
for _, step := range m {
|
||||
buf.WriteString(sep)
|
||||
buf.WriteString("module.")
|
||||
buf.WriteString(step)
|
||||
sep = "."
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (m Module) Equal(other Module) bool {
|
||||
|
@ -1,9 +1,7 @@
|
||||
package addrs
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
svchost "github.com/hashicorp/terraform-svchost"
|
||||
tfaddr "github.com/hashicorp/terraform-registry-address"
|
||||
)
|
||||
|
||||
// A ModulePackage represents a physical location where Terraform can retrieve
|
||||
@ -45,45 +43,4 @@ func (p ModulePackage) String() string {
|
||||
// registry in order to find a real module package address. These being
|
||||
// distinct is intended to help future maintainers more easily follow the
|
||||
// series of steps in the module installer, with the help of the type checker.
|
||||
type ModuleRegistryPackage struct {
|
||||
Host svchost.Hostname
|
||||
Namespace string
|
||||
Name string
|
||||
TargetSystem string
|
||||
}
|
||||
|
||||
func (s ModuleRegistryPackage) String() string {
|
||||
var buf strings.Builder
|
||||
// Note: we're using the "display" form of the hostname here because
|
||||
// for our service hostnames "for display" means something different:
|
||||
// it means to render non-ASCII characters directly as Unicode
|
||||
// characters, rather than using the "punycode" representation we
|
||||
// use for internal processing, and so the "display" representation
|
||||
// is actually what users would write in their configurations.
|
||||
return s.Host.ForDisplay() + "/" + s.ForRegistryProtocol()
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (s ModuleRegistryPackage) ForDisplay() string {
|
||||
if s.Host == DefaultModuleRegistryHost {
|
||||
return s.ForRegistryProtocol()
|
||||
}
|
||||
return s.Host.ForDisplay() + "/" + s.ForRegistryProtocol()
|
||||
}
|
||||
|
||||
// ForRegistryProtocol returns a string representation of just the namespace,
|
||||
// name, and target system portions of the address, always omitting the
|
||||
// registry hostname and the subdirectory portion, if any.
|
||||
//
|
||||
// This is primarily intended for generating addresses to send to the
|
||||
// registry in question via the registry protocol, since the protocol
|
||||
// skips sending the registry its own hostname as part of identifiers.
|
||||
func (s ModuleRegistryPackage) ForRegistryProtocol() string {
|
||||
var buf strings.Builder
|
||||
buf.WriteString(s.Namespace)
|
||||
buf.WriteByte('/')
|
||||
buf.WriteString(s.Name)
|
||||
buf.WriteByte('/')
|
||||
buf.WriteString(s.TargetSystem)
|
||||
return buf.String()
|
||||
}
|
||||
type ModuleRegistryPackage = tfaddr.ModulePackage
|
||||
|
@ -3,10 +3,9 @@ package addrs
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
svchost "github.com/hashicorp/terraform-svchost"
|
||||
tfaddr "github.com/hashicorp/terraform-registry-address"
|
||||
"github.com/hashicorp/terraform/internal/getmodules"
|
||||
)
|
||||
|
||||
@ -197,30 +196,11 @@ func (s ModuleSourceLocal) ForDisplay() string {
|
||||
// combination of a ModuleSourceRegistry and a module version number into
|
||||
// a concrete ModuleSourceRemote that Terraform will then download and
|
||||
// install.
|
||||
type ModuleSourceRegistry struct {
|
||||
// PackageAddr is the registry package that the target module belongs to.
|
||||
// The module installer must translate this into a ModuleSourceRemote
|
||||
// using the registry API and then take that underlying address's
|
||||
// PackageAddr in order to find the actual package location.
|
||||
PackageAddr ModuleRegistryPackage
|
||||
|
||||
// If Subdir is non-empty then it represents a sub-directory within the
|
||||
// remote package that the registry address eventually resolves to.
|
||||
// This will ultimately become the suffix of the Subdir of the
|
||||
// ModuleSourceRemote that the registry address translates to.
|
||||
//
|
||||
// Subdir uses a normalized forward-slash-based path syntax within the
|
||||
// virtual filesystem represented by the final package. It will never
|
||||
// include `../` or `./` sequences.
|
||||
Subdir string
|
||||
}
|
||||
type ModuleSourceRegistry tfaddr.Module
|
||||
|
||||
// DefaultModuleRegistryHost is the hostname used for registry-based module
|
||||
// source addresses that do not have an explicit hostname.
|
||||
const DefaultModuleRegistryHost = svchost.Hostname("registry.terraform.io")
|
||||
|
||||
var moduleRegistryNamePattern = regexp.MustCompile("^[0-9A-Za-z](?:[0-9A-Za-z-_]{0,62}[0-9A-Za-z])?$")
|
||||
var moduleRegistryTargetSystemPattern = regexp.MustCompile("^[0-9a-z]{1,64}$")
|
||||
const DefaultModuleRegistryHost = tfaddr.DefaultModuleRegistryHost
|
||||
|
||||
// ParseModuleSourceRegistry is a variant of ParseModuleSource which only
|
||||
// accepts module registry addresses, and will reject any other address type.
|
||||
@ -237,147 +217,30 @@ func ParseModuleSourceRegistry(raw string) (ModuleSource, error) {
|
||||
return ModuleSourceRegistry{}, fmt.Errorf("can't use local directory %q as a module registry address", raw)
|
||||
}
|
||||
|
||||
ret, err := parseModuleSourceRegistry(raw)
|
||||
src, err := tfaddr.ParseModuleSource(raw)
|
||||
if err != nil {
|
||||
// This is to make sure we return a nil ModuleSource, rather than
|
||||
// a non-nil ModuleSource containing a zero-value ModuleSourceRegistry.
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func parseModuleSourceRegistry(raw string) (ModuleSourceRegistry, error) {
|
||||
var err error
|
||||
|
||||
var subDir string
|
||||
raw, subDir = getmodules.SplitPackageSubdir(raw)
|
||||
if strings.HasPrefix(subDir, "../") {
|
||||
return ModuleSourceRegistry{}, fmt.Errorf("subdirectory path %q leads outside of the module package", subDir)
|
||||
}
|
||||
|
||||
parts := strings.Split(raw, "/")
|
||||
// A valid registry address has either three or four parts, because the
|
||||
// leading hostname part is optional.
|
||||
if len(parts) != 3 && len(parts) != 4 {
|
||||
return ModuleSourceRegistry{}, fmt.Errorf("a module registry source address must have either three or four slash-separated components")
|
||||
}
|
||||
|
||||
host := DefaultModuleRegistryHost
|
||||
if len(parts) == 4 {
|
||||
host, err = svchost.ForComparison(parts[0])
|
||||
if err != nil {
|
||||
// The svchost library doesn't produce very good error messages to
|
||||
// return to an end-user, so we'll use some custom ones here.
|
||||
switch {
|
||||
case strings.Contains(parts[0], "--"):
|
||||
// Looks like possibly punycode, which we don't allow here
|
||||
// to ensure that source addresses are written readably.
|
||||
return ModuleSourceRegistry{}, fmt.Errorf("invalid module registry hostname %q; internationalized domain names must be given as direct unicode characters, not in punycode", parts[0])
|
||||
default:
|
||||
return ModuleSourceRegistry{}, fmt.Errorf("invalid module registry hostname %q", parts[0])
|
||||
}
|
||||
}
|
||||
if !strings.Contains(host.String(), ".") {
|
||||
return ModuleSourceRegistry{}, fmt.Errorf("invalid module registry hostname: must contain at least one dot")
|
||||
}
|
||||
// Discard the hostname prefix now that we've processed it
|
||||
parts = parts[1:]
|
||||
}
|
||||
|
||||
ret := ModuleSourceRegistry{
|
||||
PackageAddr: ModuleRegistryPackage{
|
||||
Host: host,
|
||||
},
|
||||
|
||||
Subdir: subDir,
|
||||
}
|
||||
|
||||
if host == svchost.Hostname("github.com") || host == svchost.Hostname("bitbucket.org") {
|
||||
return ret, fmt.Errorf("can't use %q as a module registry host, because it's reserved for installing directly from version control repositories", host)
|
||||
}
|
||||
|
||||
if ret.PackageAddr.Namespace, err = parseModuleRegistryName(parts[0]); err != nil {
|
||||
if strings.Contains(parts[0], ".") {
|
||||
// Seems like the user omitted one of the latter components in
|
||||
// an address with an explicit hostname.
|
||||
return ret, fmt.Errorf("source address must have three more components after the hostname: the namespace, the name, and the target system")
|
||||
}
|
||||
return ret, fmt.Errorf("invalid namespace %q: %s", parts[0], err)
|
||||
}
|
||||
if ret.PackageAddr.Name, err = parseModuleRegistryName(parts[1]); err != nil {
|
||||
return ret, fmt.Errorf("invalid module name %q: %s", parts[1], err)
|
||||
}
|
||||
if ret.PackageAddr.TargetSystem, err = parseModuleRegistryTargetSystem(parts[2]); err != nil {
|
||||
if strings.Contains(parts[2], "?") {
|
||||
// The user was trying to include a query string, probably?
|
||||
return ret, fmt.Errorf("module registry addresses may not include a query string portion")
|
||||
}
|
||||
return ret, fmt.Errorf("invalid target system %q: %s", parts[2], err)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// parseModuleRegistryName validates and normalizes a string in either the
|
||||
// "namespace" or "name" position of a module registry source address.
|
||||
func parseModuleRegistryName(given string) (string, error) {
|
||||
// Similar to the names in provider source addresses, we defined these
|
||||
// to be compatible with what filesystems and typical remote systems
|
||||
// like GitHub allow in names. Unfortunately we didn't end up defining
|
||||
// these exactly equivalently: provider names can only use dashes as
|
||||
// punctuation, whereas module names can use underscores. So here we're
|
||||
// using some regular expressions from the original module source
|
||||
// implementation, rather than using the IDNA rules as we do in
|
||||
// ParseProviderPart.
|
||||
|
||||
if !moduleRegistryNamePattern.MatchString(given) {
|
||||
return "", fmt.Errorf("must be between one and 64 characters, including ASCII letters, digits, dashes, and underscores, where dashes and underscores may not be the prefix or suffix")
|
||||
}
|
||||
|
||||
// We also skip normalizing the name to lowercase, because we historically
|
||||
// didn't do that and so existing module registries might be doing
|
||||
// case-sensitive matching.
|
||||
return given, nil
|
||||
}
|
||||
|
||||
// parseModuleRegistryTargetSystem validates and normalizes a string in the
|
||||
// "target system" position of a module registry source address. This is
|
||||
// what we historically called "provider" but never actually enforced as
|
||||
// being a provider address, and now _cannot_ be a provider address because
|
||||
// provider addresses have three slash-separated components of their own.
|
||||
func parseModuleRegistryTargetSystem(given string) (string, error) {
|
||||
// Similar to the names in provider source addresses, we defined these
|
||||
// to be compatible with what filesystems and typical remote systems
|
||||
// like GitHub allow in names. Unfortunately we didn't end up defining
|
||||
// these exactly equivalently: provider names can't use dashes or
|
||||
// underscores. So here we're using some regular expressions from the
|
||||
// original module source implementation, rather than using the IDNA rules
|
||||
// as we do in ParseProviderPart.
|
||||
|
||||
if !moduleRegistryTargetSystemPattern.MatchString(given) {
|
||||
return "", fmt.Errorf("must be between one and 64 ASCII letters or digits")
|
||||
}
|
||||
|
||||
// We also skip normalizing the name to lowercase, because we historically
|
||||
// didn't do that and so existing module registries might be doing
|
||||
// case-sensitive matching.
|
||||
return given, nil
|
||||
return ModuleSourceRegistry{
|
||||
Package: src.Package,
|
||||
Subdir: src.Subdir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s ModuleSourceRegistry) moduleSource() {}
|
||||
|
||||
func (s ModuleSourceRegistry) String() string {
|
||||
if s.Subdir != "" {
|
||||
return s.PackageAddr.String() + "//" + s.Subdir
|
||||
return s.Package.String() + "//" + s.Subdir
|
||||
}
|
||||
return s.PackageAddr.String()
|
||||
return s.Package.String()
|
||||
}
|
||||
|
||||
func (s ModuleSourceRegistry) ForDisplay() string {
|
||||
if s.Subdir != "" {
|
||||
return s.PackageAddr.ForDisplay() + "//" + s.Subdir
|
||||
return s.Package.ForDisplay() + "//" + s.Subdir
|
||||
}
|
||||
return s.PackageAddr.ForDisplay()
|
||||
return s.Package.ForDisplay()
|
||||
}
|
||||
|
||||
// ModuleSourceRemote is a ModuleSource representing a remote location from
|
||||
@ -387,9 +250,9 @@ func (s ModuleSourceRegistry) ForDisplay() string {
|
||||
// means that it's selecting a sub-directory of the given package to use as
|
||||
// the entry point into the package.
|
||||
type ModuleSourceRemote struct {
|
||||
// PackageAddr is the address of the remote package that the requested
|
||||
// Package is the address of the remote package that the requested
|
||||
// module belongs to.
|
||||
PackageAddr ModulePackage
|
||||
Package ModulePackage
|
||||
|
||||
// If Subdir is non-empty then it represents a sub-directory within the
|
||||
// remote package which will serve as the entry-point for the package.
|
||||
@ -445,8 +308,8 @@ func parseModuleSourceRemote(raw string) (ModuleSourceRemote, error) {
|
||||
}
|
||||
|
||||
return ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage(norm),
|
||||
Subdir: subDir,
|
||||
Package: ModulePackage(norm),
|
||||
Subdir: subDir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -454,9 +317,9 @@ func (s ModuleSourceRemote) moduleSource() {}
|
||||
|
||||
func (s ModuleSourceRemote) String() string {
|
||||
if s.Subdir != "" {
|
||||
return s.PackageAddr.String() + "//" + s.Subdir
|
||||
return s.Package.String() + "//" + s.Subdir
|
||||
}
|
||||
return s.PackageAddr.String()
|
||||
return s.Package.String()
|
||||
}
|
||||
|
||||
func (s ModuleSourceRemote) ForDisplay() string {
|
||||
|
@ -59,7 +59,7 @@ func TestParseModuleSource(t *testing.T) {
|
||||
"main registry implied": {
|
||||
input: "hashicorp/subnets/cidr",
|
||||
want: ModuleSourceRegistry{
|
||||
PackageAddr: ModuleRegistryPackage{
|
||||
Package: ModuleRegistryPackage{
|
||||
Host: svchost.Hostname("registry.terraform.io"),
|
||||
Namespace: "hashicorp",
|
||||
Name: "subnets",
|
||||
@ -71,7 +71,7 @@ func TestParseModuleSource(t *testing.T) {
|
||||
"main registry implied, subdir": {
|
||||
input: "hashicorp/subnets/cidr//examples/foo",
|
||||
want: ModuleSourceRegistry{
|
||||
PackageAddr: ModuleRegistryPackage{
|
||||
Package: ModuleRegistryPackage{
|
||||
Host: svchost.Hostname("registry.terraform.io"),
|
||||
Namespace: "hashicorp",
|
||||
Name: "subnets",
|
||||
@ -92,7 +92,7 @@ func TestParseModuleSource(t *testing.T) {
|
||||
"custom registry": {
|
||||
input: "example.com/awesomecorp/network/happycloud",
|
||||
want: ModuleSourceRegistry{
|
||||
PackageAddr: ModuleRegistryPackage{
|
||||
Package: ModuleRegistryPackage{
|
||||
Host: svchost.Hostname("example.com"),
|
||||
Namespace: "awesomecorp",
|
||||
Name: "network",
|
||||
@ -104,7 +104,7 @@ func TestParseModuleSource(t *testing.T) {
|
||||
"custom registry, subdir": {
|
||||
input: "example.com/awesomecorp/network/happycloud//examples/foo",
|
||||
want: ModuleSourceRegistry{
|
||||
PackageAddr: ModuleRegistryPackage{
|
||||
Package: ModuleRegistryPackage{
|
||||
Host: svchost.Hostname("example.com"),
|
||||
Namespace: "awesomecorp",
|
||||
Name: "network",
|
||||
@ -118,68 +118,68 @@ func TestParseModuleSource(t *testing.T) {
|
||||
"github.com shorthand": {
|
||||
input: "github.com/hashicorp/terraform-cidr-subnets",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("git::https://github.com/hashicorp/terraform-cidr-subnets.git"),
|
||||
Package: ModulePackage("git::https://github.com/hashicorp/terraform-cidr-subnets.git"),
|
||||
},
|
||||
},
|
||||
"github.com shorthand, subdir": {
|
||||
input: "github.com/hashicorp/terraform-cidr-subnets//example/foo",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("git::https://github.com/hashicorp/terraform-cidr-subnets.git"),
|
||||
Subdir: "example/foo",
|
||||
Package: ModulePackage("git::https://github.com/hashicorp/terraform-cidr-subnets.git"),
|
||||
Subdir: "example/foo",
|
||||
},
|
||||
},
|
||||
"git protocol, URL-style": {
|
||||
input: "git://example.com/code/baz.git",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("git://example.com/code/baz.git"),
|
||||
Package: ModulePackage("git://example.com/code/baz.git"),
|
||||
},
|
||||
},
|
||||
"git protocol, URL-style, subdir": {
|
||||
input: "git://example.com/code/baz.git//bleep/bloop",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("git://example.com/code/baz.git"),
|
||||
Subdir: "bleep/bloop",
|
||||
Package: ModulePackage("git://example.com/code/baz.git"),
|
||||
Subdir: "bleep/bloop",
|
||||
},
|
||||
},
|
||||
"git over HTTPS, URL-style": {
|
||||
input: "git::https://example.com/code/baz.git",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("git::https://example.com/code/baz.git"),
|
||||
Package: ModulePackage("git::https://example.com/code/baz.git"),
|
||||
},
|
||||
},
|
||||
"git over HTTPS, URL-style, subdir": {
|
||||
input: "git::https://example.com/code/baz.git//bleep/bloop",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("git::https://example.com/code/baz.git"),
|
||||
Subdir: "bleep/bloop",
|
||||
Package: ModulePackage("git::https://example.com/code/baz.git"),
|
||||
Subdir: "bleep/bloop",
|
||||
},
|
||||
},
|
||||
"git over SSH, URL-style": {
|
||||
input: "git::ssh://git@example.com/code/baz.git",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("git::ssh://git@example.com/code/baz.git"),
|
||||
Package: ModulePackage("git::ssh://git@example.com/code/baz.git"),
|
||||
},
|
||||
},
|
||||
"git over SSH, URL-style, subdir": {
|
||||
input: "git::ssh://git@example.com/code/baz.git//bleep/bloop",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("git::ssh://git@example.com/code/baz.git"),
|
||||
Subdir: "bleep/bloop",
|
||||
Package: ModulePackage("git::ssh://git@example.com/code/baz.git"),
|
||||
Subdir: "bleep/bloop",
|
||||
},
|
||||
},
|
||||
"git over SSH, scp-style": {
|
||||
input: "git::git@example.com:code/baz.git",
|
||||
want: ModuleSourceRemote{
|
||||
// Normalized to URL-style
|
||||
PackageAddr: ModulePackage("git::ssh://git@example.com/code/baz.git"),
|
||||
Package: ModulePackage("git::ssh://git@example.com/code/baz.git"),
|
||||
},
|
||||
},
|
||||
"git over SSH, scp-style, subdir": {
|
||||
input: "git::git@example.com:code/baz.git//bleep/bloop",
|
||||
want: ModuleSourceRemote{
|
||||
// Normalized to URL-style
|
||||
PackageAddr: ModulePackage("git::ssh://git@example.com/code/baz.git"),
|
||||
Subdir: "bleep/bloop",
|
||||
Package: ModulePackage("git::ssh://git@example.com/code/baz.git"),
|
||||
Subdir: "bleep/bloop",
|
||||
},
|
||||
},
|
||||
|
||||
@ -190,63 +190,63 @@ func TestParseModuleSource(t *testing.T) {
|
||||
"Google Cloud Storage bucket implied, path prefix": {
|
||||
input: "www.googleapis.com/storage/v1/BUCKET_NAME/PATH_TO_MODULE",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH_TO_MODULE"),
|
||||
Package: ModulePackage("gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH_TO_MODULE"),
|
||||
},
|
||||
},
|
||||
"Google Cloud Storage bucket, path prefix": {
|
||||
input: "gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH_TO_MODULE",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH_TO_MODULE"),
|
||||
Package: ModulePackage("gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH_TO_MODULE"),
|
||||
},
|
||||
},
|
||||
"Google Cloud Storage bucket implied, archive object": {
|
||||
input: "www.googleapis.com/storage/v1/BUCKET_NAME/PATH/TO/module.zip",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH/TO/module.zip"),
|
||||
Package: ModulePackage("gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH/TO/module.zip"),
|
||||
},
|
||||
},
|
||||
"Google Cloud Storage bucket, archive object": {
|
||||
input: "gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH/TO/module.zip",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH/TO/module.zip"),
|
||||
Package: ModulePackage("gcs::https://www.googleapis.com/storage/v1/BUCKET_NAME/PATH/TO/module.zip"),
|
||||
},
|
||||
},
|
||||
|
||||
"Amazon S3 bucket implied, archive object": {
|
||||
input: "s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("s3::https://s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip"),
|
||||
Package: ModulePackage("s3::https://s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip"),
|
||||
},
|
||||
},
|
||||
"Amazon S3 bucket, archive object": {
|
||||
input: "s3::https://s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("s3::https://s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip"),
|
||||
Package: ModulePackage("s3::https://s3-eu-west-1.amazonaws.com/examplecorp-terraform-modules/vpc.zip"),
|
||||
},
|
||||
},
|
||||
|
||||
"HTTP URL": {
|
||||
input: "http://example.com/module",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("http://example.com/module"),
|
||||
Package: ModulePackage("http://example.com/module"),
|
||||
},
|
||||
},
|
||||
"HTTPS URL": {
|
||||
input: "https://example.com/module",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("https://example.com/module"),
|
||||
Package: ModulePackage("https://example.com/module"),
|
||||
},
|
||||
},
|
||||
"HTTPS URL, archive file": {
|
||||
input: "https://example.com/module.zip",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("https://example.com/module.zip"),
|
||||
Package: ModulePackage("https://example.com/module.zip"),
|
||||
},
|
||||
},
|
||||
"HTTPS URL, forced archive file": {
|
||||
input: "https://example.com/module?archive=tar",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("https://example.com/module?archive=tar"),
|
||||
Package: ModulePackage("https://example.com/module?archive=tar"),
|
||||
},
|
||||
},
|
||||
"HTTPS URL, forced archive file and checksum": {
|
||||
@ -255,7 +255,7 @@ func TestParseModuleSource(t *testing.T) {
|
||||
// The query string only actually gets processed when we finally
|
||||
// do the get, so "checksum=blah" is accepted as valid up
|
||||
// at this parsing layer.
|
||||
PackageAddr: ModulePackage("https://example.com/module?archive=tar&checksum=blah"),
|
||||
Package: ModulePackage("https://example.com/module?archive=tar&checksum=blah"),
|
||||
},
|
||||
},
|
||||
|
||||
@ -266,7 +266,7 @@ func TestParseModuleSource(t *testing.T) {
|
||||
// is replaced by a deep filesystem copy instead.
|
||||
input: "/tmp/foo/example",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("file:///tmp/foo/example"),
|
||||
Package: ModulePackage("file:///tmp/foo/example"),
|
||||
},
|
||||
},
|
||||
"absolute filesystem path, subdir": {
|
||||
@ -277,8 +277,8 @@ func TestParseModuleSource(t *testing.T) {
|
||||
// syntax to move the package root higher in the real filesystem.
|
||||
input: "/tmp/foo//example",
|
||||
want: ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("file:///tmp/foo"),
|
||||
Subdir: "example",
|
||||
Package: ModulePackage("file:///tmp/foo"),
|
||||
Subdir: "example",
|
||||
},
|
||||
},
|
||||
|
||||
@ -310,7 +310,7 @@ func TestParseModuleSource(t *testing.T) {
|
||||
// Unfortunately go-getter doesn't actually reject a totally
|
||||
// invalid address like this until getting time, as long as
|
||||
// it looks somewhat like a URL.
|
||||
PackageAddr: ModulePackage("dfgdfgsd:dgfhdfghdfghdfg/dfghdfghdfg"),
|
||||
Package: ModulePackage("dfgdfgsd:dgfhdfghdfghdfg/dfghdfghdfg"),
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -344,8 +344,8 @@ func TestParseModuleSource(t *testing.T) {
|
||||
func TestModuleSourceRemoteFromRegistry(t *testing.T) {
|
||||
t.Run("both have subdir", func(t *testing.T) {
|
||||
remote := ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("boop"),
|
||||
Subdir: "foo",
|
||||
Package: ModulePackage("boop"),
|
||||
Subdir: "foo",
|
||||
}
|
||||
registry := ModuleSourceRegistry{
|
||||
Subdir: "bar",
|
||||
@ -363,8 +363,8 @@ func TestModuleSourceRemoteFromRegistry(t *testing.T) {
|
||||
})
|
||||
t.Run("only remote has subdir", func(t *testing.T) {
|
||||
remote := ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("boop"),
|
||||
Subdir: "foo",
|
||||
Package: ModulePackage("boop"),
|
||||
Subdir: "foo",
|
||||
}
|
||||
registry := ModuleSourceRegistry{
|
||||
Subdir: "",
|
||||
@ -382,8 +382,8 @@ func TestModuleSourceRemoteFromRegistry(t *testing.T) {
|
||||
})
|
||||
t.Run("only registry has subdir", func(t *testing.T) {
|
||||
remote := ModuleSourceRemote{
|
||||
PackageAddr: ModulePackage("boop"),
|
||||
Subdir: "",
|
||||
Package: ModulePackage("boop"),
|
||||
Subdir: "",
|
||||
}
|
||||
registry := ModuleSourceRegistry{
|
||||
Subdir: "bar",
|
||||
@ -565,7 +565,7 @@ func TestParseModuleSourceRegistry(t *testing.T) {
|
||||
if got, want := addr.ForDisplay(), test.wantForDisplay; got != want {
|
||||
t.Errorf("wrong ForDisplay() result\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
if got, want := addr.PackageAddr.ForRegistryProtocol(), test.wantForProtocol; got != want {
|
||||
if got, want := addr.Package.ForRegistryProtocol(), test.wantForProtocol; got != want {
|
||||
t.Errorf("wrong ForRegistryProtocol() result\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
})
|
||||
|
@ -55,3 +55,42 @@ func TestModuleEqual_false(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleString(t *testing.T) {
|
||||
testCases := map[string]Module{
|
||||
"": {},
|
||||
"module.alpha": {
|
||||
"alpha",
|
||||
},
|
||||
"module.alpha.module.beta": {
|
||||
"alpha",
|
||||
"beta",
|
||||
},
|
||||
"module.alpha.module.beta.module.charlie": {
|
||||
"alpha",
|
||||
"beta",
|
||||
"charlie",
|
||||
},
|
||||
}
|
||||
for str, module := range testCases {
|
||||
t.Run(str, func(t *testing.T) {
|
||||
if got, want := module.String(), str; got != want {
|
||||
t.Errorf("wrong result: got %q, want %q", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkModuleStringShort(b *testing.B) {
|
||||
module := Module{"a", "b"}
|
||||
for n := 0; n < b.N; n++ {
|
||||
module.String()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkModuleStringLong(b *testing.B) {
|
||||
module := Module{"southamerica-brazil-region", "user-regional-desktop", "user-name"}
|
||||
for n := 0; n < b.N; n++ {
|
||||
module.String()
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,9 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
// anyKeyImpl is the InstanceKey representation indicating a wildcard, which
|
||||
@ -179,8 +180,7 @@ func (e *MoveEndpointInModule) InModuleInstance(modInst ModuleInstance) AbsMovea
|
||||
// while selecting a particular object to move.
|
||||
//
|
||||
// This is a rather special-purpose function here mainly to support our
|
||||
// validation rule that a module can only traverse down into child modules
|
||||
// that belong to the same module package.
|
||||
// validation rule that a module can only traverse down into child modules.
|
||||
func (e *MoveEndpointInModule) ModuleCallTraversals() (Module, []ModuleCall) {
|
||||
// We're returning []ModuleCall rather than Module here to make it clearer
|
||||
// that this is a relative sequence of calls rather than an absolute
|
||||
|
@ -1,32 +1,24 @@
|
||||
package addrs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/idna"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
tfaddr "github.com/hashicorp/terraform-registry-address"
|
||||
svchost "github.com/hashicorp/terraform-svchost"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
// Provider encapsulates a single provider type. In the future this will be
|
||||
// extended to include additional fields including Namespace and SourceHost
|
||||
type Provider struct {
|
||||
Type string
|
||||
Namespace string
|
||||
Hostname svchost.Hostname
|
||||
}
|
||||
type Provider = tfaddr.Provider
|
||||
|
||||
// DefaultProviderRegistryHost is the hostname used for provider addresses that do
|
||||
// not have an explicit hostname.
|
||||
const DefaultProviderRegistryHost = svchost.Hostname("registry.terraform.io")
|
||||
const DefaultProviderRegistryHost = tfaddr.DefaultProviderRegistryHost
|
||||
|
||||
// BuiltInProviderHost is the pseudo-hostname used for the "built-in" provider
|
||||
// namespace. Built-in provider addresses must also have their namespace set
|
||||
// to BuiltInProviderNamespace in order to be considered as built-in.
|
||||
const BuiltInProviderHost = svchost.Hostname("terraform.io")
|
||||
const BuiltInProviderHost = tfaddr.BuiltInProviderHost
|
||||
|
||||
// BuiltInProviderNamespace is the provider namespace used for "built-in"
|
||||
// providers. Built-in provider addresses must also have their hostname
|
||||
@ -35,34 +27,17 @@ const BuiltInProviderHost = svchost.Hostname("terraform.io")
|
||||
// The this namespace is literally named "builtin", in the hope that users
|
||||
// who see FQNs containing this will be able to infer the way in which they are
|
||||
// special, even if they haven't encountered the concept formally yet.
|
||||
const BuiltInProviderNamespace = "builtin"
|
||||
const BuiltInProviderNamespace = tfaddr.BuiltInProviderNamespace
|
||||
|
||||
// LegacyProviderNamespace is the special string used in the Namespace field
|
||||
// of type Provider to mark a legacy provider address. This special namespace
|
||||
// value would normally be invalid, and can be used only when the hostname is
|
||||
// DefaultRegistryHost because that host owns the mapping from legacy name to
|
||||
// FQN.
|
||||
const LegacyProviderNamespace = "-"
|
||||
const LegacyProviderNamespace = tfaddr.LegacyProviderNamespace
|
||||
|
||||
// String returns an FQN string, indended for use in machine-readable output.
|
||||
func (pt Provider) String() string {
|
||||
if pt.IsZero() {
|
||||
panic("called String on zero-value addrs.Provider")
|
||||
}
|
||||
return pt.Hostname.ForDisplay() + "/" + pt.Namespace + "/" + pt.Type
|
||||
}
|
||||
|
||||
// ForDisplay returns a user-friendly FQN string, simplified for readability. If
|
||||
// the provider is using the default hostname, the hostname is omitted.
|
||||
func (pt Provider) ForDisplay() string {
|
||||
if pt.IsZero() {
|
||||
panic("called ForDisplay on zero-value addrs.Provider")
|
||||
}
|
||||
|
||||
if pt.Hostname == DefaultProviderRegistryHost {
|
||||
return pt.Namespace + "/" + pt.Type
|
||||
}
|
||||
return pt.Hostname.ForDisplay() + "/" + pt.Namespace + "/" + pt.Type
|
||||
func IsDefaultProvider(addr Provider) bool {
|
||||
return addr.Hostname == DefaultProviderRegistryHost && addr.Namespace == "hashicorp"
|
||||
}
|
||||
|
||||
// NewProvider constructs a provider address from its parts, and normalizes
|
||||
@ -77,18 +52,7 @@ func (pt Provider) ForDisplay() string {
|
||||
// When accepting namespace or type values from outside the program, use
|
||||
// ParseProviderPart first to check that the given value is valid.
|
||||
func NewProvider(hostname svchost.Hostname, namespace, typeName string) Provider {
|
||||
if namespace == LegacyProviderNamespace {
|
||||
// Legacy provider addresses must always be created via
|
||||
// NewLegacyProvider so that we can use static analysis to find
|
||||
// codepaths still working with those.
|
||||
panic("attempt to create legacy provider address using NewProvider; use NewLegacyProvider instead")
|
||||
}
|
||||
|
||||
return Provider{
|
||||
Type: MustParseProviderPart(typeName),
|
||||
Namespace: MustParseProviderPart(namespace),
|
||||
Hostname: hostname,
|
||||
}
|
||||
return tfaddr.NewProvider(hostname, namespace, typeName)
|
||||
}
|
||||
|
||||
// ImpliedProviderForUnqualifiedType represents the rules for inferring what
|
||||
@ -118,7 +82,7 @@ func ImpliedProviderForUnqualifiedType(typeName string) Provider {
|
||||
// NewDefaultProvider returns the default address of a HashiCorp-maintained,
|
||||
// Registry-hosted provider.
|
||||
func NewDefaultProvider(name string) Provider {
|
||||
return Provider{
|
||||
return tfaddr.Provider{
|
||||
Type: MustParseProviderPart(name),
|
||||
Namespace: "hashicorp",
|
||||
Hostname: DefaultProviderRegistryHost,
|
||||
@ -128,7 +92,7 @@ func NewDefaultProvider(name string) Provider {
|
||||
// NewBuiltInProvider returns the address of a "built-in" provider. See
|
||||
// the docs for Provider.IsBuiltIn for more information.
|
||||
func NewBuiltInProvider(name string) Provider {
|
||||
return Provider{
|
||||
return tfaddr.Provider{
|
||||
Type: MustParseProviderPart(name),
|
||||
Namespace: BuiltInProviderNamespace,
|
||||
Hostname: BuiltInProviderHost,
|
||||
@ -148,80 +112,6 @@ func NewLegacyProvider(name string) Provider {
|
||||
}
|
||||
}
|
||||
|
||||
// LegacyString returns the provider type, which is frequently used
|
||||
// interchangeably with provider name. This function can and should be removed
|
||||
// when provider type is fully integrated. As a safeguard for future
|
||||
// refactoring, this function panics if the Provider is not a legacy provider.
|
||||
func (pt Provider) LegacyString() string {
|
||||
if pt.IsZero() {
|
||||
panic("called LegacyString on zero-value addrs.Provider")
|
||||
}
|
||||
if pt.Namespace != LegacyProviderNamespace && pt.Namespace != BuiltInProviderNamespace {
|
||||
panic(pt.String() + " cannot be represented as a legacy string")
|
||||
}
|
||||
return pt.Type
|
||||
}
|
||||
|
||||
// IsZero returns true if the receiver is the zero value of addrs.Provider.
|
||||
//
|
||||
// The zero value is not a valid addrs.Provider and calling other methods on
|
||||
// such a value is likely to either panic or otherwise misbehave.
|
||||
func (pt Provider) IsZero() bool {
|
||||
return pt == Provider{}
|
||||
}
|
||||
|
||||
// IsBuiltIn returns true if the receiver is the address of a "built-in"
|
||||
// provider. That is, a provider under terraform.io/builtin/ which is
|
||||
// included as part of the Terraform binary itself rather than one to be
|
||||
// installed from elsewhere.
|
||||
//
|
||||
// These are ignored by the provider installer because they are assumed to
|
||||
// already be available without any further installation.
|
||||
func (pt Provider) IsBuiltIn() bool {
|
||||
return pt.Hostname == BuiltInProviderHost && pt.Namespace == BuiltInProviderNamespace
|
||||
}
|
||||
|
||||
// LessThan returns true if the receiver should sort before the other given
|
||||
// address in an ordered list of provider addresses.
|
||||
//
|
||||
// This ordering is an arbitrary one just to allow deterministic results from
|
||||
// functions that would otherwise have no natural ordering. It's subject
|
||||
// to change in future.
|
||||
func (pt Provider) LessThan(other Provider) bool {
|
||||
switch {
|
||||
case pt.Hostname != other.Hostname:
|
||||
return pt.Hostname < other.Hostname
|
||||
case pt.Namespace != other.Namespace:
|
||||
return pt.Namespace < other.Namespace
|
||||
default:
|
||||
return pt.Type < other.Type
|
||||
}
|
||||
}
|
||||
|
||||
// IsLegacy returns true if the provider is a legacy-style provider
|
||||
func (pt Provider) IsLegacy() bool {
|
||||
if pt.IsZero() {
|
||||
panic("called IsLegacy() on zero-value addrs.Provider")
|
||||
}
|
||||
|
||||
return pt.Hostname == DefaultProviderRegistryHost && pt.Namespace == LegacyProviderNamespace
|
||||
|
||||
}
|
||||
|
||||
// IsDefault returns true if the provider is a default hashicorp provider
|
||||
func (pt Provider) IsDefault() bool {
|
||||
if pt.IsZero() {
|
||||
panic("called IsDefault() on zero-value addrs.Provider")
|
||||
}
|
||||
|
||||
return pt.Hostname == DefaultProviderRegistryHost && pt.Namespace == "hashicorp"
|
||||
}
|
||||
|
||||
// Equals returns true if the receiver and other provider have the same attributes.
|
||||
func (pt Provider) Equals(other Provider) bool {
|
||||
return pt == other
|
||||
}
|
||||
|
||||
// ParseProviderSourceString parses the source attribute and returns a provider.
|
||||
// This is intended primarily to parse the FQN-like strings returned by
|
||||
// terraform-config-inspect.
|
||||
@ -230,146 +120,24 @@ func (pt Provider) Equals(other Provider) bool {
|
||||
// name
|
||||
// namespace/name
|
||||
// hostname/namespace/name
|
||||
func ParseProviderSourceString(str string) (Provider, tfdiags.Diagnostics) {
|
||||
var ret Provider
|
||||
func ParseProviderSourceString(str string) (tfaddr.Provider, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
// split the source string into individual components
|
||||
parts := strings.Split(str, "/")
|
||||
if len(parts) == 0 || len(parts) > 3 {
|
||||
ret, err := tfaddr.ParseProviderSource(str)
|
||||
if pe, ok := err.(*tfaddr.ParserError); ok {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid provider source string",
|
||||
Detail: `The "source" attribute must be in the format "[hostname/][namespace/]name"`,
|
||||
Summary: pe.Summary,
|
||||
Detail: pe.Detail,
|
||||
})
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
// check for an invalid empty string in any part
|
||||
for i := range parts {
|
||||
if parts[i] == "" {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid provider source string",
|
||||
Detail: `The "source" attribute must be in the format "[hostname/][namespace/]name"`,
|
||||
})
|
||||
return ret, diags
|
||||
}
|
||||
if !ret.HasKnownNamespace() {
|
||||
ret.Namespace = "hashicorp"
|
||||
}
|
||||
|
||||
// check the 'name' portion, which is always the last part
|
||||
givenName := parts[len(parts)-1]
|
||||
name, err := ParseProviderPart(givenName)
|
||||
if err != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid provider type",
|
||||
Detail: fmt.Sprintf(`Invalid provider type %q in source %q: %s"`, givenName, str, err),
|
||||
})
|
||||
return ret, diags
|
||||
}
|
||||
ret.Type = name
|
||||
ret.Hostname = DefaultProviderRegistryHost
|
||||
|
||||
if len(parts) == 1 {
|
||||
return NewDefaultProvider(parts[0]), diags
|
||||
}
|
||||
|
||||
if len(parts) >= 2 {
|
||||
// the namespace is always the second-to-last part
|
||||
givenNamespace := parts[len(parts)-2]
|
||||
if givenNamespace == LegacyProviderNamespace {
|
||||
// For now we're tolerating legacy provider addresses until we've
|
||||
// finished updating the rest of the codebase to no longer use them,
|
||||
// or else we'd get errors round-tripping through legacy subsystems.
|
||||
ret.Namespace = LegacyProviderNamespace
|
||||
} else {
|
||||
namespace, err := ParseProviderPart(givenNamespace)
|
||||
if err != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid provider namespace",
|
||||
Detail: fmt.Sprintf(`Invalid provider namespace %q in source %q: %s"`, namespace, str, err),
|
||||
})
|
||||
return Provider{}, diags
|
||||
}
|
||||
ret.Namespace = namespace
|
||||
}
|
||||
}
|
||||
|
||||
// Final Case: 3 parts
|
||||
if len(parts) == 3 {
|
||||
// the namespace is always the first part in a three-part source string
|
||||
hn, err := svchost.ForComparison(parts[0])
|
||||
if err != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid provider source hostname",
|
||||
Detail: fmt.Sprintf(`Invalid provider source hostname namespace %q in source %q: %s"`, hn, str, err),
|
||||
})
|
||||
return Provider{}, diags
|
||||
}
|
||||
ret.Hostname = hn
|
||||
}
|
||||
|
||||
if ret.Namespace == LegacyProviderNamespace && ret.Hostname != DefaultProviderRegistryHost {
|
||||
// Legacy provider addresses must always be on the default registry
|
||||
// host, because the default registry host decides what actual FQN
|
||||
// each one maps to.
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid provider namespace",
|
||||
Detail: "The legacy provider namespace \"-\" can be used only with hostname " + DefaultProviderRegistryHost.ForDisplay() + ".",
|
||||
})
|
||||
return Provider{}, diags
|
||||
}
|
||||
|
||||
// Due to how plugin executables are named and provider git repositories
|
||||
// are conventionally named, it's a reasonable and
|
||||
// apparently-somewhat-common user error to incorrectly use the
|
||||
// "terraform-provider-" prefix in a provider source address. There is
|
||||
// no good reason for a provider to have the prefix "terraform-" anyway,
|
||||
// so we've made that invalid from the start both so we can give feedback
|
||||
// to provider developers about the terraform- prefix being redundant
|
||||
// and give specialized feedback to folks who incorrectly use the full
|
||||
// terraform-provider- prefix to help them self-correct.
|
||||
const redundantPrefix = "terraform-"
|
||||
const userErrorPrefix = "terraform-provider-"
|
||||
if strings.HasPrefix(ret.Type, redundantPrefix) {
|
||||
if strings.HasPrefix(ret.Type, userErrorPrefix) {
|
||||
// Likely user error. We only return this specialized error if
|
||||
// whatever is after the prefix would otherwise be a
|
||||
// syntactically-valid provider type, so we don't end up advising
|
||||
// the user to try something that would be invalid for another
|
||||
// reason anyway.
|
||||
// (This is mainly just for robustness, because the validation
|
||||
// we already did above should've rejected most/all ways for
|
||||
// the suggestedType to end up invalid here.)
|
||||
suggestedType := ret.Type[len(userErrorPrefix):]
|
||||
if _, err := ParseProviderPart(suggestedType); err == nil {
|
||||
suggestedAddr := ret
|
||||
suggestedAddr.Type = suggestedType
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid provider type",
|
||||
fmt.Sprintf("Provider source %q has a type with the prefix %q, which isn't valid. Although that prefix is often used in the names of version control repositories for Terraform providers, provider source strings should not include it.\n\nDid you mean %q?", ret.ForDisplay(), userErrorPrefix, suggestedAddr.ForDisplay()),
|
||||
))
|
||||
return Provider{}, diags
|
||||
}
|
||||
}
|
||||
// Otherwise, probably instead an incorrectly-named provider, perhaps
|
||||
// arising from a similar instinct to what causes there to be
|
||||
// thousands of Python packages on PyPI with "python-"-prefixed
|
||||
// names.
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid provider type",
|
||||
fmt.Sprintf("Provider source %q has a type with the prefix %q, which isn't allowed because it would be redundant to name a Terraform provider with that prefix. If you are the author of this provider, rename it to not include the prefix.", ret, redundantPrefix),
|
||||
))
|
||||
return Provider{}, diags
|
||||
}
|
||||
|
||||
return ret, diags
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// MustParseProviderSourceString is a wrapper around ParseProviderSourceString that panics if
|
||||
@ -409,36 +177,7 @@ func MustParseProviderSourceString(str string) Provider {
|
||||
// It's valid to pass the result of this function as the argument to a
|
||||
// subsequent call, in which case the result will be identical.
|
||||
func ParseProviderPart(given string) (string, error) {
|
||||
if len(given) == 0 {
|
||||
return "", fmt.Errorf("must have at least one character")
|
||||
}
|
||||
|
||||
// We're going to process the given name using the same "IDNA" library we
|
||||
// use for the hostname portion, since it already implements the case
|
||||
// folding rules we want.
|
||||
//
|
||||
// The idna library doesn't expose individual label parsing directly, but
|
||||
// once we've verified it doesn't contain any dots we can just treat it
|
||||
// like a top-level domain for this library's purposes.
|
||||
if strings.ContainsRune(given, '.') {
|
||||
return "", fmt.Errorf("dots are not allowed")
|
||||
}
|
||||
|
||||
// We don't allow names containing multiple consecutive dashes, just as
|
||||
// a matter of preference: they look weird, confusing, or incorrect.
|
||||
// This also, as a side-effect, prevents the use of the "punycode"
|
||||
// indicator prefix "xn--" that would cause the IDNA library to interpret
|
||||
// the given name as punycode, because that would be weird and unexpected.
|
||||
if strings.Contains(given, "--") {
|
||||
return "", fmt.Errorf("cannot use multiple consecutive dashes")
|
||||
}
|
||||
|
||||
result, err := idna.Lookup.ToUnicode(given)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("must contain only letters, digits, and dashes, and may not use leading or trailing dashes")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
return tfaddr.ParseProviderPart(given)
|
||||
}
|
||||
|
||||
// MustParseProviderPart is a wrapper around ParseProviderPart that panics if
|
||||
|
@ -124,7 +124,7 @@ func TestProviderDisplay(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderIsDefault(t *testing.T) {
|
||||
func TestProviderIsDefaultProvider(t *testing.T) {
|
||||
tests := []struct {
|
||||
Input Provider
|
||||
Want bool
|
||||
@ -156,7 +156,7 @@ func TestProviderIsDefault(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got := test.Input.IsDefault()
|
||||
got := IsDefaultProvider(test.Input)
|
||||
if got != test.Want {
|
||||
t.Errorf("wrong result for %s\n", test.Input.String())
|
||||
}
|
||||
|
@ -1,23 +1,37 @@
|
||||
package addrs
|
||||
|
||||
// Set represents a set of addresses of types that implement UniqueKeyer.
|
||||
type Set map[UniqueKey]UniqueKeyer
|
||||
//
|
||||
// Modify the set only by the methods on this type. This type exposes its
|
||||
// internals for convenience during reading, such as iterating over set elements
|
||||
// by ranging over the map values, but making direct modifications could
|
||||
// potentially make the set data invalid or inconsistent, leading to undefined
|
||||
// behavior elsewhere.
|
||||
type Set[T UniqueKeyer] map[UniqueKey]T
|
||||
|
||||
func (s Set) Has(addr UniqueKeyer) bool {
|
||||
// Has returns true if and only if the set includes the given address.
|
||||
func (s Set[T]) Has(addr T) bool {
|
||||
_, exists := s[addr.UniqueKey()]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (s Set) Add(addr UniqueKeyer) {
|
||||
// Add inserts the given address into the set, if not already present. If
|
||||
// an equivalent address is already in the set, this replaces that address
|
||||
// with the new value.
|
||||
func (s Set[T]) Add(addr T) {
|
||||
s[addr.UniqueKey()] = addr
|
||||
}
|
||||
|
||||
func (s Set) Remove(addr UniqueKeyer) {
|
||||
// Remove deletes the given address from the set, if present. If not present,
|
||||
// this is a no-op.
|
||||
func (s Set[T]) Remove(addr T) {
|
||||
delete(s, addr.UniqueKey())
|
||||
}
|
||||
|
||||
func (s Set) Union(other Set) Set {
|
||||
ret := make(Set)
|
||||
// Union returns a new set which contains the union of all of the elements
|
||||
// of both the reciever and the given other set.
|
||||
func (s Set[T]) Union(other Set[T]) Set[T] {
|
||||
ret := make(Set[T])
|
||||
for k, addr := range s {
|
||||
ret[k] = addr
|
||||
}
|
||||
@ -27,8 +41,10 @@ func (s Set) Union(other Set) Set {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s Set) Intersection(other Set) Set {
|
||||
ret := make(Set)
|
||||
// Intersection returns a new set which contains the intersection of all of the
|
||||
// elements of both the reciever and the given other set.
|
||||
func (s Set[T]) Intersection(other Set[T]) Set[T] {
|
||||
ret := make(Set[T])
|
||||
for k, addr := range s {
|
||||
if _, exists := other[k]; exists {
|
||||
ret[k] = addr
|
||||
|
@ -21,3 +21,7 @@ type UniqueKey interface {
|
||||
type UniqueKeyer interface {
|
||||
UniqueKey() UniqueKey
|
||||
}
|
||||
|
||||
func Equivalent[T UniqueKeyer](a, b T) bool {
|
||||
return a.UniqueKey() == b.UniqueKey()
|
||||
}
|
||||
|
@ -16,8 +16,6 @@ import (
|
||||
backendAzure "github.com/hashicorp/terraform/internal/backend/remote-state/azure"
|
||||
backendConsul "github.com/hashicorp/terraform/internal/backend/remote-state/consul"
|
||||
backendCos "github.com/hashicorp/terraform/internal/backend/remote-state/cos"
|
||||
backendEtcdv2 "github.com/hashicorp/terraform/internal/backend/remote-state/etcdv2"
|
||||
backendEtcdv3 "github.com/hashicorp/terraform/internal/backend/remote-state/etcdv3"
|
||||
backendGCS "github.com/hashicorp/terraform/internal/backend/remote-state/gcs"
|
||||
backendHTTP "github.com/hashicorp/terraform/internal/backend/remote-state/http"
|
||||
backendInmem "github.com/hashicorp/terraform/internal/backend/remote-state/inmem"
|
||||
@ -44,6 +42,10 @@ import (
|
||||
var backends map[string]backend.InitFn
|
||||
var backendsLock sync.Mutex
|
||||
|
||||
// RemovedBackends is a record of previously supported backends which have
|
||||
// since been deprecated and removed.
|
||||
var RemovedBackends map[string]string
|
||||
|
||||
// Init initializes the backends map with all our hardcoded backends.
|
||||
func Init(services *disco.Disco) {
|
||||
backendsLock.Lock()
|
||||
@ -54,26 +56,22 @@ func Init(services *disco.Disco) {
|
||||
"remote": func() backend.Backend { return backendRemote.New(services) },
|
||||
|
||||
// Remote State backends.
|
||||
"artifactory": func() backend.Backend { return backendArtifactory.New() },
|
||||
"azurerm": func() backend.Backend { return backendAzure.New() },
|
||||
"consul": func() backend.Backend { return backendConsul.New() },
|
||||
"cos": func() backend.Backend { return backendCos.New() },
|
||||
"etcd": func() backend.Backend { return backendEtcdv2.New() },
|
||||
"etcdv3": func() backend.Backend { return backendEtcdv3.New() },
|
||||
"gcs": func() backend.Backend { return backendGCS.New() },
|
||||
"http": func() backend.Backend { return backendHTTP.New() },
|
||||
"inmem": func() backend.Backend { return backendInmem.New() },
|
||||
"kubernetes": func() backend.Backend { return backendKubernetes.New() },
|
||||
"manta": func() backend.Backend { return backendManta.New() },
|
||||
"oss": func() backend.Backend { return backendOSS.New() },
|
||||
"pg": func() backend.Backend { return backendPg.New() },
|
||||
"s3": func() backend.Backend { return backendS3.New() },
|
||||
"swift": func() backend.Backend { return backendSwift.New() },
|
||||
"azurerm": func() backend.Backend { return backendAzure.New() },
|
||||
"consul": func() backend.Backend { return backendConsul.New() },
|
||||
"cos": func() backend.Backend { return backendCos.New() },
|
||||
"gcs": func() backend.Backend { return backendGCS.New() },
|
||||
"http": func() backend.Backend { return backendHTTP.New() },
|
||||
"inmem": func() backend.Backend { return backendInmem.New() },
|
||||
"kubernetes": func() backend.Backend { return backendKubernetes.New() },
|
||||
"oss": func() backend.Backend { return backendOSS.New() },
|
||||
"pg": func() backend.Backend { return backendPg.New() },
|
||||
"s3": func() backend.Backend { return backendS3.New() },
|
||||
|
||||
// Terraform Cloud 'backend'
|
||||
// This is an implementation detail only, used for the cloud package
|
||||
"cloud": func() backend.Backend { return backendCloud.New(services) },
|
||||
|
||||
// FIXME: remove deprecated backends for v1.3
|
||||
// Deprecated backends.
|
||||
"azure": func() backend.Backend {
|
||||
return deprecateBackend(
|
||||
@ -81,6 +79,29 @@ func Init(services *disco.Disco) {
|
||||
`Warning: "azure" name is deprecated, please use "azurerm"`,
|
||||
)
|
||||
},
|
||||
"artifactory": func() backend.Backend {
|
||||
return deprecateBackend(
|
||||
backendArtifactory.New(),
|
||||
`Warning: "artifactory" backend is deprecated, and will be removed in a future release."`,
|
||||
)
|
||||
},
|
||||
"manta": func() backend.Backend {
|
||||
return deprecateBackend(
|
||||
backendManta.New(),
|
||||
`Warning: "manta" backend is deprecated, and will be removed in a future release."`,
|
||||
)
|
||||
},
|
||||
"swift": func() backend.Backend {
|
||||
return deprecateBackend(
|
||||
backendSwift.New(),
|
||||
`Warning: "swift" backend is deprecated, and will be removed in a future release."`,
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
RemovedBackends = map[string]string{
|
||||
"etcd": `The "etcd" backend is not supported in Terraform v1.3 or later.`,
|
||||
"etcdv3": `The "etcdv3" backend is not supported in Terraform v1.3 or later.`,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,14 +18,15 @@ func TestInit_backend(t *testing.T) {
|
||||
{"azurerm", "*azure.Backend"},
|
||||
{"consul", "*consul.Backend"},
|
||||
{"cos", "*cos.Backend"},
|
||||
{"etcdv3", "*etcd.Backend"},
|
||||
{"gcs", "*gcs.Backend"},
|
||||
{"inmem", "*inmem.Backend"},
|
||||
{"manta", "*manta.Backend"},
|
||||
{"pg", "*pg.Backend"},
|
||||
{"s3", "*s3.Backend"},
|
||||
{"swift", "*swift.Backend"},
|
||||
|
||||
{"azure", "init.deprecatedBackendShim"},
|
||||
{"artifactory", "init.deprecatedBackendShim"},
|
||||
{"manta", "init.deprecatedBackendShim"},
|
||||
{"swift", "init.deprecatedBackendShim"},
|
||||
}
|
||||
|
||||
// Make sure we get the requested backend
|
||||
|
@ -6,6 +6,8 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/command/arguments"
|
||||
"github.com/hashicorp/terraform/internal/command/clistate"
|
||||
@ -20,7 +22,6 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
"github.com/hashicorp/terraform/internal/terminal"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestLocalRun(t *testing.T) {
|
||||
@ -220,6 +221,10 @@ func (s *stateStorageThatFailsRefresh) State() *states.State {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stateStorageThatFailsRefresh) GetRootOutputValues() (map[string]*states.OutputValue, error) {
|
||||
return nil, fmt.Errorf("unimplemented")
|
||||
}
|
||||
|
||||
func (s *stateStorageThatFailsRefresh) WriteState(*states.State) error {
|
||||
return fmt.Errorf("unimplemented")
|
||||
}
|
||||
|
@ -62,7 +62,14 @@ func TestLocalProvider(t *testing.T, b *Local, name string, schema *terraform.Pr
|
||||
p.GetProviderSchemaResponse.DataSources[name] = providers.Schema{Block: dat}
|
||||
}
|
||||
|
||||
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
||||
// this is a destroy plan,
|
||||
if req.ProposedNewState.IsNull() {
|
||||
resp.PlannedState = req.ProposedNewState
|
||||
resp.PlannedPrivate = req.PriorPrivate
|
||||
return resp
|
||||
}
|
||||
|
||||
rSchema, _ := schema.SchemaForResourceType(addrs.ManagedResourceMode, req.TypeName)
|
||||
if rSchema == nil {
|
||||
rSchema = &configschema.Block{} // default schema is empty
|
||||
|
@ -7,11 +7,6 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/manicminer/hamilton/environments"
|
||||
|
||||
"github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs"
|
||||
"github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources"
|
||||
armStorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2021-01-01/storage"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
@ -19,6 +14,10 @@ import (
|
||||
"github.com/hashicorp/go-azure-helpers/authentication"
|
||||
"github.com/hashicorp/go-azure-helpers/sender"
|
||||
"github.com/hashicorp/terraform/internal/httpclient"
|
||||
"github.com/hashicorp/terraform/version"
|
||||
"github.com/manicminer/hamilton/environments"
|
||||
"github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs"
|
||||
"github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/containers"
|
||||
)
|
||||
|
||||
type ArmClient struct {
|
||||
@ -91,7 +90,7 @@ func buildArmClient(ctx context.Context, config BackendConfig) (*ArmClient, erro
|
||||
SupportsClientSecretAuth: true,
|
||||
SupportsManagedServiceIdentity: config.UseMsi,
|
||||
SupportsOIDCAuth: config.UseOIDC,
|
||||
UseMicrosoftGraph: config.UseMicrosoftGraph,
|
||||
UseMicrosoftGraph: true,
|
||||
}
|
||||
armConfig, err := builder.Build()
|
||||
if err != nil {
|
||||
@ -109,37 +108,19 @@ func buildArmClient(ctx context.Context, config BackendConfig) (*ArmClient, erro
|
||||
}
|
||||
|
||||
sender := sender.BuildSender("backend/remote-state/azure")
|
||||
var auth autorest.Authorizer
|
||||
if builder.UseMicrosoftGraph {
|
||||
log.Printf("[DEBUG] Obtaining an MSAL / Microsoft Graph token for Resource Manager..")
|
||||
auth, err = armConfig.GetMSALToken(ctx, hamiltonEnv.ResourceManager, sender, oauthConfig, env.TokenAudience)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
log.Printf("[DEBUG] Obtaining an ADAL / Azure Active Directory Graph token for Resource Manager..")
|
||||
auth, err = armConfig.GetADALToken(ctx, sender, oauthConfig, env.TokenAudience)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("[DEBUG] Obtaining an MSAL / Microsoft Graph token for Resource Manager..")
|
||||
auth, err := armConfig.GetMSALToken(ctx, hamiltonEnv.ResourceManager, sender, oauthConfig, env.TokenAudience)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.UseAzureADAuthentication {
|
||||
if builder.UseMicrosoftGraph {
|
||||
log.Printf("[DEBUG] Obtaining an MSAL / Microsoft Graph token for Storage..")
|
||||
storageAuth, err := armConfig.GetMSALToken(ctx, hamiltonEnv.Storage, sender, oauthConfig, env.ResourceIdentifiers.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.azureAdStorageAuth = &storageAuth
|
||||
} else {
|
||||
log.Printf("[DEBUG] Obtaining an ADAL / Azure Active Directory Graph token for Storage..")
|
||||
storageAuth, err := armConfig.GetADALToken(ctx, sender, oauthConfig, env.ResourceIdentifiers.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.azureAdStorageAuth = &storageAuth
|
||||
log.Printf("[DEBUG] Obtaining an MSAL / Microsoft Graph token for Storage..")
|
||||
storageAuth, err := armConfig.GetMSALToken(ctx, hamiltonEnv.Storage, sender, oauthConfig, env.ResourceIdentifiers.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.azureAdStorageAuth = &storageAuth
|
||||
}
|
||||
|
||||
accountsClient := armStorage.NewAccountsClientWithBaseURI(env.ResourceManagerEndpoint, armConfig.SubscriptionID)
|
||||
@ -252,7 +233,7 @@ func (c *ArmClient) configureClient(client *autorest.Client, auth autorest.Autho
|
||||
}
|
||||
|
||||
func buildUserAgent() string {
|
||||
userAgent := httpclient.UserAgentString()
|
||||
userAgent := httpclient.TerraformUserAgent(version.Version)
|
||||
|
||||
// append the CloudShell version to the user agent if it exists
|
||||
if azureAgent := os.Getenv("AZURE_HTTP_USER_AGENT"); azureAgent != "" {
|
||||
|
@ -164,13 +164,6 @@ func New() backend.Backend {
|
||||
Description: "Should Terraform use AzureAD Authentication to access the Blob?",
|
||||
DefaultFunc: schema.EnvDefaultFunc("ARM_USE_AZUREAD", false),
|
||||
},
|
||||
"use_microsoft_graph": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Deprecated: "This field now defaults to `true` and will be removed in v1.3 of Terraform Core due to the deprecation of ADAL by Microsoft.",
|
||||
Description: "Should Terraform obtain an MSAL auth token and use Microsoft Graph rather than Azure Active Directory?",
|
||||
DefaultFunc: schema.EnvDefaultFunc("ARM_USE_MSGRAPH", true),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -213,7 +206,6 @@ type BackendConfig struct {
|
||||
UseMsi bool
|
||||
UseOIDC bool
|
||||
UseAzureADAuthentication bool
|
||||
UseMicrosoftGraph bool
|
||||
}
|
||||
|
||||
func (b *Backend) configure(ctx context.Context) error {
|
||||
@ -248,7 +240,6 @@ func (b *Backend) configure(ctx context.Context) error {
|
||||
UseMsi: data.Get("use_msi").(bool),
|
||||
UseOIDC: data.Get("use_oidc").(bool),
|
||||
UseAzureADAuthentication: data.Get("use_azuread_auth").(bool),
|
||||
UseMicrosoftGraph: data.Get("use_microsoft_graph").(bool),
|
||||
}
|
||||
|
||||
armClient, err := buildArmClient(context.TODO(), config)
|
||||
|
@ -123,7 +123,7 @@ func TestAccBackendOIDCBasic(t *testing.T) {
|
||||
backend.TestBackendStates(t, b)
|
||||
}
|
||||
|
||||
func TestAccBackendADALAzureADAuthBasic(t *testing.T) {
|
||||
func TestAccBackendAzureADAuthBasic(t *testing.T) {
|
||||
testAccAzureBackend(t)
|
||||
rs := acctest.RandString(4)
|
||||
res := testResourceNames(rs, "testState")
|
||||
@ -151,7 +151,7 @@ func TestAccBackendADALAzureADAuthBasic(t *testing.T) {
|
||||
backend.TestBackendStates(t, b)
|
||||
}
|
||||
|
||||
func TestAccBackendADALManagedServiceIdentityBasic(t *testing.T) {
|
||||
func TestAccBackendManagedServiceIdentityBasic(t *testing.T) {
|
||||
testAccAzureBackendRunningInAzure(t)
|
||||
rs := acctest.RandString(4)
|
||||
res := testResourceNames(rs, "testState")
|
||||
@ -179,7 +179,7 @@ func TestAccBackendADALManagedServiceIdentityBasic(t *testing.T) {
|
||||
backend.TestBackendStates(t, b)
|
||||
}
|
||||
|
||||
func TestAccBackendADALServicePrincipalClientCertificateBasic(t *testing.T) {
|
||||
func TestAccBackendServicePrincipalClientCertificateBasic(t *testing.T) {
|
||||
testAccAzureBackend(t)
|
||||
|
||||
clientCertPassword := os.Getenv("ARM_CLIENT_CERTIFICATE_PASSWORD")
|
||||
@ -216,7 +216,7 @@ func TestAccBackendADALServicePrincipalClientCertificateBasic(t *testing.T) {
|
||||
backend.TestBackendStates(t, b)
|
||||
}
|
||||
|
||||
func TestAccBackendADALServicePrincipalClientSecretBasic(t *testing.T) {
|
||||
func TestAccBackendServicePrincipalClientSecretBasic(t *testing.T) {
|
||||
testAccAzureBackend(t)
|
||||
rs := acctest.RandString(4)
|
||||
res := testResourceNames(rs, "testState")
|
||||
@ -245,7 +245,7 @@ func TestAccBackendADALServicePrincipalClientSecretBasic(t *testing.T) {
|
||||
backend.TestBackendStates(t, b)
|
||||
}
|
||||
|
||||
func TestAccBackendADALServicePrincipalClientSecretCustomEndpoint(t *testing.T) {
|
||||
func TestAccBackendServicePrincipalClientSecretCustomEndpoint(t *testing.T) {
|
||||
testAccAzureBackend(t)
|
||||
|
||||
// this is only applicable for Azure Stack.
|
||||
@ -281,169 +281,6 @@ func TestAccBackendADALServicePrincipalClientSecretCustomEndpoint(t *testing.T)
|
||||
backend.TestBackendStates(t, b)
|
||||
}
|
||||
|
||||
func TestAccBackendMSALAzureADAuthBasic(t *testing.T) {
|
||||
testAccAzureBackend(t)
|
||||
rs := acctest.RandString(4)
|
||||
res := testResourceNames(rs, "testState")
|
||||
res.useAzureADAuth = true
|
||||
res.useMicrosoftGraph = true
|
||||
armClient := buildTestClient(t, res)
|
||||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
armClient.destroyTestResources(ctx, res)
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.storageContainerName,
|
||||
"key": res.storageKeyName,
|
||||
"access_key": res.storageAccountAccessKey,
|
||||
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||
"endpoint": os.Getenv("ARM_ENDPOINT"),
|
||||
"use_azuread_auth": true,
|
||||
})).(*Backend)
|
||||
|
||||
backend.TestBackendStates(t, b)
|
||||
}
|
||||
|
||||
func TestAccBackendMSALManagedServiceIdentityBasic(t *testing.T) {
|
||||
testAccAzureBackendRunningInAzure(t)
|
||||
rs := acctest.RandString(4)
|
||||
res := testResourceNames(rs, "testState")
|
||||
res.useMicrosoftGraph = true
|
||||
armClient := buildTestClient(t, res)
|
||||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.storageContainerName,
|
||||
"key": res.storageKeyName,
|
||||
"resource_group_name": res.resourceGroup,
|
||||
"use_msi": true,
|
||||
"subscription_id": os.Getenv("ARM_SUBSCRIPTION_ID"),
|
||||
"tenant_id": os.Getenv("ARM_TENANT_ID"),
|
||||
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||
"endpoint": os.Getenv("ARM_ENDPOINT"),
|
||||
})).(*Backend)
|
||||
|
||||
backend.TestBackendStates(t, b)
|
||||
}
|
||||
|
||||
func TestAccBackendMSALServicePrincipalClientCertificateBasic(t *testing.T) {
|
||||
testAccAzureBackend(t)
|
||||
|
||||
clientCertPassword := os.Getenv("ARM_CLIENT_CERTIFICATE_PASSWORD")
|
||||
clientCertPath := os.Getenv("ARM_CLIENT_CERTIFICATE_PATH")
|
||||
if clientCertPath == "" {
|
||||
t.Skip("Skipping since `ARM_CLIENT_CERTIFICATE_PATH` is not specified!")
|
||||
}
|
||||
|
||||
rs := acctest.RandString(4)
|
||||
res := testResourceNames(rs, "testState")
|
||||
res.useMicrosoftGraph = true
|
||||
armClient := buildTestClient(t, res)
|
||||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.storageContainerName,
|
||||
"key": res.storageKeyName,
|
||||
"resource_group_name": res.resourceGroup,
|
||||
"subscription_id": os.Getenv("ARM_SUBSCRIPTION_ID"),
|
||||
"tenant_id": os.Getenv("ARM_TENANT_ID"),
|
||||
"client_id": os.Getenv("ARM_CLIENT_ID"),
|
||||
"client_certificate_password": clientCertPassword,
|
||||
"client_certificate_path": clientCertPath,
|
||||
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||
"endpoint": os.Getenv("ARM_ENDPOINT"),
|
||||
})).(*Backend)
|
||||
|
||||
backend.TestBackendStates(t, b)
|
||||
}
|
||||
|
||||
func TestAccBackendMSALServicePrincipalClientSecretBasic(t *testing.T) {
|
||||
testAccAzureBackend(t)
|
||||
rs := acctest.RandString(4)
|
||||
res := testResourceNames(rs, "testState")
|
||||
res.useMicrosoftGraph = true
|
||||
armClient := buildTestClient(t, res)
|
||||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.storageContainerName,
|
||||
"key": res.storageKeyName,
|
||||
"resource_group_name": res.resourceGroup,
|
||||
"subscription_id": os.Getenv("ARM_SUBSCRIPTION_ID"),
|
||||
"tenant_id": os.Getenv("ARM_TENANT_ID"),
|
||||
"client_id": os.Getenv("ARM_CLIENT_ID"),
|
||||
"client_secret": os.Getenv("ARM_CLIENT_SECRET"),
|
||||
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||
"endpoint": os.Getenv("ARM_ENDPOINT"),
|
||||
})).(*Backend)
|
||||
|
||||
backend.TestBackendStates(t, b)
|
||||
}
|
||||
|
||||
func TestAccBackendMSALServicePrincipalClientSecretCustomEndpoint(t *testing.T) {
|
||||
testAccAzureBackend(t)
|
||||
|
||||
// this is only applicable for Azure Stack.
|
||||
endpoint := os.Getenv("ARM_ENDPOINT")
|
||||
if endpoint == "" {
|
||||
t.Skip("Skipping as ARM_ENDPOINT isn't configured")
|
||||
}
|
||||
|
||||
rs := acctest.RandString(4)
|
||||
res := testResourceNames(rs, "testState")
|
||||
res.useMicrosoftGraph = true
|
||||
armClient := buildTestClient(t, res)
|
||||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.storageContainerName,
|
||||
"key": res.storageKeyName,
|
||||
"resource_group_name": res.resourceGroup,
|
||||
"subscription_id": os.Getenv("ARM_SUBSCRIPTION_ID"),
|
||||
"tenant_id": os.Getenv("ARM_TENANT_ID"),
|
||||
"client_id": os.Getenv("ARM_CLIENT_ID"),
|
||||
"client_secret": os.Getenv("ARM_CLIENT_SECRET"),
|
||||
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||
"endpoint": endpoint,
|
||||
})).(*Backend)
|
||||
|
||||
backend.TestBackendStates(t, b)
|
||||
}
|
||||
|
||||
func TestAccBackendAccessKeyLocked(t *testing.T) {
|
||||
testAccAzureBackend(t)
|
||||
rs := acctest.RandString(4)
|
||||
|
@ -10,10 +10,9 @@ import (
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/states/remote"
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
"github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -93,7 +93,6 @@ func buildTestClient(t *testing.T, res resourceNames) *ArmClient {
|
||||
StorageAccountName: res.storageAccountName,
|
||||
UseMsi: msiEnabled,
|
||||
UseAzureADAuthentication: res.useAzureADAuth,
|
||||
UseMicrosoftGraph: res.useMicrosoftGraph,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to build ArmClient: %+v", err)
|
||||
@ -137,7 +136,6 @@ type resourceNames struct {
|
||||
storageKeyName string
|
||||
storageAccountAccessKey string
|
||||
useAzureADAuth bool
|
||||
useMicrosoftGraph bool
|
||||
}
|
||||
|
||||
func testResourceNames(rString string, keyName string) resourceNames {
|
||||
|
@ -113,6 +113,12 @@ func New() backend.Backend {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
"accelerate": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Description: "Whether to enable global Acceleration",
|
||||
Default: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -138,7 +144,16 @@ func (b *Backend) configure(ctx context.Context) error {
|
||||
b.encrypt = data.Get("encrypt").(bool)
|
||||
b.acl = data.Get("acl").(string)
|
||||
|
||||
u, err := url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com", b.bucket, b.region))
|
||||
var (
|
||||
u *url.URL
|
||||
err error
|
||||
)
|
||||
accelerate := data.Get("accelerate").(bool)
|
||||
if accelerate {
|
||||
u, err = url.Parse(fmt.Sprintf("https://%s.cos.accelerate.myqcloud.com", b.bucket))
|
||||
} else {
|
||||
u, err = url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com", b.bucket, b.region))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -123,6 +123,11 @@ func (c *remoteClient) Unlock(check string) error {
|
||||
return c.lockError(err)
|
||||
}
|
||||
|
||||
err = c.cosUnlock(c.bucket, c.lockFile)
|
||||
if err != nil {
|
||||
return c.lockError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -362,6 +367,16 @@ func (c *remoteClient) cosUnlock(bucket, cosFile string) error {
|
||||
|
||||
var err error
|
||||
for i := 0; i < 30; i++ {
|
||||
tagExists, err := c.CheckTag(lockTagKey, lockTagValue)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !tagExists {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = c.DeleteTag(lockTagKey, lockTagValue)
|
||||
if err == nil {
|
||||
return nil
|
||||
@ -372,6 +387,30 @@ func (c *remoteClient) cosUnlock(bucket, cosFile string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// CheckTag checks if tag key:value exists
|
||||
func (c *remoteClient) CheckTag(key, value string) (exists bool, err error) {
|
||||
request := tag.NewDescribeTagsRequest()
|
||||
request.TagKey = &key
|
||||
request.TagValue = &value
|
||||
|
||||
response, err := c.tagClient.DescribeTags(request)
|
||||
log.Printf("[DEBUG] create tag %s:%s: error: %v", key, value, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(response.Response.Tags) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
tagKey := response.Response.Tags[0].TagKey
|
||||
tagValue := response.Response.Tags[0].TagValue
|
||||
|
||||
exists = key == *tagKey && value == *tagValue
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CreateTag create tag by key and value
|
||||
func (c *remoteClient) CreateTag(key, value string) error {
|
||||
request := tag.NewCreateTagRequest()
|
||||
|
@ -1,96 +0,0 @@
|
||||
// legacy etcd2.x backend
|
||||
|
||||
package etcdv2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/legacy/helper/schema"
|
||||
"github.com/hashicorp/terraform/internal/states/remote"
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
etcdapi "go.etcd.io/etcd/client"
|
||||
)
|
||||
|
||||
func New() backend.Backend {
|
||||
s := &schema.Backend{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"path": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "The path where to store the state",
|
||||
},
|
||||
"endpoints": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "A space-separated list of the etcd endpoints",
|
||||
},
|
||||
"username": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "Username",
|
||||
},
|
||||
"password": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "Password",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := &Backend{Backend: s}
|
||||
result.Backend.ConfigureFunc = result.configure
|
||||
return result
|
||||
}
|
||||
|
||||
type Backend struct {
|
||||
*schema.Backend
|
||||
|
||||
client etcdapi.Client
|
||||
path string
|
||||
}
|
||||
|
||||
func (b *Backend) configure(ctx context.Context) error {
|
||||
data := schema.FromContextBackendConfig(ctx)
|
||||
|
||||
b.path = data.Get("path").(string)
|
||||
|
||||
endpoints := data.Get("endpoints").(string)
|
||||
username := data.Get("username").(string)
|
||||
password := data.Get("password").(string)
|
||||
|
||||
config := etcdapi.Config{
|
||||
Endpoints: strings.Split(endpoints, " "),
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
client, err := etcdapi.New(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.client = client
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Backend) Workspaces() ([]string, error) {
|
||||
return nil, backend.ErrWorkspacesNotSupported
|
||||
}
|
||||
|
||||
func (b *Backend) DeleteWorkspace(string) error {
|
||||
return backend.ErrWorkspacesNotSupported
|
||||
}
|
||||
|
||||
func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
|
||||
if name != backend.DefaultStateName {
|
||||
return nil, backend.ErrWorkspacesNotSupported
|
||||
}
|
||||
return &remote.State{
|
||||
Client: &EtcdClient{
|
||||
Client: b.client,
|
||||
Path: b.path,
|
||||
},
|
||||
}, nil
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package etcdv2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
)
|
||||
|
||||
func TestBackend_impl(t *testing.T) {
|
||||
var _ backend.Backend = new(Backend)
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package etcdv2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/states/remote"
|
||||
etcdapi "go.etcd.io/etcd/client"
|
||||
)
|
||||
|
||||
// EtcdClient is a remote client that stores data in etcd.
|
||||
type EtcdClient struct {
|
||||
Client etcdapi.Client
|
||||
Path string
|
||||
}
|
||||
|
||||
func (c *EtcdClient) Get() (*remote.Payload, error) {
|
||||
resp, err := etcdapi.NewKeysAPI(c.Client).Get(context.Background(), c.Path, &etcdapi.GetOptions{Quorum: true})
|
||||
if err != nil {
|
||||
if err, ok := err.(etcdapi.Error); ok && err.Code == etcdapi.ErrorCodeKeyNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if resp.Node.Dir {
|
||||
return nil, fmt.Errorf("path is a directory")
|
||||
}
|
||||
|
||||
data := []byte(resp.Node.Value)
|
||||
md5 := md5.Sum(data)
|
||||
return &remote.Payload{
|
||||
Data: data,
|
||||
MD5: md5[:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *EtcdClient) Put(data []byte) error {
|
||||
_, err := etcdapi.NewKeysAPI(c.Client).Set(context.Background(), c.Path, string(data), nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *EtcdClient) Delete() error {
|
||||
_, err := etcdapi.NewKeysAPI(c.Client).Delete(context.Background(), c.Path, nil)
|
||||
return err
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package etcdv2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/states/remote"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestEtcdClient_impl(t *testing.T) {
|
||||
var _ remote.Client = new(EtcdClient)
|
||||
}
|
||||
|
||||
func TestEtcdClient(t *testing.T) {
|
||||
endpoint := os.Getenv("ETCD_ENDPOINT")
|
||||
if endpoint == "" {
|
||||
t.Skipf("skipping; ETCD_ENDPOINT must be set")
|
||||
}
|
||||
|
||||
// Get the backend
|
||||
config := map[string]cty.Value{
|
||||
"endpoints": cty.StringVal(endpoint),
|
||||
"path": cty.StringVal(fmt.Sprintf("tf-unit/%s", time.Now().String())),
|
||||
}
|
||||
|
||||
if username := os.Getenv("ETCD_USERNAME"); username != "" {
|
||||
config["username"] = cty.StringVal(username)
|
||||
}
|
||||
if password := os.Getenv("ETCD_PASSWORD"); password != "" {
|
||||
config["password"] = cty.StringVal(password)
|
||||
}
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), configs.SynthBody("synth", config))
|
||||
state, err := b.StateMgr(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatalf("Error for valid config: %s", err)
|
||||
}
|
||||
|
||||
remote.TestClient(t, state.(*remote.State).Client)
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/legacy/helper/schema"
|
||||
etcdv3 "go.etcd.io/etcd/clientv3"
|
||||
"go.etcd.io/etcd/pkg/transport"
|
||||
)
|
||||
|
||||
const (
|
||||
endpointsKey = "endpoints"
|
||||
usernameKey = "username"
|
||||
usernameEnvVarName = "ETCDV3_USERNAME"
|
||||
passwordKey = "password"
|
||||
passwordEnvVarName = "ETCDV3_PASSWORD"
|
||||
maxRequestBytesKey = "max_request_bytes"
|
||||
prefixKey = "prefix"
|
||||
lockKey = "lock"
|
||||
cacertPathKey = "cacert_path"
|
||||
certPathKey = "cert_path"
|
||||
keyPathKey = "key_path"
|
||||
)
|
||||
|
||||
func New() backend.Backend {
|
||||
s := &schema.Backend{
|
||||
Schema: map[string]*schema.Schema{
|
||||
endpointsKey: &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
},
|
||||
MinItems: 1,
|
||||
Required: true,
|
||||
Description: "Endpoints for the etcd cluster.",
|
||||
},
|
||||
|
||||
usernameKey: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "Username used to connect to the etcd cluster.",
|
||||
DefaultFunc: schema.EnvDefaultFunc(usernameEnvVarName, ""),
|
||||
},
|
||||
|
||||
passwordKey: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "Password used to connect to the etcd cluster.",
|
||||
DefaultFunc: schema.EnvDefaultFunc(passwordEnvVarName, ""),
|
||||
},
|
||||
|
||||
maxRequestBytesKey: &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
Description: "The max request size to send to etcd.",
|
||||
Default: 0,
|
||||
},
|
||||
|
||||
prefixKey: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "An optional prefix to be added to keys when to storing state in etcd.",
|
||||
Default: "",
|
||||
},
|
||||
|
||||
lockKey: &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Description: "Whether to lock state access.",
|
||||
Default: true,
|
||||
},
|
||||
|
||||
cacertPathKey: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "The path to a PEM-encoded CA bundle with which to verify certificates of TLS-enabled etcd servers.",
|
||||
Default: "",
|
||||
},
|
||||
|
||||
certPathKey: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "The path to a PEM-encoded certificate to provide to etcd for secure client identification.",
|
||||
Default: "",
|
||||
},
|
||||
|
||||
keyPathKey: &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "The path to a PEM-encoded key to provide to etcd for secure client identification.",
|
||||
Default: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := &Backend{Backend: s}
|
||||
result.Backend.ConfigureFunc = result.configure
|
||||
return result
|
||||
}
|
||||
|
||||
type Backend struct {
|
||||
*schema.Backend
|
||||
|
||||
// The fields below are set from configure.
|
||||
client *etcdv3.Client
|
||||
data *schema.ResourceData
|
||||
lock bool
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (b *Backend) configure(ctx context.Context) error {
|
||||
var err error
|
||||
// Grab the resource data.
|
||||
b.data = schema.FromContextBackendConfig(ctx)
|
||||
// Store the lock information.
|
||||
b.lock = b.data.Get(lockKey).(bool)
|
||||
// Store the prefix information.
|
||||
b.prefix = b.data.Get(prefixKey).(string)
|
||||
// Initialize a client to test config.
|
||||
b.client, err = b.rawClient()
|
||||
// Return err, if any.
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *Backend) rawClient() (*etcdv3.Client, error) {
|
||||
config := etcdv3.Config{}
|
||||
tlsInfo := transport.TLSInfo{}
|
||||
|
||||
if v, ok := b.data.GetOk(endpointsKey); ok {
|
||||
config.Endpoints = retrieveEndpoints(v)
|
||||
}
|
||||
if v, ok := b.data.GetOk(usernameKey); ok && v.(string) != "" {
|
||||
config.Username = v.(string)
|
||||
}
|
||||
if v, ok := b.data.GetOk(passwordKey); ok && v.(string) != "" {
|
||||
config.Password = v.(string)
|
||||
}
|
||||
if v, ok := b.data.GetOk(maxRequestBytesKey); ok && v.(int) != 0 {
|
||||
config.MaxCallSendMsgSize = v.(int)
|
||||
}
|
||||
if v, ok := b.data.GetOk(cacertPathKey); ok && v.(string) != "" {
|
||||
tlsInfo.TrustedCAFile = v.(string)
|
||||
}
|
||||
if v, ok := b.data.GetOk(certPathKey); ok && v.(string) != "" {
|
||||
tlsInfo.CertFile = v.(string)
|
||||
}
|
||||
if v, ok := b.data.GetOk(keyPathKey); ok && v.(string) != "" {
|
||||
tlsInfo.KeyFile = v.(string)
|
||||
}
|
||||
|
||||
if tlsCfg, err := tlsInfo.ClientConfig(); err != nil {
|
||||
return nil, err
|
||||
} else if !tlsInfo.Empty() {
|
||||
config.TLS = tlsCfg // Assign TLS configuration only if it valid and non-empty.
|
||||
}
|
||||
|
||||
return etcdv3.New(config)
|
||||
}
|
||||
|
||||
func retrieveEndpoints(v interface{}) []string {
|
||||
var endpoints []string
|
||||
list := v.([]interface{})
|
||||
for _, ep := range list {
|
||||
endpoints = append(endpoints, ep.(string))
|
||||
}
|
||||
return endpoints
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
etcdv3 "go.etcd.io/etcd/clientv3"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/states"
|
||||
"github.com/hashicorp/terraform/internal/states/remote"
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
)
|
||||
|
||||
func (b *Backend) Workspaces() ([]string, error) {
|
||||
res, err := b.client.Get(context.TODO(), b.prefix, etcdv3.WithPrefix(), etcdv3.WithKeysOnly())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]string, 1, len(res.Kvs)+1)
|
||||
result[0] = backend.DefaultStateName
|
||||
for _, kv := range res.Kvs {
|
||||
if strings.TrimPrefix(string(kv.Key), b.prefix) != backend.DefaultStateName {
|
||||
result = append(result, strings.TrimPrefix(string(kv.Key), b.prefix))
|
||||
}
|
||||
}
|
||||
sort.Strings(result[1:])
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (b *Backend) DeleteWorkspace(name string) error {
|
||||
if name == backend.DefaultStateName || name == "" {
|
||||
return fmt.Errorf("Can't delete default state.")
|
||||
}
|
||||
|
||||
key := b.determineKey(name)
|
||||
|
||||
_, err := b.client.Delete(context.TODO(), key)
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
|
||||
var stateMgr statemgr.Full = &remote.State{
|
||||
Client: &RemoteClient{
|
||||
Client: b.client,
|
||||
DoLock: b.lock,
|
||||
Key: b.determineKey(name),
|
||||
},
|
||||
}
|
||||
|
||||
if !b.lock {
|
||||
stateMgr = &statemgr.LockDisabled{Inner: stateMgr}
|
||||
}
|
||||
|
||||
lockInfo := statemgr.NewLockInfo()
|
||||
lockInfo.Operation = "init"
|
||||
lockUnlock := func(parent error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := stateMgr.RefreshState(); err != nil {
|
||||
err = lockUnlock(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v := stateMgr.State(); v == nil {
|
||||
lockId, err := stateMgr.Lock(lockInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to lock state in etcd: %s.", err)
|
||||
}
|
||||
|
||||
lockUnlock = func(parent error) error {
|
||||
if err := stateMgr.Unlock(lockId); err != nil {
|
||||
return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
||||
if err := stateMgr.WriteState(states.NewState()); err != nil {
|
||||
err = lockUnlock(err)
|
||||
return nil, err
|
||||
}
|
||||
if err := stateMgr.PersistState(); err != nil {
|
||||
err = lockUnlock(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := lockUnlock(nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return stateMgr, nil
|
||||
}
|
||||
|
||||
func (b *Backend) determineKey(name string) string {
|
||||
return b.prefix + name
|
||||
}
|
||||
|
||||
const errStateUnlock = `
|
||||
Error unlocking etcd state. Lock ID: %s
|
||||
|
||||
Error: %s
|
||||
|
||||
You may have to force-unlock this state in order to use it again.
|
||||
`
|
@ -1,107 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
etcdv3 "go.etcd.io/etcd/clientv3"
|
||||
)
|
||||
|
||||
var (
|
||||
etcdv3Endpoints = strings.Split(os.Getenv("TF_ETCDV3_ENDPOINTS"), ",")
|
||||
)
|
||||
|
||||
const (
|
||||
keyPrefix = "tf-unit"
|
||||
)
|
||||
|
||||
func TestBackend_impl(t *testing.T) {
|
||||
var _ backend.Backend = new(Backend)
|
||||
}
|
||||
|
||||
func cleanupEtcdv3(t *testing.T) {
|
||||
client, err := etcdv3.New(etcdv3.Config{
|
||||
Endpoints: etcdv3Endpoints,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := client.KV.Delete(context.TODO(), keyPrefix, etcdv3.WithPrefix())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("Cleaned up %d keys.", res.Deleted)
|
||||
}
|
||||
|
||||
func prepareEtcdv3(t *testing.T) {
|
||||
skip := os.Getenv("TF_ACC") == "" && os.Getenv("TF_ETCDV3_TEST") == ""
|
||||
if skip {
|
||||
t.Log("etcd server tests require setting TF_ACC or TF_ETCDV3_TEST")
|
||||
t.Skip()
|
||||
}
|
||||
if reflect.DeepEqual(etcdv3Endpoints, []string{""}) {
|
||||
t.Fatal("etcd server tests require setting TF_ETCDV3_ENDPOINTS")
|
||||
}
|
||||
cleanupEtcdv3(t)
|
||||
}
|
||||
|
||||
func TestBackend(t *testing.T) {
|
||||
prepareEtcdv3(t)
|
||||
defer cleanupEtcdv3(t)
|
||||
|
||||
prefix := fmt.Sprintf("%s/%s/", keyPrefix, time.Now().Format(time.RFC3339))
|
||||
|
||||
// Get the backend. We need two to test locking.
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": stringsToInterfaces(etcdv3Endpoints),
|
||||
"prefix": prefix,
|
||||
}))
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": stringsToInterfaces(etcdv3Endpoints),
|
||||
"prefix": prefix,
|
||||
}))
|
||||
|
||||
// Test
|
||||
backend.TestBackendStates(t, b1)
|
||||
backend.TestBackendStateLocks(t, b1, b2)
|
||||
backend.TestBackendStateForceUnlock(t, b1, b2)
|
||||
}
|
||||
|
||||
func TestBackend_lockDisabled(t *testing.T) {
|
||||
prepareEtcdv3(t)
|
||||
defer cleanupEtcdv3(t)
|
||||
|
||||
prefix := fmt.Sprintf("%s/%s/", keyPrefix, time.Now().Format(time.RFC3339))
|
||||
|
||||
// Get the backend. We need two to test locking.
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": stringsToInterfaces(etcdv3Endpoints),
|
||||
"prefix": prefix,
|
||||
"lock": false,
|
||||
}))
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": stringsToInterfaces(etcdv3Endpoints),
|
||||
"prefix": prefix + "/" + "different", // Diff so locking test would fail if it was locking
|
||||
"lock": false,
|
||||
}))
|
||||
|
||||
// Test
|
||||
backend.TestBackendStateLocks(t, b1, b2)
|
||||
}
|
||||
|
||||
func stringsToInterfaces(strSlice []string) []interface{} {
|
||||
var interfaceSlice []interface{}
|
||||
for _, v := range strSlice {
|
||||
interfaceSlice = append(interfaceSlice, v)
|
||||
}
|
||||
return interfaceSlice
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/terraform/internal/states/remote"
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
etcdv3 "go.etcd.io/etcd/clientv3"
|
||||
etcdv3sync "go.etcd.io/etcd/clientv3/concurrency"
|
||||
)
|
||||
|
||||
const (
|
||||
lockAcquireTimeout = 2 * time.Second
|
||||
lockInfoSuffix = ".lockinfo"
|
||||
)
|
||||
|
||||
// RemoteClient is a remote client that will store data in etcd.
|
||||
type RemoteClient struct {
|
||||
Client *etcdv3.Client
|
||||
DoLock bool
|
||||
Key string
|
||||
|
||||
etcdMutex *etcdv3sync.Mutex
|
||||
etcdSession *etcdv3sync.Session
|
||||
info *statemgr.LockInfo
|
||||
mu sync.Mutex
|
||||
modRevision int64
|
||||
}
|
||||
|
||||
func (c *RemoteClient) Get() (*remote.Payload, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
res, err := c.Client.KV.Get(context.TODO(), c.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.Count == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if res.Count >= 2 {
|
||||
return nil, fmt.Errorf("Expected a single result but got %d.", res.Count)
|
||||
}
|
||||
|
||||
c.modRevision = res.Kvs[0].ModRevision
|
||||
|
||||
payload := res.Kvs[0].Value
|
||||
md5 := md5.Sum(payload)
|
||||
|
||||
return &remote.Payload{
|
||||
Data: payload,
|
||||
MD5: md5[:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *RemoteClient) Put(data []byte) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
res, err := etcdv3.NewKV(c.Client).Txn(context.TODO()).If(
|
||||
etcdv3.Compare(etcdv3.ModRevision(c.Key), "=", c.modRevision),
|
||||
).Then(
|
||||
etcdv3.OpPut(c.Key, string(data)),
|
||||
etcdv3.OpGet(c.Key),
|
||||
).Commit()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !res.Succeeded {
|
||||
return fmt.Errorf("The transaction did not succeed.")
|
||||
}
|
||||
if len(res.Responses) != 2 {
|
||||
return fmt.Errorf("Expected two responses but got %d.", len(res.Responses))
|
||||
}
|
||||
|
||||
c.modRevision = res.Responses[1].GetResponseRange().Kvs[0].ModRevision
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RemoteClient) Delete() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
_, err := c.Client.KV.Delete(context.TODO(), c.Key)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if !c.DoLock {
|
||||
return "", nil
|
||||
}
|
||||
if c.etcdSession != nil {
|
||||
return "", fmt.Errorf("state %q already locked", c.Key)
|
||||
}
|
||||
|
||||
c.info = info
|
||||
return c.lock()
|
||||
}
|
||||
|
||||
func (c *RemoteClient) Unlock(id string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if !c.DoLock {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.unlock(id)
|
||||
}
|
||||
|
||||
func (c *RemoteClient) deleteLockInfo(info *statemgr.LockInfo) error {
|
||||
res, err := c.Client.KV.Delete(context.TODO(), c.Key+lockInfoSuffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.Deleted == 0 {
|
||||
return fmt.Errorf("No keys deleted for %s when deleting lock info.", c.Key+lockInfoSuffix)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {
|
||||
res, err := c.Client.KV.Get(context.TODO(), c.Key+lockInfoSuffix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.Count == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
li := &statemgr.LockInfo{}
|
||||
err = json.Unmarshal(res.Kvs[0].Value, li)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error unmarshaling lock info: %s.", err)
|
||||
}
|
||||
|
||||
return li, nil
|
||||
}
|
||||
|
||||
func (c *RemoteClient) putLockInfo(info *statemgr.LockInfo) error {
|
||||
c.info.Path = c.etcdMutex.Key()
|
||||
c.info.Created = time.Now().UTC()
|
||||
|
||||
_, err := c.Client.KV.Put(context.TODO(), c.Key+lockInfoSuffix, string(c.info.Marshal()))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *RemoteClient) lock() (string, error) {
|
||||
session, err := etcdv3sync.NewSession(c.Client)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), lockAcquireTimeout)
|
||||
defer cancel()
|
||||
|
||||
mutex := etcdv3sync.NewMutex(session, c.Key)
|
||||
if err1 := mutex.Lock(ctx); err1 != nil {
|
||||
lockInfo, err2 := c.getLockInfo()
|
||||
if err2 != nil {
|
||||
return "", &statemgr.LockError{Err: err2}
|
||||
}
|
||||
return "", &statemgr.LockError{Info: lockInfo, Err: err1}
|
||||
}
|
||||
|
||||
c.etcdMutex = mutex
|
||||
c.etcdSession = session
|
||||
|
||||
err = c.putLockInfo(c.info)
|
||||
if err != nil {
|
||||
if unlockErr := c.unlock(c.info.ID); unlockErr != nil {
|
||||
err = multierror.Append(err, unlockErr)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
return c.info.ID, nil
|
||||
}
|
||||
|
||||
func (c *RemoteClient) unlock(id string) error {
|
||||
if c.etcdMutex == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errs error
|
||||
|
||||
if err := c.deleteLockInfo(c.info); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
if err := c.etcdMutex.Unlock(context.TODO()); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
if err := c.etcdSession.Close(); err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
|
||||
c.etcdMutex = nil
|
||||
c.etcdSession = nil
|
||||
|
||||
return errs
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/states/remote"
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
)
|
||||
|
||||
func TestRemoteClient_impl(t *testing.T) {
|
||||
var _ remote.Client = new(RemoteClient)
|
||||
}
|
||||
|
||||
func TestRemoteClient(t *testing.T) {
|
||||
prepareEtcdv3(t)
|
||||
defer cleanupEtcdv3(t)
|
||||
|
||||
prefix := fmt.Sprintf("%s/%s/", keyPrefix, time.Now().Format(time.RFC3339))
|
||||
|
||||
// Get the backend
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": stringsToInterfaces(etcdv3Endpoints),
|
||||
"prefix": prefix,
|
||||
}))
|
||||
|
||||
// Grab the client
|
||||
state, err := b.StateMgr(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %s.", err)
|
||||
}
|
||||
|
||||
// Test
|
||||
remote.TestClient(t, state.(*remote.State).Client)
|
||||
}
|
||||
|
||||
func TestEtcdv3_stateLock(t *testing.T) {
|
||||
prepareEtcdv3(t)
|
||||
defer cleanupEtcdv3(t)
|
||||
|
||||
prefix := fmt.Sprintf("%s/%s/", keyPrefix, time.Now().Format(time.RFC3339))
|
||||
|
||||
// Get the backend
|
||||
s1, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": stringsToInterfaces(etcdv3Endpoints),
|
||||
"prefix": prefix,
|
||||
})).StateMgr(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s2, err := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": stringsToInterfaces(etcdv3Endpoints),
|
||||
"prefix": prefix,
|
||||
})).StateMgr(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
remote.TestRemoteLocks(t, s1.(*remote.State).Client, s2.(*remote.State).Client)
|
||||
}
|
||||
|
||||
func TestEtcdv3_destroyLock(t *testing.T) {
|
||||
prepareEtcdv3(t)
|
||||
defer cleanupEtcdv3(t)
|
||||
|
||||
prefix := fmt.Sprintf("%s/%s/", keyPrefix, time.Now().Format(time.RFC3339))
|
||||
|
||||
// Get the backend
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"endpoints": stringsToInterfaces(etcdv3Endpoints),
|
||||
"prefix": prefix,
|
||||
}))
|
||||
|
||||
// Grab the client
|
||||
s, err := b.StateMgr(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c := s.(*remote.State).Client.(*RemoteClient)
|
||||
|
||||
info := statemgr.NewLockInfo()
|
||||
id, err := c.Lock(info)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := c.Unlock(id); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
res, err := c.Client.KV.Get(context.TODO(), c.info.Path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res.Count != 0 {
|
||||
t.Fatalf("lock key not cleaned up at: %s", string(res.Kvs[0].Key))
|
||||
}
|
||||
}
|
@ -100,14 +100,23 @@ func (c *httpClient) Lock(info *statemgr.LockInfo) (string, error) {
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("HTTP remote state already locked, failed to read body")
|
||||
return "", &statemgr.LockError{
|
||||
Info: info,
|
||||
Err: fmt.Errorf("HTTP remote state already locked, failed to read body"),
|
||||
}
|
||||
}
|
||||
existing := statemgr.LockInfo{}
|
||||
err = json.Unmarshal(body, &existing)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("HTTP remote state already locked, failed to unmarshal body")
|
||||
return "", &statemgr.LockError{
|
||||
Info: info,
|
||||
Err: fmt.Errorf("HTTP remote state already locked, failed to unmarshal body"),
|
||||
}
|
||||
}
|
||||
return "", &statemgr.LockError{
|
||||
Info: info,
|
||||
Err: fmt.Errorf("HTTP remote state already locked: ID=%s", existing.ID),
|
||||
}
|
||||
return "", fmt.Errorf("HTTP remote state already locked: ID=%s", existing.ID)
|
||||
default:
|
||||
return "", fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode)
|
||||
}
|
||||
|
@ -5,9 +5,12 @@ import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
tfe "github.com/hashicorp/go-tfe"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/command/jsonstate"
|
||||
"github.com/hashicorp/terraform/internal/states/remote"
|
||||
"github.com/hashicorp/terraform/internal/states/statefile"
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
@ -65,12 +68,22 @@ func (r *remoteClient) Put(state []byte) error {
|
||||
return fmt.Errorf("Error reading state: %s", err)
|
||||
}
|
||||
|
||||
ov, err := jsonstate.MarshalOutputs(stateFile.State.RootModule().OutputValues)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading output values: %s", err)
|
||||
}
|
||||
o, err := json.Marshal(ov)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error converting output values to json: %s", err)
|
||||
}
|
||||
|
||||
options := tfe.StateVersionCreateOptions{
|
||||
Lineage: tfe.String(stateFile.Lineage),
|
||||
Serial: tfe.Int64(int64(stateFile.Serial)),
|
||||
MD5: tfe.String(fmt.Sprintf("%x", md5.Sum(state))),
|
||||
State: tfe.String(base64.StdEncoding.EncodeToString(state)),
|
||||
Force: tfe.Bool(r.forcePush),
|
||||
Lineage: tfe.String(stateFile.Lineage),
|
||||
Serial: tfe.Int64(int64(stateFile.Serial)),
|
||||
MD5: tfe.String(fmt.Sprintf("%x", md5.Sum(state))),
|
||||
State: tfe.String(base64.StdEncoding.EncodeToString(state)),
|
||||
Force: tfe.Bool(r.forcePush),
|
||||
JSONStateOutputs: tfe.String(base64.StdEncoding.EncodeToString(o)),
|
||||
}
|
||||
|
||||
// If we have a run ID, make sure to add it to the options
|
||||
|
@ -193,10 +193,15 @@ func getBackend(cfg cty.Value) (backend.Backend, cty.Value, tfdiags.Diagnostics)
|
||||
log.Printf("[DEBUG] Initializing remote state backend: %s", backendType)
|
||||
f := getBackendFactory(backendType)
|
||||
if f == nil {
|
||||
detail := fmt.Sprintf("There is no backend type named %q.", backendType)
|
||||
if msg, removed := backendInit.RemovedBackends[backendType]; removed {
|
||||
detail = msg
|
||||
}
|
||||
|
||||
diags = diags.Append(tfdiags.AttributeValue(
|
||||
tfdiags.Error,
|
||||
"Invalid backend configuration",
|
||||
fmt.Sprintf("There is no backend type named %q.", backendType),
|
||||
detail,
|
||||
cty.Path(nil).GetAttr("backend"),
|
||||
))
|
||||
return nil, cty.NilVal, diags
|
||||
|
@ -16,19 +16,19 @@ import (
|
||||
version "github.com/hashicorp/go-version"
|
||||
svchost "github.com/hashicorp/terraform-svchost"
|
||||
"github.com/hashicorp/terraform-svchost/disco"
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/states/remote"
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
tfversion "github.com/hashicorp/terraform/version"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/mitchellh/colorstring"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
tfversion "github.com/hashicorp/terraform/version"
|
||||
|
||||
backendLocal "github.com/hashicorp/terraform/internal/backend/local"
|
||||
)
|
||||
|
||||
@ -628,7 +628,7 @@ func (b *Cloud) StateMgr(name string) (statemgr.Full, error) {
|
||||
runID: os.Getenv("TFE_RUN_ID"),
|
||||
}
|
||||
|
||||
return &remote.State{Client: client}, nil
|
||||
return NewState(client), nil
|
||||
}
|
||||
|
||||
// Operation implements backend.Enhanced.
|
||||
|
@ -5,9 +5,13 @@ import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
tfe "github.com/hashicorp/go-tfe"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/command/jsonstate"
|
||||
"github.com/hashicorp/terraform/internal/states/remote"
|
||||
"github.com/hashicorp/terraform/internal/states/statefile"
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
@ -33,12 +37,12 @@ func (r *remoteClient) Get() (*remote.Payload, error) {
|
||||
// If no state exists, then return nil.
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Error retrieving state: %v", err)
|
||||
return nil, fmt.Errorf("failed to retrieve state: %w", err)
|
||||
}
|
||||
|
||||
state, err := r.client.StateVersions.Download(ctx, sv.DownloadURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error downloading state: %v", err)
|
||||
return nil, fmt.Errorf("failed to download state: %w", err)
|
||||
}
|
||||
|
||||
// If the state is empty, then return nil.
|
||||
@ -62,15 +66,25 @@ func (r *remoteClient) Put(state []byte) error {
|
||||
// Read the raw state into a Terraform state.
|
||||
stateFile, err := statefile.Read(bytes.NewReader(state))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error reading state: %s", err)
|
||||
return fmt.Errorf("failed to read state: %w", err)
|
||||
}
|
||||
|
||||
ov, err := jsonstate.MarshalOutputs(stateFile.State.RootModule().OutputValues)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to translate outputs: %w", err)
|
||||
}
|
||||
o, err := json.Marshal(ov)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal outputs to json: %w", err)
|
||||
}
|
||||
|
||||
options := tfe.StateVersionCreateOptions{
|
||||
Lineage: tfe.String(stateFile.Lineage),
|
||||
Serial: tfe.Int64(int64(stateFile.Serial)),
|
||||
MD5: tfe.String(fmt.Sprintf("%x", md5.Sum(state))),
|
||||
State: tfe.String(base64.StdEncoding.EncodeToString(state)),
|
||||
Force: tfe.Bool(r.forcePush),
|
||||
Lineage: tfe.String(stateFile.Lineage),
|
||||
Serial: tfe.Int64(int64(stateFile.Serial)),
|
||||
MD5: tfe.String(fmt.Sprintf("%x", md5.Sum(state))),
|
||||
State: tfe.String(base64.StdEncoding.EncodeToString(state)),
|
||||
Force: tfe.Bool(r.forcePush),
|
||||
JSONStateOutputs: tfe.String(base64.StdEncoding.EncodeToString(o)),
|
||||
}
|
||||
|
||||
// If we have a run ID, make sure to add it to the options
|
||||
@ -83,7 +97,7 @@ func (r *remoteClient) Put(state []byte) error {
|
||||
_, err = r.client.StateVersions.Create(ctx, r.workspace.ID, options)
|
||||
if err != nil {
|
||||
r.stateUploadErr = true
|
||||
return fmt.Errorf("Error uploading state: %v", err)
|
||||
return fmt.Errorf("failed to upload state: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -93,7 +107,7 @@ func (r *remoteClient) Put(state []byte) error {
|
||||
func (r *remoteClient) Delete() error {
|
||||
err := r.client.Workspaces.Delete(context.Background(), r.organization, r.workspace.Name)
|
||||
if err != nil && err != tfe.ErrResourceNotFound {
|
||||
return fmt.Errorf("Error deleting workspace %s: %v", r.workspace.Name, err)
|
||||
return fmt.Errorf("failed to delete workspace %s: %w", r.workspace.Name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -146,7 +160,7 @@ func (r *remoteClient) Unlock(id string) error {
|
||||
if r.lockInfo != nil {
|
||||
// Verify the expected lock ID.
|
||||
if r.lockInfo.ID != id {
|
||||
lockErr.Err = fmt.Errorf("lock ID does not match existing lock")
|
||||
lockErr.Err = errors.New("lock ID does not match existing lock")
|
||||
return lockErr
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
tfe "github.com/hashicorp/go-tfe"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/states"
|
||||
"github.com/hashicorp/terraform/internal/states/remote"
|
||||
"github.com/hashicorp/terraform/internal/states/statefile"
|
||||
@ -19,7 +21,50 @@ func TestRemoteClient(t *testing.T) {
|
||||
remote.TestClient(t, client)
|
||||
}
|
||||
|
||||
func TestRemoteClient_stateLock(t *testing.T) {
|
||||
func TestRemoteClient_stateVersionCreated(t *testing.T) {
|
||||
b, bCleanup := testBackendWithName(t)
|
||||
defer bCleanup()
|
||||
|
||||
raw, err := b.StateMgr(testBackendSingleWorkspaceName)
|
||||
if err != nil {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
client := raw.(*State).Client
|
||||
|
||||
err = client.Put(([]byte)(`
|
||||
{
|
||||
"version": 4,
|
||||
"terraform_version": "1.3.0",
|
||||
"serial": 1,
|
||||
"lineage": "backend-change",
|
||||
"outputs": {
|
||||
"foo": {
|
||||
"type": "string",
|
||||
"value": "bar"
|
||||
}
|
||||
}
|
||||
}`))
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
stateVersionsAPI := b.client.StateVersions.(*MockStateVersions)
|
||||
if got, want := len(stateVersionsAPI.stateVersions), 1; got != want {
|
||||
t.Fatalf("wrong number of state versions in the mock client %d; want %d", got, want)
|
||||
}
|
||||
|
||||
var stateVersion *tfe.StateVersion
|
||||
for _, sv := range stateVersionsAPI.stateVersions {
|
||||
stateVersion = sv
|
||||
}
|
||||
|
||||
if stateVersionsAPI.outputStates[stateVersion.ID] == nil || len(stateVersionsAPI.outputStates[stateVersion.ID]) == 0 {
|
||||
t.Fatal("no state version outputs in the mock client")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteClient_TestRemoteLocks(t *testing.T) {
|
||||
b, bCleanup := testBackendWithName(t)
|
||||
defer bCleanup()
|
||||
|
||||
@ -33,7 +78,7 @@ func TestRemoteClient_stateLock(t *testing.T) {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
remote.TestRemoteLocks(t, s1.(*remote.State).Client, s2.(*remote.State).Client)
|
||||
remote.TestRemoteLocks(t, s1.(*State).Client, s2.(*State).Client)
|
||||
}
|
||||
|
||||
func TestRemoteClient_withRunID(t *testing.T) {
|
||||
|
160
internal/cloud/state.go
Normal file
160
internal/cloud/state.go
Normal file
@ -0,0 +1,160 @@
|
||||
package cloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-tfe"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/states"
|
||||
"github.com/hashicorp/terraform/internal/states/remote"
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
)
|
||||
|
||||
// State is similar to remote State and delegates to it, except in the case of output values,
|
||||
// which use a separate methodology that ensures the caller is authorized to read cloud
|
||||
// workspace outputs.
|
||||
type State struct {
|
||||
Client *remoteClient
|
||||
|
||||
delegate remote.State
|
||||
}
|
||||
|
||||
var ErrStateVersionUnauthorizedUpgradeState = errors.New(strings.TrimSpace(`
|
||||
You are not authorized to read the full state version containing outputs.
|
||||
State versions created by terraform v1.3.0 and newer do not require this level
|
||||
of authorization and therefore this error can usually be fixed by upgrading the
|
||||
remote state version.
|
||||
`))
|
||||
|
||||
// Proof that cloud State is a statemgr.Persistent interface
|
||||
var _ statemgr.Persistent = (*State)(nil)
|
||||
|
||||
func NewState(client *remoteClient) *State {
|
||||
return &State{
|
||||
Client: client,
|
||||
delegate: remote.State{Client: client},
|
||||
}
|
||||
}
|
||||
|
||||
// State delegates calls to read State to the remote State
|
||||
func (s *State) State() *states.State {
|
||||
return s.delegate.State()
|
||||
}
|
||||
|
||||
// Lock delegates calls to lock state to the remote State
|
||||
func (s *State) Lock(info *statemgr.LockInfo) (string, error) {
|
||||
return s.delegate.Lock(info)
|
||||
}
|
||||
|
||||
// Unlock delegates calls to unlock state to the remote State
|
||||
func (s *State) Unlock(id string) error {
|
||||
return s.delegate.Unlock(id)
|
||||
}
|
||||
|
||||
// RefreshState delegates calls to refresh State to the remote State
|
||||
func (s *State) RefreshState() error {
|
||||
return s.delegate.RefreshState()
|
||||
}
|
||||
|
||||
// RefreshState delegates calls to refresh State to the remote State
|
||||
func (s *State) PersistState() error {
|
||||
return s.delegate.PersistState()
|
||||
}
|
||||
|
||||
// WriteState delegates calls to write State to the remote State
|
||||
func (s *State) WriteState(state *states.State) error {
|
||||
return s.delegate.WriteState(state)
|
||||
}
|
||||
|
||||
func (s *State) fallbackReadOutputsFromFullState() (map[string]*states.OutputValue, error) {
|
||||
log.Printf("[DEBUG] falling back to reading full state")
|
||||
|
||||
if err := s.RefreshState(); err != nil {
|
||||
return nil, fmt.Errorf("failed to load state: %w", err)
|
||||
}
|
||||
|
||||
state := s.State()
|
||||
if state == nil {
|
||||
// We know that there is supposed to be state (and this is not simply a new workspace
|
||||
// without state) because the fallback is only invoked when outputs are present but
|
||||
// detailed types are not available.
|
||||
return nil, ErrStateVersionUnauthorizedUpgradeState
|
||||
}
|
||||
|
||||
return state.RootModule().OutputValues, nil
|
||||
}
|
||||
|
||||
// GetRootOutputValues fetches output values from Terraform Cloud
|
||||
func (s *State) GetRootOutputValues() (map[string]*states.OutputValue, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
so, err := s.Client.client.StateVersionOutputs.ReadCurrent(ctx, s.Client.workspace.ID)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read state version outputs: %w", err)
|
||||
}
|
||||
|
||||
result := make(map[string]*states.OutputValue)
|
||||
|
||||
for _, output := range so.Items {
|
||||
if output.DetailedType == nil {
|
||||
// If there is no detailed type information available, this state was probably created
|
||||
// with a version of terraform < 1.3.0. In this case, we'll eject completely from this
|
||||
// function and fall back to the old behavior of reading the entire state file, which
|
||||
// requires a higher level of authorization.
|
||||
return s.fallbackReadOutputsFromFullState()
|
||||
}
|
||||
|
||||
if output.Sensitive {
|
||||
// Since this is a sensitive value, the output must be requested explicitly in order to
|
||||
// read its value, which is assumed to be present by callers
|
||||
sensitiveOutput, err := s.Client.client.StateVersionOutputs.Read(ctx, output.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read state version output %s: %w", output.ID, err)
|
||||
}
|
||||
output.Value = sensitiveOutput.Value
|
||||
}
|
||||
|
||||
cval, err := tfeOutputToCtyValue(*output)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not decode output %s (ID %s)", output.Name, output.ID)
|
||||
}
|
||||
|
||||
result[output.Name] = &states.OutputValue{
|
||||
Value: cval,
|
||||
Sensitive: output.Sensitive,
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// tfeOutputToCtyValue decodes a combination of TFE output value and detailed-type to create a
|
||||
// cty value that is suitable for use in terraform.
|
||||
func tfeOutputToCtyValue(output tfe.StateVersionOutput) (cty.Value, error) {
|
||||
var result cty.Value
|
||||
bufType, err := json.Marshal(output.DetailedType)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("could not marshal output %s type: %w", output.ID, err)
|
||||
}
|
||||
|
||||
var ctype cty.Type
|
||||
err = ctype.UnmarshalJSON(bufType)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("could not interpret output %s type: %w", output.ID, err)
|
||||
}
|
||||
|
||||
result, err = gocty.ToCtyValue(output.Value, ctype)
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("could not interpret value %v as type %s for output %s: %w", result, ctype.FriendlyName(), output.ID, err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
83
internal/cloud/state_test.go
Normal file
83
internal/cloud/state_test.go
Normal file
@ -0,0 +1,83 @@
|
||||
package cloud
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/go-tfe"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
)
|
||||
|
||||
func TestState_impl(t *testing.T) {
|
||||
var _ statemgr.Reader = new(State)
|
||||
var _ statemgr.Writer = new(State)
|
||||
var _ statemgr.Persister = new(State)
|
||||
var _ statemgr.Refresher = new(State)
|
||||
var _ statemgr.OutputReader = new(State)
|
||||
var _ statemgr.Locker = new(State)
|
||||
}
|
||||
|
||||
type ExpectedOutput struct {
|
||||
Name string
|
||||
Sensitive bool
|
||||
IsNull bool
|
||||
}
|
||||
|
||||
func TestState_GetRootOutputValues(t *testing.T) {
|
||||
b, bCleanup := testBackendWithOutputs(t)
|
||||
defer bCleanup()
|
||||
|
||||
client := &remoteClient{
|
||||
client: b.client,
|
||||
workspace: &tfe.Workspace{
|
||||
ID: "ws-abcd",
|
||||
},
|
||||
}
|
||||
|
||||
state := NewState(client)
|
||||
outputs, err := state.GetRootOutputValues()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("error returned from GetRootOutputValues: %s", err)
|
||||
}
|
||||
|
||||
cases := []ExpectedOutput{
|
||||
{
|
||||
Name: "sensitive_output",
|
||||
Sensitive: true,
|
||||
IsNull: false,
|
||||
},
|
||||
{
|
||||
Name: "nonsensitive_output",
|
||||
Sensitive: false,
|
||||
IsNull: false,
|
||||
},
|
||||
{
|
||||
Name: "object_output",
|
||||
Sensitive: false,
|
||||
IsNull: false,
|
||||
},
|
||||
{
|
||||
Name: "list_output",
|
||||
Sensitive: false,
|
||||
IsNull: false,
|
||||
},
|
||||
}
|
||||
|
||||
if len(outputs) != len(cases) {
|
||||
t.Errorf("Expected %d item but %d were returned", len(cases), len(outputs))
|
||||
}
|
||||
|
||||
for _, testCase := range cases {
|
||||
so, ok := outputs[testCase.Name]
|
||||
if !ok {
|
||||
t.Fatalf("Expected key %s but it was not found", testCase.Name)
|
||||
}
|
||||
if so.Value.IsNull() != testCase.IsNull {
|
||||
t.Errorf("Key %s does not match null expectation %v", testCase.Name, testCase.IsNull)
|
||||
}
|
||||
if so.Sensitive != testCase.Sensitive {
|
||||
t.Errorf("Key %s does not match sensitive expectation %v", testCase.Name, testCase.Sensitive)
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package cloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@ -14,6 +15,9 @@ import (
|
||||
svchost "github.com/hashicorp/terraform-svchost"
|
||||
"github.com/hashicorp/terraform-svchost/auth"
|
||||
"github.com/hashicorp/terraform-svchost/disco"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/backend"
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
@ -23,8 +27,6 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
"github.com/hashicorp/terraform/version"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
backendLocal "github.com/hashicorp/terraform/internal/backend/local"
|
||||
)
|
||||
@ -117,7 +119,69 @@ func testRemoteClient(t *testing.T) remote.Client {
|
||||
t.Fatalf("error: %v", err)
|
||||
}
|
||||
|
||||
return raw.(*remote.State).Client
|
||||
return raw.(*State).Client
|
||||
}
|
||||
|
||||
func testBackendWithOutputs(t *testing.T) (*Cloud, func()) {
|
||||
b, cleanup := testBackendWithName(t)
|
||||
|
||||
// Get a new mock client to use for adding outputs
|
||||
mc := NewMockClient()
|
||||
|
||||
mc.StateVersionOutputs.create("svo-abcd", &tfe.StateVersionOutput{
|
||||
ID: "svo-abcd",
|
||||
Value: "foobar",
|
||||
Sensitive: true,
|
||||
Type: "string",
|
||||
Name: "sensitive_output",
|
||||
DetailedType: "string",
|
||||
})
|
||||
|
||||
mc.StateVersionOutputs.create("svo-zyxw", &tfe.StateVersionOutput{
|
||||
ID: "svo-zyxw",
|
||||
Value: "bazqux",
|
||||
Type: "string",
|
||||
Name: "nonsensitive_output",
|
||||
DetailedType: "string",
|
||||
})
|
||||
|
||||
var dt interface{}
|
||||
var val interface{}
|
||||
err := json.Unmarshal([]byte(`["object", {"foo":"string"}]`), &dt)
|
||||
if err != nil {
|
||||
t.Fatalf("could not unmarshal detailed type: %s", err)
|
||||
}
|
||||
err = json.Unmarshal([]byte(`{"foo":"bar"}`), &val)
|
||||
if err != nil {
|
||||
t.Fatalf("could not unmarshal value: %s", err)
|
||||
}
|
||||
mc.StateVersionOutputs.create("svo-efgh", &tfe.StateVersionOutput{
|
||||
ID: "svo-efgh",
|
||||
Value: val,
|
||||
Type: "object",
|
||||
Name: "object_output",
|
||||
DetailedType: dt,
|
||||
})
|
||||
|
||||
err = json.Unmarshal([]byte(`["list", "bool"]`), &dt)
|
||||
if err != nil {
|
||||
t.Fatalf("could not unmarshal detailed type: %s", err)
|
||||
}
|
||||
err = json.Unmarshal([]byte(`[true, false, true, true]`), &val)
|
||||
if err != nil {
|
||||
t.Fatalf("could not unmarshal value: %s", err)
|
||||
}
|
||||
mc.StateVersionOutputs.create("svo-ijkl", &tfe.StateVersionOutput{
|
||||
ID: "svo-ijkl",
|
||||
Value: val,
|
||||
Type: "array",
|
||||
Name: "list_output",
|
||||
DetailedType: dt,
|
||||
})
|
||||
|
||||
b.client.StateVersionOutputs = mc.StateVersionOutputs
|
||||
|
||||
return b, cleanup
|
||||
}
|
||||
|
||||
func testBackend(t *testing.T, obj cty.Value) (*Cloud, func()) {
|
||||
@ -149,6 +213,7 @@ func testBackend(t *testing.T, obj cty.Value) (*Cloud, func()) {
|
||||
b.client.PolicyChecks = mc.PolicyChecks
|
||||
b.client.Runs = mc.Runs
|
||||
b.client.StateVersions = mc.StateVersions
|
||||
b.client.StateVersionOutputs = mc.StateVersionOutputs
|
||||
b.client.Variables = mc.Variables
|
||||
b.client.Workspaces = mc.Workspaces
|
||||
|
||||
|
@ -16,8 +16,9 @@ import (
|
||||
"time"
|
||||
|
||||
tfe "github.com/hashicorp/go-tfe"
|
||||
tfversion "github.com/hashicorp/terraform/version"
|
||||
"github.com/mitchellh/copystructure"
|
||||
|
||||
tfversion "github.com/hashicorp/terraform/version"
|
||||
)
|
||||
|
||||
type MockClient struct {
|
||||
@ -29,6 +30,7 @@ type MockClient struct {
|
||||
PolicyChecks *MockPolicyChecks
|
||||
Runs *MockRuns
|
||||
StateVersions *MockStateVersions
|
||||
StateVersionOutputs *MockStateVersionOutputs
|
||||
Variables *MockVariables
|
||||
Workspaces *MockWorkspaces
|
||||
}
|
||||
@ -43,6 +45,7 @@ func NewMockClient() *MockClient {
|
||||
c.PolicyChecks = newMockPolicyChecks(c)
|
||||
c.Runs = newMockRuns(c)
|
||||
c.StateVersions = newMockStateVersions(c)
|
||||
c.StateVersionOutputs = newMockStateVersionOutputs(c)
|
||||
c.Variables = newMockVariables(c)
|
||||
c.Workspaces = newMockWorkspaces(c)
|
||||
return c
|
||||
@ -923,6 +926,7 @@ type MockStateVersions struct {
|
||||
states map[string][]byte
|
||||
stateVersions map[string]*tfe.StateVersion
|
||||
workspaces map[string][]string
|
||||
outputStates map[string][]byte
|
||||
}
|
||||
|
||||
func newMockStateVersions(client *MockClient) *MockStateVersions {
|
||||
@ -931,6 +935,7 @@ func newMockStateVersions(client *MockClient) *MockStateVersions {
|
||||
states: make(map[string][]byte),
|
||||
stateVersions: make(map[string]*tfe.StateVersion),
|
||||
workspaces: make(map[string][]string),
|
||||
outputStates: make(map[string][]byte),
|
||||
}
|
||||
}
|
||||
|
||||
@ -972,6 +977,7 @@ func (m *MockStateVersions) Create(ctx context.Context, workspaceID string, opti
|
||||
}
|
||||
|
||||
m.states[sv.DownloadURL] = state
|
||||
m.outputStates[sv.ID] = []byte(*options.JSONStateOutputs)
|
||||
m.stateVersions[sv.ID] = sv
|
||||
m.workspaces[workspaceID] = append(m.workspaces[workspaceID], sv.ID)
|
||||
|
||||
@ -1025,6 +1031,49 @@ func (m *MockStateVersions) ListOutputs(ctx context.Context, svID string, option
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
type MockStateVersionOutputs struct {
|
||||
client *MockClient
|
||||
outputs map[string]*tfe.StateVersionOutput
|
||||
}
|
||||
|
||||
func newMockStateVersionOutputs(client *MockClient) *MockStateVersionOutputs {
|
||||
return &MockStateVersionOutputs{
|
||||
client: client,
|
||||
outputs: make(map[string]*tfe.StateVersionOutput),
|
||||
}
|
||||
}
|
||||
|
||||
// This is a helper function in order to create mocks to be read later
|
||||
func (m *MockStateVersionOutputs) create(id string, svo *tfe.StateVersionOutput) {
|
||||
m.outputs[id] = svo
|
||||
}
|
||||
|
||||
func (m *MockStateVersionOutputs) Read(ctx context.Context, outputID string) (*tfe.StateVersionOutput, error) {
|
||||
result, ok := m.outputs[outputID]
|
||||
if !ok {
|
||||
return nil, tfe.ErrResourceNotFound
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m *MockStateVersionOutputs) ReadCurrent(ctx context.Context, workspaceID string) (*tfe.StateVersionOutputsList, error) {
|
||||
svl := &tfe.StateVersionOutputsList{}
|
||||
for _, sv := range m.outputs {
|
||||
svl.Items = append(svl.Items, sv)
|
||||
}
|
||||
|
||||
svl.Pagination = &tfe.Pagination{
|
||||
CurrentPage: 1,
|
||||
NextPage: 1,
|
||||
PreviousPage: 1,
|
||||
TotalPages: 1,
|
||||
TotalCount: len(svl.Items),
|
||||
}
|
||||
|
||||
return svl, nil
|
||||
}
|
||||
|
||||
type MockVariables struct {
|
||||
client *MockClient
|
||||
workspaces map[string]*tfe.VariableList
|
||||
|
@ -3,11 +3,9 @@ package command
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
@ -21,7 +19,6 @@ import (
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/command/views"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
@ -29,7 +26,6 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/states/statemgr"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
tfversion "github.com/hashicorp/terraform/version"
|
||||
)
|
||||
|
||||
func TestApply(t *testing.T) {
|
||||
@ -2055,81 +2051,7 @@ func TestApply_jsonGoldenReference(t *testing.T) {
|
||||
t.Fatal("state should not be nil")
|
||||
}
|
||||
|
||||
// Load the golden reference fixture
|
||||
wantFile, err := os.Open(path.Join(testFixturePath("apply"), "output.jsonlog"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open output file: %s", err)
|
||||
}
|
||||
defer wantFile.Close()
|
||||
wantBytes, err := ioutil.ReadAll(wantFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read output file: %s", err)
|
||||
}
|
||||
want := string(wantBytes)
|
||||
|
||||
got := output.Stdout()
|
||||
|
||||
// Split the output and the reference into lines so that we can compare
|
||||
// messages
|
||||
got = strings.TrimSuffix(got, "\n")
|
||||
gotLines := strings.Split(got, "\n")
|
||||
|
||||
want = strings.TrimSuffix(want, "\n")
|
||||
wantLines := strings.Split(want, "\n")
|
||||
|
||||
if len(gotLines) != len(wantLines) {
|
||||
t.Errorf("unexpected number of log lines: got %d, want %d", len(gotLines), len(wantLines))
|
||||
}
|
||||
|
||||
// Verify that the log starts with a version message
|
||||
type versionMessage struct {
|
||||
Level string `json:"@level"`
|
||||
Message string `json:"@message"`
|
||||
Type string `json:"type"`
|
||||
Terraform string `json:"terraform"`
|
||||
UI string `json:"ui"`
|
||||
}
|
||||
var gotVersion versionMessage
|
||||
if err := json.Unmarshal([]byte(gotLines[0]), &gotVersion); err != nil {
|
||||
t.Errorf("failed to unmarshal version line: %s\n%s", err, gotLines[0])
|
||||
}
|
||||
wantVersion := versionMessage{
|
||||
"info",
|
||||
fmt.Sprintf("Terraform %s", tfversion.String()),
|
||||
"version",
|
||||
tfversion.String(),
|
||||
views.JSON_UI_VERSION,
|
||||
}
|
||||
if !cmp.Equal(wantVersion, gotVersion) {
|
||||
t.Errorf("unexpected first message:\n%s", cmp.Diff(wantVersion, gotVersion))
|
||||
}
|
||||
|
||||
// Compare the rest of the lines against the golden reference
|
||||
var gotLineMaps []map[string]interface{}
|
||||
for i, line := range gotLines[1:] {
|
||||
index := i + 1
|
||||
var gotMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(line), &gotMap); err != nil {
|
||||
t.Errorf("failed to unmarshal got line %d: %s\n%s", index, err, gotLines[index])
|
||||
}
|
||||
if _, ok := gotMap["@timestamp"]; !ok {
|
||||
t.Errorf("missing @timestamp field in log: %s", gotLines[index])
|
||||
}
|
||||
delete(gotMap, "@timestamp")
|
||||
gotLineMaps = append(gotLineMaps, gotMap)
|
||||
}
|
||||
var wantLineMaps []map[string]interface{}
|
||||
for i, line := range wantLines[1:] {
|
||||
index := i + 1
|
||||
var wantMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(line), &wantMap); err != nil {
|
||||
t.Errorf("failed to unmarshal want line %d: %s\n%s", index, err, gotLines[index])
|
||||
}
|
||||
wantLineMaps = append(wantLineMaps, wantMap)
|
||||
}
|
||||
if diff := cmp.Diff(wantLineMaps, gotLineMaps); diff != "" {
|
||||
t.Errorf("wrong output lines\n%s", diff)
|
||||
}
|
||||
checkGoldenReference(t, output, "apply")
|
||||
}
|
||||
|
||||
func TestApply_warnings(t *testing.T) {
|
||||
|
@ -7,12 +7,14 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
@ -1052,3 +1054,92 @@ func testView(t *testing.T) (*views.View, func(*testing.T) *terminal.TestOutput)
|
||||
streams, done := terminal.StreamsForTesting(t)
|
||||
return views.NewView(streams), done
|
||||
}
|
||||
|
||||
// checkGoldenReference compares the given test output with a known "golden" output log
|
||||
// located under the specified fixture path.
|
||||
//
|
||||
// If any of these tests fail, please communicate with Terraform Cloud folks before resolving,
|
||||
// as changes to UI output may also affect the behavior of Terraform Cloud's structured run output.
|
||||
func checkGoldenReference(t *testing.T, output *terminal.TestOutput, fixturePathName string) {
|
||||
t.Helper()
|
||||
|
||||
// Load the golden reference fixture
|
||||
wantFile, err := os.Open(path.Join(testFixturePath(fixturePathName), "output.jsonlog"))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open output file: %s", err)
|
||||
}
|
||||
defer wantFile.Close()
|
||||
wantBytes, err := ioutil.ReadAll(wantFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read output file: %s", err)
|
||||
}
|
||||
want := string(wantBytes)
|
||||
|
||||
got := output.Stdout()
|
||||
|
||||
// Split the output and the reference into lines so that we can compare
|
||||
// messages
|
||||
got = strings.TrimSuffix(got, "\n")
|
||||
gotLines := strings.Split(got, "\n")
|
||||
|
||||
want = strings.TrimSuffix(want, "\n")
|
||||
wantLines := strings.Split(want, "\n")
|
||||
|
||||
if len(gotLines) != len(wantLines) {
|
||||
t.Errorf("unexpected number of log lines: got %d, want %d\n"+
|
||||
"NOTE: This failure may indicate a UI change affecting the behavior of structured run output on TFC.\n"+
|
||||
"Please communicate with Terraform Cloud team before resolving", len(gotLines), len(wantLines))
|
||||
}
|
||||
|
||||
// Verify that the log starts with a version message
|
||||
type versionMessage struct {
|
||||
Level string `json:"@level"`
|
||||
Message string `json:"@message"`
|
||||
Type string `json:"type"`
|
||||
Terraform string `json:"terraform"`
|
||||
UI string `json:"ui"`
|
||||
}
|
||||
var gotVersion versionMessage
|
||||
if err := json.Unmarshal([]byte(gotLines[0]), &gotVersion); err != nil {
|
||||
t.Errorf("failed to unmarshal version line: %s\n%s", err, gotLines[0])
|
||||
}
|
||||
wantVersion := versionMessage{
|
||||
"info",
|
||||
fmt.Sprintf("Terraform %s", version.String()),
|
||||
"version",
|
||||
version.String(),
|
||||
views.JSON_UI_VERSION,
|
||||
}
|
||||
if !cmp.Equal(wantVersion, gotVersion) {
|
||||
t.Errorf("unexpected first message:\n%s", cmp.Diff(wantVersion, gotVersion))
|
||||
}
|
||||
|
||||
// Compare the rest of the lines against the golden reference
|
||||
var gotLineMaps []map[string]interface{}
|
||||
for i, line := range gotLines[1:] {
|
||||
index := i + 1
|
||||
var gotMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(line), &gotMap); err != nil {
|
||||
t.Errorf("failed to unmarshal got line %d: %s\n%s", index, err, gotLines[index])
|
||||
}
|
||||
if _, ok := gotMap["@timestamp"]; !ok {
|
||||
t.Errorf("missing @timestamp field in log: %s", gotLines[index])
|
||||
}
|
||||
delete(gotMap, "@timestamp")
|
||||
gotLineMaps = append(gotLineMaps, gotMap)
|
||||
}
|
||||
var wantLineMaps []map[string]interface{}
|
||||
for i, line := range wantLines[1:] {
|
||||
index := i + 1
|
||||
var wantMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(line), &wantMap); err != nil {
|
||||
t.Errorf("failed to unmarshal want line %d: %s\n%s", index, err, gotLines[index])
|
||||
}
|
||||
wantLineMaps = append(wantLineMaps, wantMap)
|
||||
}
|
||||
if diff := cmp.Diff(wantLineMaps, gotLineMaps); diff != "" {
|
||||
t.Errorf("wrong output lines\n%s\n"+
|
||||
"NOTE: This failure may indicate a UI change affecting the behavior of structured run output on TFC.\n"+
|
||||
"Please communicate with Terraform Cloud team before resolving", diff)
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,16 @@ func TestProviderProtocols(t *testing.T) {
|
||||
}
|
||||
|
||||
if !strings.Contains(stdout, "Apply complete! Resources: 2 added, 0 changed, 0 destroyed.") {
|
||||
t.Fatalf("wrong output:\n%s", stdout)
|
||||
t.Fatalf("wrong output:\nstdout:%s\nstderr%s", stdout, stderr)
|
||||
}
|
||||
|
||||
/// DESTROY
|
||||
stdout, stderr, err = tf.Run("destroy", "-auto-approve")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr)
|
||||
}
|
||||
|
||||
if !strings.Contains(stdout, "Resources: 2 destroyed") {
|
||||
t.Fatalf("wrong destroy output\nstdout:%s\nstderr:%s", stdout, stderr)
|
||||
}
|
||||
}
|
||||
|
@ -55,11 +55,6 @@ func (c *FmtCommand) Run(args []string) int {
|
||||
}
|
||||
|
||||
args = cmdFlags.Args()
|
||||
if len(args) > 1 {
|
||||
c.Ui.Error("The fmt command expects at most one argument.")
|
||||
cmdFlags.Usage()
|
||||
return 1
|
||||
}
|
||||
|
||||
var paths []string
|
||||
if len(args) == 0 {
|
||||
@ -68,7 +63,7 @@ func (c *FmtCommand) Run(args []string) int {
|
||||
c.list = false
|
||||
c.write = false
|
||||
} else {
|
||||
paths = []string{args[0]}
|
||||
paths = args
|
||||
}
|
||||
|
||||
var output io.Writer
|
||||
@ -528,15 +523,20 @@ func (c *FmtCommand) trimNewlines(tokens hclwrite.Tokens) hclwrite.Tokens {
|
||||
|
||||
func (c *FmtCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: terraform [global options] fmt [options] [DIR]
|
||||
Usage: terraform [global options] fmt [options] [target...]
|
||||
|
||||
Rewrites all Terraform configuration files to a canonical format. Both
|
||||
configuration files (.tf) and variables files (.tfvars) are updated.
|
||||
JSON files (.tf.json or .tfvars.json) are not modified.
|
||||
Rewrites all Terraform configuration files to a canonical format. Both
|
||||
configuration files (.tf) and variables files (.tfvars) are updated.
|
||||
JSON files (.tf.json or .tfvars.json) are not modified.
|
||||
|
||||
If DIR is not specified then the current working directory will be used.
|
||||
If DIR is "-" then content will be read from STDIN. The given content must
|
||||
be in the Terraform language native syntax; JSON is not supported.
|
||||
By default, fmt scans the current directory for configuration files. If you
|
||||
provide a directory for the target argument, then fmt will scan that
|
||||
directory instead. If you provide a file, then fmt will process just that
|
||||
file. If you provide a single dash ("-"), then fmt will read from standard
|
||||
input (STDIN).
|
||||
|
||||
The content must be in the Terraform language native syntax; JSON is not
|
||||
supported.
|
||||
|
||||
Options:
|
||||
|
||||
|
@ -166,7 +166,16 @@ func TestFmt_snippetInError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFmt_tooManyArgs(t *testing.T) {
|
||||
func TestFmt_manyArgs(t *testing.T) {
|
||||
tempDir := fmtFixtureWriteDir(t)
|
||||
// Add a second file
|
||||
secondSrc := `locals { x = 1 }`
|
||||
|
||||
err := ioutil.WriteFile(filepath.Join(tempDir, "second.tf"), []byte(secondSrc), 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &FmtCommand{
|
||||
Meta: Meta{
|
||||
@ -176,16 +185,21 @@ func TestFmt_tooManyArgs(t *testing.T) {
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"one",
|
||||
"two",
|
||||
filepath.Join(tempDir, "main.tf"),
|
||||
filepath.Join(tempDir, "second.tf"),
|
||||
}
|
||||
if code := c.Run(args); code != 1 {
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("wrong exit code. errors: \n%s", ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
expected := "The fmt command expects at most one argument."
|
||||
if actual := ui.ErrorWriter.String(); !strings.Contains(actual, expected) {
|
||||
t.Fatalf("expected:\n%s\n\nto include: %q", actual, expected)
|
||||
got, err := filepath.Abs(strings.TrimSpace(ui.OutputWriter.String()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := filepath.Join(tempDir, fmtFixture.filename)
|
||||
|
||||
if got != want {
|
||||
t.Fatalf("wrong output\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,7 +278,7 @@ func appendSourceSnippets(buf *bytes.Buffer, diag *viewsjson.Diagnostic, color *
|
||||
)
|
||||
}
|
||||
|
||||
if len(snippet.Values) > 0 {
|
||||
if len(snippet.Values) > 0 || (snippet.FunctionCall != nil && snippet.FunctionCall.Signature != nil) {
|
||||
// The diagnostic may also have information about the dynamic
|
||||
// values of relevant variables at the point of evaluation.
|
||||
// This is particularly useful for expressions that get evaluated
|
||||
@ -291,6 +291,24 @@ func appendSourceSnippets(buf *bytes.Buffer, diag *viewsjson.Diagnostic, color *
|
||||
})
|
||||
|
||||
fmt.Fprint(buf, color.Color(" [dark_gray]├────────────────[reset]\n"))
|
||||
if callInfo := snippet.FunctionCall; callInfo != nil && callInfo.Signature != nil {
|
||||
|
||||
fmt.Fprintf(buf, color.Color(" [dark_gray]│[reset] while calling [bold]%s[reset]("), callInfo.CalledAs)
|
||||
for i, param := range callInfo.Signature.Params {
|
||||
if i > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteString(param.Name)
|
||||
}
|
||||
if param := callInfo.Signature.VariadicParam; param != nil {
|
||||
if len(callInfo.Signature.Params) > 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteString(param.Name)
|
||||
buf.WriteString("...")
|
||||
}
|
||||
buf.WriteString(")\n")
|
||||
}
|
||||
for _, value := range values {
|
||||
fmt.Fprintf(buf, color.Color(" [dark_gray]│[reset] [bold]%s[reset] %s\n"), value.Traversal, value.Statement)
|
||||
}
|
||||
|
@ -6,9 +6,11 @@ import (
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/hashicorp/hcl/v2/hcltest"
|
||||
"github.com/mitchellh/colorstring"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
|
||||
viewsjson "github.com/hashicorp/terraform/internal/command/views/json"
|
||||
"github.com/hashicorp/terraform/internal/lang/marks"
|
||||
@ -128,6 +130,7 @@ func TestDiagnostic(t *testing.T) {
|
||||
}),
|
||||
},
|
||||
},
|
||||
Extra: diagnosticCausedBySensitive(true),
|
||||
},
|
||||
`[red]╷[reset]
|
||||
[red]│[reset] [bold][red]Error: [reset][bold]Bad bad bad[reset]
|
||||
@ -162,6 +165,7 @@ func TestDiagnostic(t *testing.T) {
|
||||
}),
|
||||
},
|
||||
},
|
||||
Extra: diagnosticCausedByUnknown(true),
|
||||
},
|
||||
`[red]╷[reset]
|
||||
[red]│[reset] [bold][red]Error: [reset][bold]Bad bad bad[reset]
|
||||
@ -196,6 +200,7 @@ func TestDiagnostic(t *testing.T) {
|
||||
}),
|
||||
},
|
||||
},
|
||||
Extra: diagnosticCausedByUnknown(true),
|
||||
},
|
||||
`[red]╷[reset]
|
||||
[red]│[reset] [bold][red]Error: [reset][bold]Bad bad bad[reset]
|
||||
@ -207,6 +212,54 @@ func TestDiagnostic(t *testing.T) {
|
||||
[red]│[reset]
|
||||
[red]│[reset] Whatever shall we do?
|
||||
[red]╵[reset]
|
||||
`,
|
||||
},
|
||||
"error with source code subject and function call annotation": {
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Bad bad bad",
|
||||
Detail: "Whatever shall we do?",
|
||||
Subject: &hcl.Range{
|
||||
Filename: "test.tf",
|
||||
Start: hcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||||
End: hcl.Pos{Line: 1, Column: 12, Byte: 11},
|
||||
},
|
||||
Expression: hcltest.MockExprLiteral(cty.True),
|
||||
EvalContext: &hcl.EvalContext{
|
||||
Functions: map[string]function.Function{
|
||||
"beep": function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "pos_param_0",
|
||||
Type: cty.String,
|
||||
},
|
||||
{
|
||||
Name: "pos_param_1",
|
||||
Type: cty.Number,
|
||||
},
|
||||
},
|
||||
VarParam: &function.Parameter{
|
||||
Name: "var_param",
|
||||
Type: cty.Bool,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
// This is simulating what the HCL function call expression
|
||||
// type would generate on evaluation, by implementing the
|
||||
// same interface it uses.
|
||||
Extra: fakeDiagFunctionCallExtra("beep"),
|
||||
},
|
||||
`[red]╷[reset]
|
||||
[red]│[reset] [bold][red]Error: [reset][bold]Bad bad bad[reset]
|
||||
[red]│[reset]
|
||||
[red]│[reset] on test.tf line 1:
|
||||
[red]│[reset] 1: test [underline]source[reset] code
|
||||
[red]│[reset] [dark_gray]├────────────────[reset]
|
||||
[red]│[reset] [dark_gray]│[reset] while calling [bold]beep[reset](pos_param_0, pos_param_1, var_param...)
|
||||
[red]│[reset]
|
||||
[red]│[reset] Whatever shall we do?
|
||||
[red]╵[reset]
|
||||
`,
|
||||
},
|
||||
}
|
||||
@ -341,6 +394,7 @@ Whatever shall we do?
|
||||
}),
|
||||
},
|
||||
},
|
||||
Extra: diagnosticCausedBySensitive(true),
|
||||
},
|
||||
`
|
||||
Error: Bad bad bad
|
||||
@ -350,10 +404,75 @@ Error: Bad bad bad
|
||||
├────────────────
|
||||
│ boop.beep has a sensitive value
|
||||
|
||||
Whatever shall we do?
|
||||
`,
|
||||
},
|
||||
"error with source code subject and expression referring to sensitive value when not related to sensitivity": {
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Bad bad bad",
|
||||
Detail: "Whatever shall we do?",
|
||||
Subject: &hcl.Range{
|
||||
Filename: "test.tf",
|
||||
Start: hcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||||
End: hcl.Pos{Line: 1, Column: 12, Byte: 11},
|
||||
},
|
||||
Expression: hcltest.MockExprTraversal(hcl.Traversal{
|
||||
hcl.TraverseRoot{Name: "boop"},
|
||||
hcl.TraverseAttr{Name: "beep"},
|
||||
}),
|
||||
EvalContext: &hcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"boop": cty.ObjectVal(map[string]cty.Value{
|
||||
"beep": cty.StringVal("blah").Mark(marks.Sensitive),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
`
|
||||
Error: Bad bad bad
|
||||
|
||||
on test.tf line 1:
|
||||
1: test source code
|
||||
|
||||
Whatever shall we do?
|
||||
`,
|
||||
},
|
||||
"error with source code subject and unknown string expression": {
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Bad bad bad",
|
||||
Detail: "Whatever shall we do?",
|
||||
Subject: &hcl.Range{
|
||||
Filename: "test.tf",
|
||||
Start: hcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||||
End: hcl.Pos{Line: 1, Column: 12, Byte: 11},
|
||||
},
|
||||
Expression: hcltest.MockExprTraversal(hcl.Traversal{
|
||||
hcl.TraverseRoot{Name: "boop"},
|
||||
hcl.TraverseAttr{Name: "beep"},
|
||||
}),
|
||||
EvalContext: &hcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"boop": cty.ObjectVal(map[string]cty.Value{
|
||||
"beep": cty.UnknownVal(cty.String),
|
||||
}),
|
||||
},
|
||||
},
|
||||
Extra: diagnosticCausedByUnknown(true),
|
||||
},
|
||||
`
|
||||
Error: Bad bad bad
|
||||
|
||||
on test.tf line 1:
|
||||
1: test source code
|
||||
├────────────────
|
||||
│ boop.beep is a string, known only after apply
|
||||
|
||||
Whatever shall we do?
|
||||
`,
|
||||
},
|
||||
"error with source code subject and unknown string expression when problem isn't unknown-related": {
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Bad bad bad",
|
||||
@ -381,12 +500,46 @@ Error: Bad bad bad
|
||||
on test.tf line 1:
|
||||
1: test source code
|
||||
├────────────────
|
||||
│ boop.beep is a string, known only after apply
|
||||
│ boop.beep is a string
|
||||
|
||||
Whatever shall we do?
|
||||
`,
|
||||
},
|
||||
"error with source code subject and unknown expression of unknown type": {
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Bad bad bad",
|
||||
Detail: "Whatever shall we do?",
|
||||
Subject: &hcl.Range{
|
||||
Filename: "test.tf",
|
||||
Start: hcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||||
End: hcl.Pos{Line: 1, Column: 12, Byte: 11},
|
||||
},
|
||||
Expression: hcltest.MockExprTraversal(hcl.Traversal{
|
||||
hcl.TraverseRoot{Name: "boop"},
|
||||
hcl.TraverseAttr{Name: "beep"},
|
||||
}),
|
||||
EvalContext: &hcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"boop": cty.ObjectVal(map[string]cty.Value{
|
||||
"beep": cty.UnknownVal(cty.DynamicPseudoType),
|
||||
}),
|
||||
},
|
||||
},
|
||||
Extra: diagnosticCausedByUnknown(true),
|
||||
},
|
||||
`
|
||||
Error: Bad bad bad
|
||||
|
||||
on test.tf line 1:
|
||||
1: test source code
|
||||
├────────────────
|
||||
│ boop.beep will be known only after apply
|
||||
|
||||
Whatever shall we do?
|
||||
`,
|
||||
},
|
||||
"error with source code subject and unknown expression of unknown type when problem isn't unknown-related": {
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Bad bad bad",
|
||||
@ -413,8 +566,6 @@ Error: Bad bad bad
|
||||
|
||||
on test.tf line 1:
|
||||
1: test source code
|
||||
├────────────────
|
||||
│ boop.beep will be known only after apply
|
||||
|
||||
Whatever shall we do?
|
||||
`,
|
||||
@ -755,3 +906,40 @@ func TestDiagnosticFromJSON_invalid(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// fakeDiagFunctionCallExtra is a fake implementation of the interface that
|
||||
// HCL uses to provide "extra information" associated with diagnostics that
|
||||
// describe errors during a function call.
|
||||
type fakeDiagFunctionCallExtra string
|
||||
|
||||
var _ hclsyntax.FunctionCallDiagExtra = fakeDiagFunctionCallExtra("")
|
||||
|
||||
func (e fakeDiagFunctionCallExtra) CalledFunctionName() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e fakeDiagFunctionCallExtra) FunctionCallError() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// diagnosticCausedByUnknown is a testing helper for exercising our logic
|
||||
// for selectively showing unknown values alongside our source snippets for
|
||||
// diagnostics that are explicitly marked as being caused by unknown values.
|
||||
type diagnosticCausedByUnknown bool
|
||||
|
||||
var _ tfdiags.DiagnosticExtraBecauseUnknown = diagnosticCausedByUnknown(true)
|
||||
|
||||
func (e diagnosticCausedByUnknown) DiagnosticCausedByUnknown() bool {
|
||||
return bool(e)
|
||||
}
|
||||
|
||||
// diagnosticCausedBySensitive is a testing helper for exercising our logic
|
||||
// for selectively showing sensitive values alongside our source snippets for
|
||||
// diagnostics that are explicitly marked as being caused by sensitive values.
|
||||
type diagnosticCausedBySensitive bool
|
||||
|
||||
var _ tfdiags.DiagnosticExtraBecauseSensitive = diagnosticCausedBySensitive(true)
|
||||
|
||||
func (e diagnosticCausedBySensitive) DiagnosticCausedBySensitive() bool {
|
||||
return bool(e)
|
||||
}
|
||||
|
@ -310,7 +310,7 @@ func (p *blockBodyDiffPrinter) writeBlockBodyDiff(schema *configschema.Block, ol
|
||||
if result.skippedBlocks == 1 {
|
||||
noun = "block"
|
||||
}
|
||||
p.buf.WriteString("\n")
|
||||
p.buf.WriteString("\n\n")
|
||||
p.buf.WriteString(strings.Repeat(" ", indent+2))
|
||||
p.buf.WriteString(fmt.Sprintf(p.color.Color("[dark_gray]# (%d unchanged %s hidden)[reset]"), result.skippedBlocks, noun))
|
||||
}
|
||||
@ -326,8 +326,6 @@ func (p *blockBodyDiffPrinter) writeAttrsDiff(
|
||||
path cty.Path,
|
||||
result *blockBodyDiffResult) bool {
|
||||
|
||||
blankBeforeBlocks := false
|
||||
|
||||
attrNames := make([]string, 0, len(attrsS))
|
||||
displayAttrNames := make(map[string]string, len(attrsS))
|
||||
attrNameLen := 0
|
||||
@ -349,8 +347,8 @@ func (p *blockBodyDiffPrinter) writeAttrsDiff(
|
||||
}
|
||||
}
|
||||
sort.Strings(attrNames)
|
||||
if len(attrNames) > 0 {
|
||||
blankBeforeBlocks = true
|
||||
if len(attrNames) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, name := range attrNames {
|
||||
@ -365,7 +363,7 @@ func (p *blockBodyDiffPrinter) writeAttrsDiff(
|
||||
}
|
||||
}
|
||||
|
||||
return blankBeforeBlocks
|
||||
return true
|
||||
}
|
||||
|
||||
// getPlanActionAndShow returns the action value
|
||||
@ -754,10 +752,7 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config
|
||||
action = plans.Update
|
||||
}
|
||||
|
||||
if blankBefore {
|
||||
p.buf.WriteRune('\n')
|
||||
}
|
||||
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, action, old, new, indent, path)
|
||||
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, action, old, new, indent, blankBefore, path)
|
||||
if skipped {
|
||||
return 1
|
||||
}
|
||||
@ -790,10 +785,7 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config
|
||||
commonLen = len(newItems)
|
||||
}
|
||||
|
||||
if blankBefore && (len(oldItems) > 0 || len(newItems) > 0) {
|
||||
p.buf.WriteRune('\n')
|
||||
}
|
||||
|
||||
blankBeforeInner := blankBefore
|
||||
for i := 0; i < commonLen; i++ {
|
||||
path := append(path, cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
|
||||
oldItem := oldItems[i]
|
||||
@ -802,27 +794,33 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config
|
||||
if oldItem.RawEquals(newItem) {
|
||||
action = plans.NoOp
|
||||
}
|
||||
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, action, oldItem, newItem, indent, path)
|
||||
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, action, oldItem, newItem, indent, blankBeforeInner, path)
|
||||
if skipped {
|
||||
skippedBlocks++
|
||||
} else {
|
||||
blankBeforeInner = false
|
||||
}
|
||||
}
|
||||
for i := commonLen; i < len(oldItems); i++ {
|
||||
path := append(path, cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
|
||||
oldItem := oldItems[i]
|
||||
newItem := cty.NullVal(oldItem.Type())
|
||||
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, plans.Delete, oldItem, newItem, indent, path)
|
||||
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, plans.Delete, oldItem, newItem, indent, blankBeforeInner, path)
|
||||
if skipped {
|
||||
skippedBlocks++
|
||||
} else {
|
||||
blankBeforeInner = false
|
||||
}
|
||||
}
|
||||
for i := commonLen; i < len(newItems); i++ {
|
||||
path := append(path, cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
|
||||
newItem := newItems[i]
|
||||
oldItem := cty.NullVal(newItem.Type())
|
||||
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, plans.Create, oldItem, newItem, indent, path)
|
||||
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, plans.Create, oldItem, newItem, indent, blankBeforeInner, path)
|
||||
if skipped {
|
||||
skippedBlocks++
|
||||
} else {
|
||||
blankBeforeInner = false
|
||||
}
|
||||
}
|
||||
case configschema.NestingSet:
|
||||
@ -845,10 +843,7 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config
|
||||
allItems = append(allItems, newItems...)
|
||||
all := cty.SetVal(allItems)
|
||||
|
||||
if blankBefore {
|
||||
p.buf.WriteRune('\n')
|
||||
}
|
||||
|
||||
blankBeforeInner := blankBefore
|
||||
for it := all.ElementIterator(); it.Next(); {
|
||||
_, val := it.Element()
|
||||
var action plans.Action
|
||||
@ -871,9 +866,11 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config
|
||||
newValue = val
|
||||
}
|
||||
path := append(path, cty.IndexStep{Key: val})
|
||||
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, action, oldValue, newValue, indent, path)
|
||||
skipped := p.writeNestedBlockDiff(name, nil, &blockS.Block, action, oldValue, newValue, indent, blankBeforeInner, path)
|
||||
if skipped {
|
||||
skippedBlocks++
|
||||
} else {
|
||||
blankBeforeInner = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -904,10 +901,7 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config
|
||||
}
|
||||
sort.Strings(allKeysOrder)
|
||||
|
||||
if blankBefore {
|
||||
p.buf.WriteRune('\n')
|
||||
}
|
||||
|
||||
blankBeforeInner := blankBefore
|
||||
for _, k := range allKeysOrder {
|
||||
var action plans.Action
|
||||
oldValue := oldItems[k]
|
||||
@ -926,9 +920,11 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *config
|
||||
}
|
||||
|
||||
path := append(path, cty.IndexStep{Key: cty.StringVal(k)})
|
||||
skipped := p.writeNestedBlockDiff(name, &k, &blockS.Block, action, oldValue, newValue, indent, path)
|
||||
skipped := p.writeNestedBlockDiff(name, &k, &blockS.Block, action, oldValue, newValue, indent, blankBeforeInner, path)
|
||||
if skipped {
|
||||
skippedBlocks++
|
||||
} else {
|
||||
blankBeforeInner = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -974,11 +970,15 @@ func (p *blockBodyDiffPrinter) writeSensitiveNestedBlockDiff(name string, old, n
|
||||
p.buf.WriteString("}")
|
||||
}
|
||||
|
||||
func (p *blockBodyDiffPrinter) writeNestedBlockDiff(name string, label *string, blockS *configschema.Block, action plans.Action, old, new cty.Value, indent int, path cty.Path) bool {
|
||||
func (p *blockBodyDiffPrinter) writeNestedBlockDiff(name string, label *string, blockS *configschema.Block, action plans.Action, old, new cty.Value, indent int, blankBefore bool, path cty.Path) bool {
|
||||
if action == plans.NoOp && !p.verbose {
|
||||
return true
|
||||
}
|
||||
|
||||
if blankBefore {
|
||||
p.buf.WriteRune('\n')
|
||||
}
|
||||
|
||||
p.buf.WriteString("\n")
|
||||
p.buf.WriteString(strings.Repeat(" ", indent))
|
||||
p.writeActionSymbol(action)
|
||||
|
@ -3744,6 +3744,7 @@ func TestResourceChange_nestedMap(t *testing.T) {
|
||||
+ new_field = "new_value"
|
||||
+ volume_type = "gp2"
|
||||
}
|
||||
|
||||
# (1 unchanged block hidden)
|
||||
}
|
||||
`,
|
||||
@ -3809,6 +3810,7 @@ func TestResourceChange_nestedMap(t *testing.T) {
|
||||
~ root_block_device "a" { # forces replacement
|
||||
~ volume_type = "gp2" -> "different"
|
||||
}
|
||||
|
||||
# (1 unchanged block hidden)
|
||||
}
|
||||
`,
|
||||
@ -3971,6 +3973,599 @@ func TestResourceChange_nestedMap(t *testing.T) {
|
||||
|
||||
# (1 unchanged block hidden)
|
||||
}
|
||||
`,
|
||||
},
|
||||
"in-place update - multiple unchanged blocks": {
|
||||
Action: plans.Update,
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Before: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-BEFORE"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
After: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-AFTER"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
RequiredReplace: cty.NewPathSet(),
|
||||
Schema: testSchema(configschema.NestingMap),
|
||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||
~ resource "test_instance" "example" {
|
||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||
id = "i-02ae66f368e8518a9"
|
||||
# (1 unchanged attribute hidden)
|
||||
|
||||
# (2 unchanged blocks hidden)
|
||||
}
|
||||
`,
|
||||
},
|
||||
"in-place update - multiple blocks first changed": {
|
||||
Action: plans.Update,
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Before: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-BEFORE"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
After: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-AFTER"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp3"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
RequiredReplace: cty.NewPathSet(),
|
||||
Schema: testSchema(configschema.NestingMap),
|
||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||
~ resource "test_instance" "example" {
|
||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||
id = "i-02ae66f368e8518a9"
|
||||
# (1 unchanged attribute hidden)
|
||||
|
||||
~ root_block_device "b" {
|
||||
~ volume_type = "gp2" -> "gp3"
|
||||
}
|
||||
|
||||
# (1 unchanged block hidden)
|
||||
}
|
||||
`,
|
||||
},
|
||||
"in-place update - multiple blocks second changed": {
|
||||
Action: plans.Update,
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Before: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-BEFORE"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
After: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-AFTER"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp3"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
RequiredReplace: cty.NewPathSet(),
|
||||
Schema: testSchema(configschema.NestingMap),
|
||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||
~ resource "test_instance" "example" {
|
||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||
id = "i-02ae66f368e8518a9"
|
||||
# (1 unchanged attribute hidden)
|
||||
|
||||
~ root_block_device "a" {
|
||||
~ volume_type = "gp2" -> "gp3"
|
||||
}
|
||||
|
||||
# (1 unchanged block hidden)
|
||||
}
|
||||
`,
|
||||
},
|
||||
"in-place update - multiple blocks changed": {
|
||||
Action: plans.Update,
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Before: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-BEFORE"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
After: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-AFTER"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp3"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp3"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
RequiredReplace: cty.NewPathSet(),
|
||||
Schema: testSchema(configschema.NestingMap),
|
||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||
~ resource "test_instance" "example" {
|
||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||
id = "i-02ae66f368e8518a9"
|
||||
# (1 unchanged attribute hidden)
|
||||
|
||||
~ root_block_device "a" {
|
||||
~ volume_type = "gp2" -> "gp3"
|
||||
}
|
||||
~ root_block_device "b" {
|
||||
~ volume_type = "gp2" -> "gp3"
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
"in-place update - multiple different unchanged blocks": {
|
||||
Action: plans.Update,
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Before: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-BEFORE"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
"leaf_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
After: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-AFTER"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
"leaf_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
RequiredReplace: cty.NewPathSet(),
|
||||
Schema: testSchemaMultipleBlocks(configschema.NestingMap),
|
||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||
~ resource "test_instance" "example" {
|
||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||
id = "i-02ae66f368e8518a9"
|
||||
# (1 unchanged attribute hidden)
|
||||
|
||||
# (2 unchanged blocks hidden)
|
||||
}
|
||||
`,
|
||||
},
|
||||
"in-place update - multiple different blocks first changed": {
|
||||
Action: plans.Update,
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Before: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-BEFORE"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
"leaf_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
After: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-AFTER"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
"leaf_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp3"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
RequiredReplace: cty.NewPathSet(),
|
||||
Schema: testSchemaMultipleBlocks(configschema.NestingMap),
|
||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||
~ resource "test_instance" "example" {
|
||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||
id = "i-02ae66f368e8518a9"
|
||||
# (1 unchanged attribute hidden)
|
||||
|
||||
~ leaf_block_device "b" {
|
||||
~ volume_type = "gp2" -> "gp3"
|
||||
}
|
||||
|
||||
# (1 unchanged block hidden)
|
||||
}
|
||||
`,
|
||||
},
|
||||
"in-place update - multiple different blocks second changed": {
|
||||
Action: plans.Update,
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Before: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-BEFORE"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
"leaf_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
After: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-AFTER"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp3"),
|
||||
}),
|
||||
}),
|
||||
"leaf_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
RequiredReplace: cty.NewPathSet(),
|
||||
Schema: testSchemaMultipleBlocks(configschema.NestingMap),
|
||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||
~ resource "test_instance" "example" {
|
||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||
id = "i-02ae66f368e8518a9"
|
||||
# (1 unchanged attribute hidden)
|
||||
|
||||
~ root_block_device "a" {
|
||||
~ volume_type = "gp2" -> "gp3"
|
||||
}
|
||||
|
||||
# (1 unchanged block hidden)
|
||||
}
|
||||
`,
|
||||
},
|
||||
"in-place update - multiple different blocks changed": {
|
||||
Action: plans.Update,
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Before: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-BEFORE"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
"leaf_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
After: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-AFTER"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp3"),
|
||||
}),
|
||||
}),
|
||||
"leaf_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp3"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
RequiredReplace: cty.NewPathSet(),
|
||||
Schema: testSchemaMultipleBlocks(configschema.NestingMap),
|
||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||
~ resource "test_instance" "example" {
|
||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||
id = "i-02ae66f368e8518a9"
|
||||
# (1 unchanged attribute hidden)
|
||||
|
||||
~ leaf_block_device "b" {
|
||||
~ volume_type = "gp2" -> "gp3"
|
||||
}
|
||||
|
||||
~ root_block_device "a" {
|
||||
~ volume_type = "gp2" -> "gp3"
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
"in-place update - mixed blocks unchanged": {
|
||||
Action: plans.Update,
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Before: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-BEFORE"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
"leaf_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
After: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-AFTER"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
"leaf_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
RequiredReplace: cty.NewPathSet(),
|
||||
Schema: testSchemaMultipleBlocks(configschema.NestingMap),
|
||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||
~ resource "test_instance" "example" {
|
||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||
id = "i-02ae66f368e8518a9"
|
||||
# (1 unchanged attribute hidden)
|
||||
|
||||
# (4 unchanged blocks hidden)
|
||||
}
|
||||
`,
|
||||
},
|
||||
"in-place update - mixed blocks changed": {
|
||||
Action: plans.Update,
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Before: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-BEFORE"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
"leaf_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
After: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("i-02ae66f368e8518a9"),
|
||||
"ami": cty.StringVal("ami-AFTER"),
|
||||
"disks": cty.MapVal(map[string]cty.Value{
|
||||
"disk_a": cty.ObjectVal(map[string]cty.Value{
|
||||
"mount_point": cty.StringVal("/var/diska"),
|
||||
"size": cty.StringVal("50GB"),
|
||||
}),
|
||||
}),
|
||||
"root_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp3"),
|
||||
}),
|
||||
}),
|
||||
"leaf_block_device": cty.MapVal(map[string]cty.Value{
|
||||
"a": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp2"),
|
||||
}),
|
||||
"b": cty.ObjectVal(map[string]cty.Value{
|
||||
"volume_type": cty.StringVal("gp3"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
RequiredReplace: cty.NewPathSet(),
|
||||
Schema: testSchemaMultipleBlocks(configschema.NestingMap),
|
||||
ExpectedOutput: ` # test_instance.example will be updated in-place
|
||||
~ resource "test_instance" "example" {
|
||||
~ ami = "ami-BEFORE" -> "ami-AFTER"
|
||||
id = "i-02ae66f368e8518a9"
|
||||
# (1 unchanged attribute hidden)
|
||||
|
||||
~ leaf_block_device "b" {
|
||||
~ volume_type = "gp2" -> "gp3"
|
||||
}
|
||||
|
||||
~ root_block_device "b" {
|
||||
~ volume_type = "gp2" -> "gp3"
|
||||
}
|
||||
|
||||
# (2 unchanged blocks hidden)
|
||||
}
|
||||
`,
|
||||
},
|
||||
}
|
||||
@ -5459,6 +6054,50 @@ func testSchema(nesting configschema.NestingMode) *configschema.Block {
|
||||
}
|
||||
}
|
||||
|
||||
func testSchemaMultipleBlocks(nesting configschema.NestingMode) *configschema.Block {
|
||||
return &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {Type: cty.String, Optional: true, Computed: true},
|
||||
"ami": {Type: cty.String, Optional: true},
|
||||
"disks": {
|
||||
NestedType: &configschema.Object{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"mount_point": {Type: cty.String, Optional: true},
|
||||
"size": {Type: cty.String, Optional: true},
|
||||
},
|
||||
Nesting: nesting,
|
||||
},
|
||||
},
|
||||
},
|
||||
BlockTypes: map[string]*configschema.NestedBlock{
|
||||
"root_block_device": {
|
||||
Block: configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"volume_type": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Nesting: nesting,
|
||||
},
|
||||
"leaf_block_device": {
|
||||
Block: configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"volume_type": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Nesting: nesting,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// similar to testSchema with the addition of a "new_field" block
|
||||
func testSchemaPlus(nesting configschema.NestingMode) *configschema.Block {
|
||||
return &configschema.Block{
|
||||
|
@ -45,7 +45,6 @@ func (c *ImportCommand) Run(args []string) int {
|
||||
cmdFlags.StringVar(&configPath, "config", pwd, "path")
|
||||
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
|
||||
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
|
||||
cmdFlags.BoolVar(&c.Meta.allowMissingConfig, "allow-missing-config", false, "allow missing config")
|
||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
return 1
|
||||
@ -135,7 +134,7 @@ func (c *ImportCommand) Run(args []string) int {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !c.Meta.allowMissingConfig && rc == nil {
|
||||
if rc == nil {
|
||||
modulePath := addr.Module.String()
|
||||
if modulePath == "" {
|
||||
modulePath = "the root module"
|
||||
@ -262,10 +261,6 @@ func (c *ImportCommand) Run(args []string) int {
|
||||
|
||||
c.Ui.Output(c.Colorize().Color("[reset][green]\n" + importCommandSuccessMsg))
|
||||
|
||||
if c.Meta.allowMissingConfig && rc == nil {
|
||||
c.Ui.Output(c.Colorize().Color("[reset][yellow]\n" + importCommandAllowMissingResourceMsg))
|
||||
}
|
||||
|
||||
c.showDiagnostics(diags)
|
||||
if diags.HasErrors() {
|
||||
return 1
|
||||
@ -310,8 +305,6 @@ Options:
|
||||
If no config files are present, they must be provided
|
||||
via the input prompts or env vars.
|
||||
|
||||
-allow-missing-config Allow import when no resource configuration block exists.
|
||||
|
||||
-input=false Disable interactive input prompts.
|
||||
|
||||
-lock=false Don't hold a state lock during the operation. This is
|
||||
@ -361,12 +354,3 @@ const importCommandSuccessMsg = `Import successful!
|
||||
The resources that were imported are shown above. These resources are now in
|
||||
your Terraform state and will henceforth be managed by Terraform.
|
||||
`
|
||||
|
||||
const importCommandAllowMissingResourceMsg = `Import does not generate resource configuration, you must create a resource
|
||||
configuration block that matches the current or desired state manually.
|
||||
|
||||
If there is no matching resource configuration block for the imported
|
||||
resource, Terraform will delete the resource on the next "terraform apply".
|
||||
It is recommended that you run "terraform plan" to verify that the
|
||||
configuration is correct and complete.
|
||||
`
|
||||
|
@ -644,63 +644,6 @@ func TestImport_providerConfigWithVarFile(t *testing.T) {
|
||||
testStateOutput(t, statePath, testImportStr)
|
||||
}
|
||||
|
||||
func TestImport_allowMissingResourceConfig(t *testing.T) {
|
||||
defer testChdir(t, testFixturePath("import-missing-resource-config"))()
|
||||
|
||||
statePath := testTempFile(t)
|
||||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
view, _ := testView(t)
|
||||
c := &ImportCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
Ui: ui,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
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.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||
ResourceTypes: map[string]providers.Schema{
|
||||
"test_instance": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {Type: cty.String, Optional: true, Computed: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-state", statePath,
|
||||
"-allow-missing-config",
|
||||
"test_instance.foo",
|
||||
"bar",
|
||||
}
|
||||
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
if !p.ImportResourceStateCalled {
|
||||
t.Fatal("ImportResourceState should be called")
|
||||
}
|
||||
|
||||
testStateOutput(t, statePath, testImportStr)
|
||||
}
|
||||
|
||||
func TestImport_emptyConfig(t *testing.T) {
|
||||
defer testChdir(t, testFixturePath("empty"))()
|
||||
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
@ -424,10 +426,15 @@ func (c *InitCommand) initBackend(root *configs.Module, extraConfig rawFlags) (b
|
||||
|
||||
bf := backendInit.Backend(backendType)
|
||||
if bf == nil {
|
||||
detail := fmt.Sprintf("There is no backend type named %q.", backendType)
|
||||
if msg, removed := backendInit.RemovedBackends[backendType]; removed {
|
||||
detail = msg
|
||||
}
|
||||
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported backend type",
|
||||
Detail: fmt.Sprintf("There is no backend type named %q.", backendType),
|
||||
Detail: detail,
|
||||
Subject: &root.Backend.TypeRange,
|
||||
})
|
||||
return nil, true, diags
|
||||
@ -543,6 +550,11 @@ func (c *InitCommand) getProviders(config *configs.Config, state *states.State,
|
||||
ctx, done := c.InterruptibleContext()
|
||||
defer done()
|
||||
|
||||
// We want to print out a nice warning if we don't manage to pull
|
||||
// checksums for all our providers. This is tracked via callbacks
|
||||
// and incomplete providers are stored here for later analysis.
|
||||
var incompleteProviders []string
|
||||
|
||||
// Because we're currently just streaming a series of events sequentially
|
||||
// into the terminal, we're showing only a subset of the events to keep
|
||||
// things relatively concise. Later it'd be nice to have a progress UI
|
||||
@ -784,6 +796,41 @@ func (c *InitCommand) getProviders(config *configs.Config, state *states.State,
|
||||
|
||||
c.Ui.Info(fmt.Sprintf("- Installed %s v%s (%s%s)", provider.ForDisplay(), version, authResult, keyID))
|
||||
},
|
||||
ProvidersLockUpdated: func(provider addrs.Provider, version getproviders.Version, localHashes []getproviders.Hash, signedHashes []getproviders.Hash, priorHashes []getproviders.Hash) {
|
||||
// We're going to use this opportunity to track if we have any
|
||||
// "incomplete" installs of providers. An incomplete install is
|
||||
// when we are only going to write the local hashes into our lock
|
||||
// file which means a `terraform init` command will fail in future
|
||||
// when used on machines of a different architecture.
|
||||
//
|
||||
// We want to print a warning about this.
|
||||
|
||||
if len(signedHashes) > 0 {
|
||||
// If we have any signedHashes hashes then we don't worry - as
|
||||
// we know we retrieved all available hashes for this version
|
||||
// anyway.
|
||||
return
|
||||
}
|
||||
|
||||
// If local hashes and prior hashes are exactly the same then
|
||||
// it means we didn't record any signed hashes previously, and
|
||||
// we know we're not adding any extra in now (because we already
|
||||
// checked the signedHashes), so that's a problem.
|
||||
//
|
||||
// In the actual check here, if we have any priorHashes and those
|
||||
// hashes are not the same as the local hashes then we're going to
|
||||
// accept that this provider has been configured correctly.
|
||||
if len(priorHashes) > 0 && !reflect.DeepEqual(localHashes, priorHashes) {
|
||||
return
|
||||
}
|
||||
|
||||
// Now, either signedHashes is empty, or priorHashes is exactly the
|
||||
// same as our localHashes which means we never retrieved the
|
||||
// signedHashes previously.
|
||||
//
|
||||
// Either way, this is bad. Let's complain/warn.
|
||||
incompleteProviders = append(incompleteProviders, provider.ForDisplay())
|
||||
},
|
||||
ProvidersFetched: func(authResults map[addrs.Provider]*getproviders.PackageAuthenticationResult) {
|
||||
thirdPartySigned := false
|
||||
for _, authResult := range authResults {
|
||||
@ -798,18 +845,6 @@ func (c *InitCommand) getProviders(config *configs.Config, state *states.State,
|
||||
"https://www.terraform.io/docs/cli/plugins/signing.html"))
|
||||
}
|
||||
},
|
||||
HashPackageFailure: func(provider addrs.Provider, version getproviders.Version, err error) {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to validate installed provider",
|
||||
fmt.Sprintf(
|
||||
"Validating provider %s v%s failed: %s",
|
||||
provider.ForDisplay(),
|
||||
version,
|
||||
err,
|
||||
),
|
||||
))
|
||||
},
|
||||
}
|
||||
ctx = evts.OnContext(ctx)
|
||||
|
||||
@ -869,6 +904,22 @@ func (c *InitCommand) getProviders(config *configs.Config, state *states.State,
|
||||
return true, false, diags
|
||||
}
|
||||
|
||||
// Jump in here and add a warning if any of the providers are incomplete.
|
||||
if len(incompleteProviders) > 0 {
|
||||
// We don't really care about the order here, we just want the
|
||||
// output to be deterministic.
|
||||
sort.Slice(incompleteProviders, func(i, j int) bool {
|
||||
return incompleteProviders[i] < incompleteProviders[j]
|
||||
})
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
incompleteLockFileInformationHeader,
|
||||
fmt.Sprintf(
|
||||
incompleteLockFileInformationBody,
|
||||
strings.Join(incompleteProviders, "\n - "),
|
||||
getproviders.CurrentPlatform.String())))
|
||||
}
|
||||
|
||||
if previousLocks.Empty() {
|
||||
// A change from empty to non-empty is special because it suggests
|
||||
// we're running "terraform init" for the first time against a
|
||||
@ -1190,3 +1241,18 @@ Alternatively, upgrade to the latest version of Terraform for compatibility with
|
||||
|
||||
// No version of the provider is compatible.
|
||||
const errProviderVersionIncompatible = `No compatible versions of provider %s were found.`
|
||||
|
||||
// incompleteLockFileInformationHeader is the summary displayed to users when
|
||||
// the lock file has only recorded local hashes.
|
||||
const incompleteLockFileInformationHeader = `Incomplete lock file information for providers`
|
||||
|
||||
// incompleteLockFileInformationBody is the body of text displayed to users when
|
||||
// the lock file has only recorded local hashes.
|
||||
const incompleteLockFileInformationBody = `Due to your customized provider installation methods, Terraform was forced to calculate lock file checksums locally for the following providers:
|
||||
- %s
|
||||
|
||||
The current .terraform.lock.hcl file only includes checksums for %s, so Terraform running on another platform will fail to install these providers.
|
||||
|
||||
To calculate additional checksums for another platform, run:
|
||||
terraform providers lock -platform=linux_amd64
|
||||
(where linux_amd64 is the platform to generate)`
|
||||
|
@ -1644,7 +1644,7 @@ func TestInit_providerSource(t *testing.T) {
|
||||
})
|
||||
defer close()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
ui := cli.NewMockUi()
|
||||
view, _ := testView(t)
|
||||
m := Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
@ -1726,13 +1726,16 @@ func TestInit_providerSource(t *testing.T) {
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(gotProviderLocks, wantProviderLocks, depsfile.ProviderLockComparer); diff != "" {
|
||||
t.Errorf("wrong version selections after upgrade\n%s", diff)
|
||||
}
|
||||
|
||||
outputStr := ui.OutputWriter.String()
|
||||
if want := "Installed hashicorp/test v1.2.3 (verified checksum)"; !strings.Contains(outputStr, want) {
|
||||
t.Fatalf("unexpected output: %s\nexpected to include %q", outputStr, want)
|
||||
if got, want := ui.OutputWriter.String(), "Installed hashicorp/test v1.2.3 (verified checksum)"; !strings.Contains(got, want) {
|
||||
t.Fatalf("unexpected output: %s\nexpected to include %q", got, want)
|
||||
}
|
||||
if got, want := ui.ErrorWriter.String(), "\n - hashicorp/source\n - hashicorp/test\n - hashicorp/test-beta"; !strings.Contains(got, want) {
|
||||
t.Fatalf("wrong error message\nshould contain: %s\ngot:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -442,7 +442,8 @@ func (p *plan) marshalOutputChanges(changes *plans.Changes) error {
|
||||
changeV.After, _ = changeV.After.UnmarkDeep()
|
||||
|
||||
var before, after []byte
|
||||
afterUnknown := cty.False
|
||||
var afterUnknown cty.Value
|
||||
|
||||
if changeV.Before != cty.NilVal {
|
||||
before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type())
|
||||
if err != nil {
|
||||
@ -455,8 +456,18 @@ func (p *plan) marshalOutputChanges(changes *plans.Changes) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
afterUnknown = cty.False
|
||||
} else {
|
||||
afterUnknown = cty.True
|
||||
filteredAfter := omitUnknowns(changeV.After)
|
||||
if filteredAfter.IsNull() {
|
||||
after = nil
|
||||
} else {
|
||||
after, err = ctyjson.Marshal(filteredAfter, filteredAfter.Type())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
afterUnknown = unknownAsBool(changeV.After)
|
||||
}
|
||||
}
|
||||
|
||||
@ -566,8 +577,9 @@ func omitUnknowns(val cty.Value) cty.Value {
|
||||
newVal := omitUnknowns(v)
|
||||
if newVal != cty.NilVal {
|
||||
vals = append(vals, newVal)
|
||||
} else if newVal == cty.NilVal && ty.IsListType() {
|
||||
// list length may be significant, so we will turn unknowns into nulls
|
||||
} else if newVal == cty.NilVal {
|
||||
// element order is how we correlate unknownness, so we must
|
||||
// replace unknowns with nulls
|
||||
vals = append(vals, cty.NullVal(v.Type()))
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,18 @@ func TestOmitUnknowns(t *testing.T) {
|
||||
"hello": cty.True,
|
||||
}),
|
||||
},
|
||||
{
|
||||
cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("alpha"),
|
||||
cty.UnknownVal(cty.String),
|
||||
cty.StringVal("charlie"),
|
||||
}),
|
||||
cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("alpha"),
|
||||
cty.NullVal(cty.String),
|
||||
cty.StringVal("charlie"),
|
||||
}),
|
||||
},
|
||||
{
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.StringVal("dev"),
|
||||
@ -76,6 +88,7 @@ func TestOmitUnknowns(t *testing.T) {
|
||||
cty.StringVal("dev"),
|
||||
cty.StringVal("foo"),
|
||||
cty.StringVal("stg"),
|
||||
cty.NullVal(cty.String),
|
||||
}),
|
||||
},
|
||||
{
|
||||
|
@ -159,7 +159,7 @@ func (jsonstate *state) marshalStateValues(s *states.State, schemas *terraform.S
|
||||
var err error
|
||||
|
||||
// only marshal the root module outputs
|
||||
sv.Outputs, err = marshalOutputs(s.RootModule().OutputValues)
|
||||
sv.Outputs, err = MarshalOutputs(s.RootModule().OutputValues)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -174,7 +174,9 @@ func (jsonstate *state) marshalStateValues(s *states.State, schemas *terraform.S
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshalOutputs(outputs map[string]*states.OutputValue) (map[string]output, error) {
|
||||
// MarshalOutputs translates a map of states.OutputValue to a map of jsonstate.output,
|
||||
// which are defined for json encoding.
|
||||
func MarshalOutputs(outputs map[string]*states.OutputValue) (map[string]output, error) {
|
||||
if outputs == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -6,12 +6,13 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/lang/marks"
|
||||
"github.com/hashicorp/terraform/internal/states"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestMarshalOutputs(t *testing.T) {
|
||||
@ -92,7 +93,7 @@ func TestMarshalOutputs(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got, err := marshalOutputs(test.Outputs)
|
||||
got, err := MarshalOutputs(test.Outputs)
|
||||
if test.Err {
|
||||
if err == nil {
|
||||
t.Fatal("succeeded; want error")
|
||||
|
@ -130,6 +130,24 @@ type Meta struct {
|
||||
// just trusting that someone else did it before running Terraform.
|
||||
UnmanagedProviders map[addrs.Provider]*plugin.ReattachConfig
|
||||
|
||||
// AllowExperimentalFeatures controls whether a command that embeds this
|
||||
// Meta is permitted to make use of experimental Terraform features.
|
||||
//
|
||||
// Set this field only during the initial creation of Meta. If you change
|
||||
// this field after calling methods of type Meta then the resulting
|
||||
// behavior is undefined.
|
||||
//
|
||||
// In normal code this would be set by package main only in builds
|
||||
// explicitly marked as being alpha releases or development snapshots,
|
||||
// making experimental features unavailable otherwise. Test code may
|
||||
// choose to set this if it needs to exercise experimental features.
|
||||
//
|
||||
// Some experiments predated the addition of this setting, and may
|
||||
// therefore still be available even if this flag is false. Our intent
|
||||
// is that all/most _future_ experiments will be unavailable unless this
|
||||
// flag is set, to reinforce that experiments are not for production use.
|
||||
AllowExperimentalFeatures bool
|
||||
|
||||
//----------------------------------------------------------
|
||||
// Protected: commands can set these
|
||||
//----------------------------------------------------------
|
||||
@ -213,9 +231,6 @@ type Meta struct {
|
||||
migrateState bool
|
||||
compactWarnings bool
|
||||
|
||||
// Used with the import command to allow import of state when no matching config exists.
|
||||
allowMissingConfig bool
|
||||
|
||||
// Used with commands which write state to allow users to write remote
|
||||
// state even if the remote and local Terraform versions don't match.
|
||||
ignoreRemoteVersion bool
|
||||
|
@ -466,10 +466,15 @@ func (m *Meta) backendConfig(opts *BackendOpts) (*configs.Backend, int, tfdiags.
|
||||
|
||||
bf := backendInit.Backend(c.Type)
|
||||
if bf == nil {
|
||||
detail := fmt.Sprintf("There is no backend type named %q.", c.Type)
|
||||
if msg, removed := backendInit.RemovedBackends[c.Type]; removed {
|
||||
detail = msg
|
||||
}
|
||||
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid backend type",
|
||||
Detail: fmt.Sprintf("There is no backend type named %q.", c.Type),
|
||||
Detail: detail,
|
||||
Subject: &c.TypeRange,
|
||||
})
|
||||
return nil, 0, diags
|
||||
|
@ -1,7 +1,6 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -37,7 +36,7 @@ func TestBackendMigrate_promptMultiStatePattern(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for name, tc := range cases {
|
||||
fmt.Println("Test: ", name)
|
||||
t.Log("Test: ", name)
|
||||
m := testMetaBackend(t, nil)
|
||||
input := map[string]string{}
|
||||
cleanup := testInputMap(t, input)
|
||||
|
@ -334,6 +334,7 @@ func (m *Meta) initConfigLoader() (*configload.Loader, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
loader.AllowLanguageExperiments(m.AllowExperimentalFeatures)
|
||||
m.configLoader = loader
|
||||
if m.View != nil {
|
||||
m.View.SetConfigSources(loader.Sources)
|
||||
|
@ -82,17 +82,12 @@ func (c *OutputCommand) Outputs(statePath string) (map[string]*states.OutputValu
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
if err := stateStore.RefreshState(); err != nil {
|
||||
diags = diags.Append(fmt.Errorf("Failed to load state: %s", err))
|
||||
return nil, diags
|
||||
output, err := stateStore.GetRootOutputValues()
|
||||
if err != nil {
|
||||
return nil, diags.Append(err)
|
||||
}
|
||||
|
||||
state := stateStore.State()
|
||||
if state == nil {
|
||||
state = states.NewState()
|
||||
}
|
||||
|
||||
return state.RootModule().OutputValues, nil
|
||||
return output, diags
|
||||
}
|
||||
|
||||
func (c *OutputCommand) Help() string {
|
||||
|
@ -700,6 +700,22 @@ func TestPlan_providerArgumentUnset(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
DataSources: map[string]providers.Schema{
|
||||
"test_data_source": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
"valid": {
|
||||
Type: cty.Bool,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
view, done := testView(t)
|
||||
c := &PlanCommand{
|
||||
@ -1382,6 +1398,33 @@ func TestPlan_warnings(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPlan_jsonGoldenReference(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath("plan"), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
p := planFixtureProvider()
|
||||
view, done := testView(t)
|
||||
c := &PlanCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-json",
|
||||
}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, output.Stderr())
|
||||
}
|
||||
|
||||
checkGoldenReference(t, output, "plan")
|
||||
}
|
||||
|
||||
// planFixtureSchema returns a schema suitable for processing the
|
||||
// configuration in testdata/plan . This schema should be
|
||||
// assigned to a mock provider named "test".
|
||||
@ -1408,6 +1451,22 @@ func planFixtureSchema() *providers.GetProviderSchemaResponse {
|
||||
},
|
||||
},
|
||||
},
|
||||
DataSources: map[string]providers.Schema{
|
||||
"test_data_source": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"id": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
},
|
||||
"valid": {
|
||||
Type: cty.Bool,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -1423,6 +1482,14 @@ func planFixtureProvider() *terraform.MockProvider {
|
||||
PlannedState: req.ProposedNewState,
|
||||
}
|
||||
}
|
||||
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
||||
return providers.ReadDataSourceResponse{
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("zzzzz"),
|
||||
"valid": cty.BoolVal(true),
|
||||
}),
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
@ -1456,6 +1523,14 @@ func planVarsFixtureProvider() *terraform.MockProvider {
|
||||
PlannedState: req.ProposedNewState,
|
||||
}
|
||||
}
|
||||
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
||||
return providers.ReadDataSourceResponse{
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("zzzzz"),
|
||||
"valid": cty.BoolVal(true),
|
||||
}),
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
@ -1475,6 +1550,14 @@ func planWarningsFixtureProvider() *terraform.MockProvider {
|
||||
PlannedState: req.ProposedNewState,
|
||||
}
|
||||
}
|
||||
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
|
||||
return providers.ReadDataSourceResponse{
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("zzzzz"),
|
||||
"valid": cty.BoolVal(true),
|
||||
}),
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
|
@ -4,10 +4,11 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/xlab/treeprint"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/configs"
|
||||
"github.com/hashicorp/terraform/internal/getproviders"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
"github.com/xlab/treeprint"
|
||||
)
|
||||
|
||||
// ProvidersCommand is a Command implementation that prints out information
|
||||
@ -149,7 +150,7 @@ func (c *ProvidersCommand) populateTreeNode(tree treeprint.Tree, node *configs.M
|
||||
}
|
||||
|
||||
const providersCommandHelp = `
|
||||
Usage: terraform [global options] providers [dir]
|
||||
Usage: terraform [global options] providers [DIR]
|
||||
|
||||
Prints out a tree of modules in the referenced configuration annotated with
|
||||
their provider requirements.
|
||||
|
@ -13,6 +13,14 @@ import (
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
type providersLockChangeType string
|
||||
|
||||
const (
|
||||
providersLockChangeTypeNoChange providersLockChangeType = "providersLockChangeTypeNoChange"
|
||||
providersLockChangeTypeNewProvider providersLockChangeType = "providersLockChangeTypeNewProvider"
|
||||
providersLockChangeTypeNewHashes providersLockChangeType = "providersLockChangeTypeNewHashes"
|
||||
)
|
||||
|
||||
// ProvidersLockCommand is a Command implementation that implements the
|
||||
// "terraform providers lock" command, which creates or updates the current
|
||||
// configuration's dependency lock file using information from upstream
|
||||
@ -225,7 +233,7 @@ func (c *ProvidersLockCommand) Run(args []string) int {
|
||||
if keyID != "" {
|
||||
keyID = c.Colorize().Color(fmt.Sprintf(", key ID [reset][bold]%s[reset]", keyID))
|
||||
}
|
||||
c.Ui.Output(fmt.Sprintf("- Obtained %s checksums for %s (%s%s)", provider.ForDisplay(), platform, auth, keyID))
|
||||
c.Ui.Output(fmt.Sprintf("- Retrieved %s %s for %s (%s%s)", provider.ForDisplay(), version, platform, auth, keyID))
|
||||
},
|
||||
}
|
||||
ctx := evts.OnContext(ctx)
|
||||
@ -233,7 +241,7 @@ func (c *ProvidersLockCommand) Run(args []string) int {
|
||||
dir := providercache.NewDirWithPlatform(tempDir, platform)
|
||||
installer := providercache.NewInstaller(dir, source)
|
||||
|
||||
newLocks, err := installer.EnsureProviderVersions(ctx, oldLocks, reqs, providercache.InstallNewProvidersOnly)
|
||||
newLocks, err := installer.EnsureProviderVersions(ctx, oldLocks, reqs, providercache.InstallNewProvidersForce)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
@ -252,6 +260,10 @@ func (c *ProvidersLockCommand) Run(args []string) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Track whether we've made any changes to the lock file as part of this
|
||||
// operation. We can customise the final message based on our actions.
|
||||
madeAnyChange := false
|
||||
|
||||
// We now have a separate updated locks object for each platform. We need
|
||||
// to merge those all together so that the final result has the union of
|
||||
// all of the checksums we saw for each of the providers we've worked on.
|
||||
@ -270,7 +282,7 @@ func (c *ProvidersLockCommand) Run(args []string) int {
|
||||
constraints = oldLock.VersionConstraints()
|
||||
hashes = append(hashes, oldLock.AllHashes()...)
|
||||
}
|
||||
for _, platformLocks := range updatedLocks {
|
||||
for platform, platformLocks := range updatedLocks {
|
||||
platformLock := platformLocks.Provider(provider)
|
||||
if platformLock == nil {
|
||||
continue // weird, but we'll tolerate it to avoid crashing
|
||||
@ -282,6 +294,32 @@ func (c *ProvidersLockCommand) Run(args []string) int {
|
||||
// platforms here, because the SetProvider method we call below
|
||||
// handles that automatically.
|
||||
hashes = append(hashes, platformLock.AllHashes()...)
|
||||
|
||||
// At this point, we've merged all the hashes for this (provider, platform)
|
||||
// combo into the combined hashes for this provider. Let's take this
|
||||
// opportunity to print out a summary for this particular combination.
|
||||
switch providersLockCalculateChangeType(oldLock, platformLock) {
|
||||
case providersLockChangeTypeNewProvider:
|
||||
madeAnyChange = true
|
||||
c.Ui.Output(
|
||||
fmt.Sprintf(
|
||||
"- Obtained %s checksums for %s; This was a new provider and the checksums for this platform are now tracked in the lock file",
|
||||
provider.ForDisplay(),
|
||||
platform))
|
||||
case providersLockChangeTypeNewHashes:
|
||||
madeAnyChange = true
|
||||
c.Ui.Output(
|
||||
fmt.Sprintf(
|
||||
"- Obtained %s checksums for %s; Additional checksums for this platform are now tracked in the lock file",
|
||||
provider.ForDisplay(),
|
||||
platform))
|
||||
case providersLockChangeTypeNoChange:
|
||||
c.Ui.Output(
|
||||
fmt.Sprintf(
|
||||
"- Obtained %s checksums for %s; All checksums for this platform were already tracked in the lock file",
|
||||
provider.ForDisplay(),
|
||||
platform))
|
||||
}
|
||||
}
|
||||
newLocks.SetProvider(provider, version, constraints, hashes)
|
||||
}
|
||||
@ -294,8 +332,12 @@ func (c *ProvidersLockCommand) Run(args []string) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
c.Ui.Output(c.Colorize().Color("\n[bold][green]Success![reset] [bold]Terraform has updated the lock file.[reset]"))
|
||||
c.Ui.Output("\nReview the changes in .terraform.lock.hcl and then commit to your\nversion control system to retain the new checksums.\n")
|
||||
if madeAnyChange {
|
||||
c.Ui.Output(c.Colorize().Color("\n[bold][green]Success![reset] [bold]Terraform has updated the lock file.[reset]"))
|
||||
c.Ui.Output("\nReview the changes in .terraform.lock.hcl and then commit to your\nversion control system to retain the new checksums.\n")
|
||||
} else {
|
||||
c.Ui.Output(c.Colorize().Color("\n[bold][green]Success![reset] [bold]Terraform has validated the lock file and found no need for changes.[reset]"))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -357,3 +399,28 @@ Options:
|
||||
set of target platforms.
|
||||
`
|
||||
}
|
||||
|
||||
// providersLockCalculateChangeType works out whether there is any difference
|
||||
// between oldLock and newLock and returns a variable the main function can use
|
||||
// to decide on which message to print.
|
||||
//
|
||||
// One assumption made here that is not obvious without the context from the
|
||||
// main function is that while platformLock contains the lock information for a
|
||||
// single platform after the current run, oldLock contains the combined
|
||||
// information of all platforms from when the versions were last checked. A
|
||||
// simple equality check is not sufficient for deciding on change as we expect
|
||||
// that oldLock will be a superset of platformLock if no new hashes have been
|
||||
// found.
|
||||
//
|
||||
// We've separated this function out so we can write unit tests around the
|
||||
// logic. This function assumes the platformLock is not nil, as the main
|
||||
// function explicitly checks this before calling this function.
|
||||
func providersLockCalculateChangeType(oldLock *depsfile.ProviderLock, platformLock *depsfile.ProviderLock) providersLockChangeType {
|
||||
if oldLock == nil {
|
||||
return providersLockChangeTypeNewProvider
|
||||
}
|
||||
if oldLock.ContainsAll(platformLock) {
|
||||
return providersLockChangeTypeNoChange
|
||||
}
|
||||
return providersLockChangeTypeNewHashes
|
||||
}
|
||||
|
@ -8,6 +8,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/depsfile"
|
||||
"github.com/hashicorp/terraform/internal/getproviders"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
@ -33,40 +36,7 @@ func TestProvidersLock(t *testing.T) {
|
||||
|
||||
// This test depends on the -fs-mirror argument, so we always know what results to expect
|
||||
t.Run("basic", func(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath("providers-lock/basic"), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
// Our fixture dir has a generic os_arch dir, which we need to customize
|
||||
// to the actual OS/arch where this test is running in order to get the
|
||||
// desired result.
|
||||
fixtMachineDir := filepath.Join(td, "fs-mirror/registry.terraform.io/hashicorp/test/1.0.0/os_arch")
|
||||
wantMachineDir := filepath.Join(td, "fs-mirror/registry.terraform.io/hashicorp/test/1.0.0/", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH))
|
||||
err := os.Rename(fixtMachineDir, wantMachineDir)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &ProvidersLockCommand{
|
||||
Meta: Meta{
|
||||
Ui: ui,
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{"-fs-mirror=fs-mirror"}
|
||||
code := c.Run(args)
|
||||
if code != 0 {
|
||||
t.Fatalf("wrong exit code; expected 0, got %d", code)
|
||||
}
|
||||
|
||||
lockfile, err := os.ReadFile(".terraform.lock.hcl")
|
||||
if err != nil {
|
||||
t.Fatal("error reading lockfile")
|
||||
}
|
||||
|
||||
testDirectory := "providers-lock/basic"
|
||||
expected := `# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
@ -77,10 +47,65 @@ provider "registry.terraform.io/hashicorp/test" {
|
||||
]
|
||||
}
|
||||
`
|
||||
if string(lockfile) != expected {
|
||||
t.Fatalf("wrong lockfile content")
|
||||
}
|
||||
runProviderLockGenericTest(t, testDirectory, expected)
|
||||
})
|
||||
|
||||
// This test depends on the -fs-mirror argument, so we always know what results to expect
|
||||
t.Run("append", func(t *testing.T) {
|
||||
testDirectory := "providers-lock/append"
|
||||
expected := `# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/test" {
|
||||
version = "1.0.0"
|
||||
hashes = [
|
||||
"h1:7MjN4eFisdTv4tlhXH5hL4QQd39Jy4baPhFxwAd/EFE=",
|
||||
"h1:invalid",
|
||||
]
|
||||
}
|
||||
`
|
||||
runProviderLockGenericTest(t, testDirectory, expected)
|
||||
})
|
||||
}
|
||||
|
||||
func runProviderLockGenericTest(t *testing.T, testDirectory, expected string) {
|
||||
td := t.TempDir()
|
||||
testCopyDir(t, testFixturePath(testDirectory), td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
// Our fixture dir has a generic os_arch dir, which we need to customize
|
||||
// to the actual OS/arch where this test is running in order to get the
|
||||
// desired result.
|
||||
fixtMachineDir := filepath.Join(td, "fs-mirror/registry.terraform.io/hashicorp/test/1.0.0/os_arch")
|
||||
wantMachineDir := filepath.Join(td, "fs-mirror/registry.terraform.io/hashicorp/test/1.0.0/", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH))
|
||||
err := os.Rename(fixtMachineDir, wantMachineDir)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &ProvidersLockCommand{
|
||||
Meta: Meta{
|
||||
Ui: ui,
|
||||
testingOverrides: metaOverridesForProvider(p),
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{"-fs-mirror=fs-mirror"}
|
||||
code := c.Run(args)
|
||||
if code != 0 {
|
||||
t.Fatalf("wrong exit code; expected 0, got %d", code)
|
||||
}
|
||||
|
||||
lockfile, err := os.ReadFile(".terraform.lock.hcl")
|
||||
if err != nil {
|
||||
t.Fatal("error reading lockfile")
|
||||
}
|
||||
|
||||
if string(lockfile) != expected {
|
||||
t.Fatalf("wrong lockfile content")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvidersLock_args(t *testing.T) {
|
||||
@ -151,3 +176,81 @@ func TestProvidersLock_args(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestProvidersLockCalculateChangeType(t *testing.T) {
|
||||
provider := addrs.NewDefaultProvider("provider")
|
||||
v2 := getproviders.MustParseVersion("2.0.0")
|
||||
v2EqConstraints := getproviders.MustParseVersionConstraints("2.0.0")
|
||||
|
||||
t.Run("oldLock == nil", func(t *testing.T) {
|
||||
platformLock := depsfile.NewProviderLock(provider, v2, v2EqConstraints, []getproviders.Hash{
|
||||
"9r3i9a9QmASqMnQM",
|
||||
"K43RHM2klOoywtyW",
|
||||
"swJPXfuCNhJsTM5c",
|
||||
})
|
||||
|
||||
if ct := providersLockCalculateChangeType(nil, platformLock); ct != providersLockChangeTypeNewProvider {
|
||||
t.Fatalf("output was %s but should be %s", ct, providersLockChangeTypeNewProvider)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("oldLock == platformLock", func(t *testing.T) {
|
||||
platformLock := depsfile.NewProviderLock(provider, v2, v2EqConstraints, []getproviders.Hash{
|
||||
"9r3i9a9QmASqMnQM",
|
||||
"K43RHM2klOoywtyW",
|
||||
"swJPXfuCNhJsTM5c",
|
||||
})
|
||||
|
||||
oldLock := depsfile.NewProviderLock(provider, v2, v2EqConstraints, []getproviders.Hash{
|
||||
"9r3i9a9QmASqMnQM",
|
||||
"K43RHM2klOoywtyW",
|
||||
"swJPXfuCNhJsTM5c",
|
||||
})
|
||||
|
||||
if ct := providersLockCalculateChangeType(oldLock, platformLock); ct != providersLockChangeTypeNoChange {
|
||||
t.Fatalf("output was %s but should be %s", ct, providersLockChangeTypeNoChange)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("oldLock > platformLock", func(t *testing.T) {
|
||||
platformLock := depsfile.NewProviderLock(provider, v2, v2EqConstraints, []getproviders.Hash{
|
||||
"9r3i9a9QmASqMnQM",
|
||||
"K43RHM2klOoywtyW",
|
||||
"swJPXfuCNhJsTM5c",
|
||||
})
|
||||
|
||||
oldLock := depsfile.NewProviderLock(provider, v2, v2EqConstraints, []getproviders.Hash{
|
||||
"9r3i9a9QmASqMnQM",
|
||||
"1ZAChGWUMWn4zmIk",
|
||||
"K43RHM2klOoywtyW",
|
||||
"HWjRvIuWZ1LVatnc",
|
||||
"swJPXfuCNhJsTM5c",
|
||||
"KwhJK4p/U2dqbKhI",
|
||||
})
|
||||
|
||||
if ct := providersLockCalculateChangeType(oldLock, platformLock); ct != providersLockChangeTypeNoChange {
|
||||
t.Fatalf("output was %s but should be %s", ct, providersLockChangeTypeNoChange)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("oldLock < platformLock", func(t *testing.T) {
|
||||
platformLock := depsfile.NewProviderLock(provider, v2, v2EqConstraints, []getproviders.Hash{
|
||||
"9r3i9a9QmASqMnQM",
|
||||
"1ZAChGWUMWn4zmIk",
|
||||
"K43RHM2klOoywtyW",
|
||||
"HWjRvIuWZ1LVatnc",
|
||||
"swJPXfuCNhJsTM5c",
|
||||
"KwhJK4p/U2dqbKhI",
|
||||
})
|
||||
|
||||
oldLock := depsfile.NewProviderLock(provider, v2, v2EqConstraints, []getproviders.Hash{
|
||||
"9r3i9a9QmASqMnQM",
|
||||
"K43RHM2klOoywtyW",
|
||||
"swJPXfuCNhJsTM5c",
|
||||
})
|
||||
|
||||
if ct := providersLockCalculateChangeType(oldLock, platformLock); ct != providersLockChangeTypeNewHashes {
|
||||
t.Fatalf("output was %s but should be %s", ct, providersLockChangeTypeNoChange)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ func (c *ShowCommand) showFromLatestStateSnapshot() (*statefile.File, tfdiags.Di
|
||||
// Get the latest state snapshot from the backend for the current workspace
|
||||
stateFile, stateErr := getStateFromBackend(b, workspace)
|
||||
if stateErr != nil {
|
||||
diags = diags.Append(stateErr.Error())
|
||||
diags = diags.Append(stateErr)
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
|
@ -905,6 +905,34 @@ func TestShow_planWithNonDefaultStateLineage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestShow_corruptStatefile(t *testing.T) {
|
||||
td := t.TempDir()
|
||||
inputDir := "testdata/show-corrupt-statefile"
|
||||
testCopyDir(t, inputDir, td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
view, done := testView(t)
|
||||
c := &ShowCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
|
||||
code := c.Run([]string{})
|
||||
output := done(t)
|
||||
|
||||
if code != 1 {
|
||||
t.Fatalf("unexpected exit status %d; want 1\ngot: %s", code, output.Stdout())
|
||||
}
|
||||
|
||||
got := output.Stderr()
|
||||
want := `Unsupported state file format`
|
||||
if !strings.Contains(got, want) {
|
||||
t.Errorf("unexpected output\ngot: %s\nwant:\n%s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// showFixtureSchema returns a schema suitable for processing the configuration
|
||||
// in testdata/show. This schema should be assigned to a mock provider
|
||||
// named "test".
|
||||
@ -978,7 +1006,14 @@ func showFixtureProvider() *terraform.MockProvider {
|
||||
Private: req.Private,
|
||||
}
|
||||
}
|
||||
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
||||
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
|
||||
// this is a destroy plan,
|
||||
if req.ProposedNewState.IsNull() {
|
||||
resp.PlannedState = req.ProposedNewState
|
||||
resp.PlannedPrivate = req.PriorPrivate
|
||||
return resp
|
||||
}
|
||||
|
||||
idVal := req.ProposedNewState.GetAttr("id")
|
||||
amiVal := req.ProposedNewState.GetAttr("ami")
|
||||
if idVal.IsNull() {
|
||||
|
@ -317,7 +317,7 @@ func (c *TestCommand) prepareSuiteDir(ctx context.Context, suiteName string) (te
|
||||
locks := depsfile.NewLocks()
|
||||
evts := &providercache.InstallerEvents{
|
||||
QueryPackagesFailure: func(provider addrs.Provider, err error) {
|
||||
if err != nil && provider.IsDefault() && provider.Type == "test" {
|
||||
if err != nil && addrs.IsDefaultProvider(provider) && provider.Type == "test" {
|
||||
// This is some additional context for the failure error
|
||||
// we'll generate afterwards. Not the most ideal UX but
|
||||
// good enough for this prototype implementation, to help
|
||||
|
6
internal/command/testdata/plan/main.tf
vendored
6
internal/command/testdata/plan/main.tf
vendored
@ -4,6 +4,10 @@ resource "test_instance" "foo" {
|
||||
# This is here because at some point it caused a test failure
|
||||
network_interface {
|
||||
device_index = 0
|
||||
description = "Main network interface"
|
||||
description = "Main network interface"
|
||||
}
|
||||
}
|
||||
|
||||
data "test_data_source" "a" {
|
||||
id = "zzzzz"
|
||||
}
|
||||
|
5
internal/command/testdata/plan/output.jsonlog
vendored
Normal file
5
internal/command/testdata/plan/output.jsonlog
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{"@level":"info","@message":"Terraform 1.3.0-dev","@module":"terraform.ui","terraform":"1.3.0-dev","type":"version","ui":"1.0"}
|
||||
{"@level":"info","@message":"data.test_data_source.a: Refreshing...","@module":"terraform.ui","hook":{"resource":{"addr":"data.test_data_source.a","module":"","resource":"data.test_data_source.a","implied_provider":"test","resource_type":"test_data_source","resource_name":"a","resource_key":null},"action":"read"},"type":"apply_start"}
|
||||
{"@level":"info","@message":"data.test_data_source.a: Refresh complete after 0s [id=zzzzz]","@module":"terraform.ui","hook":{"resource":{"addr":"data.test_data_source.a","module":"","resource":"data.test_data_source.a","implied_provider":"test","resource_type":"test_data_source","resource_name":"a","resource_key":null},"action":"read","id_key":"id","id_value":"zzzzz","elapsed_seconds":0},"type":"apply_complete"}
|
||||
{"@level":"info","@message":"test_instance.foo: Plan to create","@module":"terraform.ui","change":{"resource":{"addr":"test_instance.foo","module":"","resource":"test_instance.foo","implied_provider":"test","resource_type":"test_instance","resource_name":"foo","resource_key":null},"action":"create"},"type":"planned_change"}
|
||||
{"@level":"info","@message":"Plan: 1 to add, 0 to change, 0 to destroy.","@module":"terraform.ui","changes":{"add":1,"change":0,"remove":0,"operation":"plan"},"type":"change_summary"}
|
9
internal/command/testdata/providers-lock/append/.terraform.lock.hcl
vendored
Normal file
9
internal/command/testdata/providers-lock/append/.terraform.lock.hcl
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/test" {
|
||||
version = "1.0.0"
|
||||
hashes = [
|
||||
"h1:invalid",
|
||||
]
|
||||
}
|
7
internal/command/testdata/providers-lock/append/main.tf
vendored
Normal file
7
internal/command/testdata/providers-lock/append/main.tf
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
test = {
|
||||
source = "hashicorp/test"
|
||||
}
|
||||
}
|
||||
}
|
1
internal/command/testdata/show-corrupt-statefile/terraform.tfstate
vendored
Normal file
1
internal/command/testdata/show-corrupt-statefile/terraform.tfstate
vendored
Normal file
@ -0,0 +1 @@
|
||||
invalid
|
19
internal/command/testdata/show-json/unknown-output/main.tf
vendored
Normal file
19
internal/command/testdata/show-json/unknown-output/main.tf
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
output "foo" {
|
||||
value = "hello"
|
||||
}
|
||||
|
||||
output "bar" {
|
||||
value = tolist([
|
||||
"hello",
|
||||
timestamp(),
|
||||
"world",
|
||||
])
|
||||
}
|
||||
|
||||
output "baz" {
|
||||
value = {
|
||||
greeting: "hello",
|
||||
time: timestamp(),
|
||||
subject: "world",
|
||||
}
|
||||
}
|
96
internal/command/testdata/show-json/unknown-output/output.json
vendored
Normal file
96
internal/command/testdata/show-json/unknown-output/output.json
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
{
|
||||
"format_version": "1.1",
|
||||
"terraform_version": "1.3.0-dev",
|
||||
"planned_values": {
|
||||
"outputs": {
|
||||
"bar": {
|
||||
"sensitive": false
|
||||
},
|
||||
"baz": {
|
||||
"sensitive": false
|
||||
},
|
||||
"foo": {
|
||||
"sensitive": false,
|
||||
"type": "string",
|
||||
"value": "hello"
|
||||
}
|
||||
},
|
||||
"root_module": {}
|
||||
},
|
||||
"output_changes": {
|
||||
"bar": {
|
||||
"actions": [
|
||||
"create"
|
||||
],
|
||||
"before": null,
|
||||
"after": [
|
||||
"hello",
|
||||
null,
|
||||
"world"
|
||||
],
|
||||
"after_unknown": [
|
||||
false,
|
||||
true,
|
||||
false
|
||||
],
|
||||
"before_sensitive": false,
|
||||
"after_sensitive": false
|
||||
},
|
||||
"baz": {
|
||||
"actions": [
|
||||
"create"
|
||||
],
|
||||
"before": null,
|
||||
"after": {
|
||||
"greeting": "hello",
|
||||
"subject": "world"
|
||||
},
|
||||
"after_unknown": {
|
||||
"time": true
|
||||
},
|
||||
"before_sensitive": false,
|
||||
"after_sensitive": false
|
||||
},
|
||||
"foo": {
|
||||
"actions": [
|
||||
"create"
|
||||
],
|
||||
"before": null,
|
||||
"after": "hello",
|
||||
"after_unknown": false,
|
||||
"before_sensitive": false,
|
||||
"after_sensitive": false
|
||||
}
|
||||
},
|
||||
"prior_state": {
|
||||
"format_version": "1.0",
|
||||
"terraform_version": "1.3.0",
|
||||
"values": {
|
||||
"outputs": {
|
||||
"foo": {
|
||||
"sensitive": false,
|
||||
"value": "hello",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"root_module": {}
|
||||
}
|
||||
},
|
||||
"configuration": {
|
||||
"root_module": {
|
||||
"outputs": {
|
||||
"bar": {
|
||||
"expression": {}
|
||||
},
|
||||
"baz": {
|
||||
"expression": {}
|
||||
},
|
||||
"foo": {
|
||||
"expression": {
|
||||
"constant_value": "hello"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,13 +7,14 @@ import (
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/command/format"
|
||||
"github.com/hashicorp/terraform/internal/command/views/json"
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/states"
|
||||
"github.com/hashicorp/terraform/internal/terraform"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// How long to wait between sending heartbeat/progress messages
|
||||
@ -59,8 +60,10 @@ type applyProgress struct {
|
||||
}
|
||||
|
||||
func (h *jsonHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation, action plans.Action, priorState, plannedNewState cty.Value) (terraform.HookAction, error) {
|
||||
idKey, idValue := format.ObjectValueIDOrName(priorState)
|
||||
h.view.Hook(json.NewApplyStart(addr, action, idKey, idValue))
|
||||
if action != plans.NoOp {
|
||||
idKey, idValue := format.ObjectValueIDOrName(priorState)
|
||||
h.view.Hook(json.NewApplyStart(addr, action, idKey, idValue))
|
||||
}
|
||||
|
||||
progress := applyProgress{
|
||||
addr: addr,
|
||||
@ -73,7 +76,9 @@ func (h *jsonHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generatio
|
||||
h.applying[addr.String()] = progress
|
||||
h.applyingLock.Unlock()
|
||||
|
||||
go h.applyingHeartbeat(progress)
|
||||
if action != plans.NoOp {
|
||||
go h.applyingHeartbeat(progress)
|
||||
}
|
||||
return terraform.HookActionContinue, nil
|
||||
}
|
||||
|
||||
@ -101,6 +106,10 @@ func (h *jsonHook) PostApply(addr addrs.AbsResourceInstance, gen states.Generati
|
||||
delete(h.applying, key)
|
||||
h.applyingLock.Unlock()
|
||||
|
||||
if progress.action == plans.NoOp {
|
||||
return terraform.HookActionContinue, nil
|
||||
}
|
||||
|
||||
elapsed := h.timeNow().Round(time.Second).Sub(progress.start)
|
||||
|
||||
if err != nil {
|
||||
|
@ -65,6 +65,7 @@ const (
|
||||
uiResourceModify
|
||||
uiResourceDestroy
|
||||
uiResourceRead
|
||||
uiResourceNoOp
|
||||
)
|
||||
|
||||
func (h *UiHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation, action plans.Action, priorState, plannedNewState cty.Value) (terraform.HookAction, error) {
|
||||
@ -89,6 +90,8 @@ func (h *UiHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation,
|
||||
case plans.Read:
|
||||
operation = "Reading..."
|
||||
op = uiResourceRead
|
||||
case plans.NoOp:
|
||||
op = uiResourceNoOp
|
||||
default:
|
||||
// We don't expect any other actions in here, so anything else is a
|
||||
// bug in the caller but we'll ignore it in order to be robust.
|
||||
@ -106,12 +109,14 @@ func (h *UiHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation,
|
||||
idValue = ""
|
||||
}
|
||||
|
||||
h.println(fmt.Sprintf(
|
||||
h.view.colorize.Color("[reset][bold]%s: %s%s[reset]"),
|
||||
dispAddr,
|
||||
operation,
|
||||
stateIdSuffix,
|
||||
))
|
||||
if operation != "" {
|
||||
h.println(fmt.Sprintf(
|
||||
h.view.colorize.Color("[reset][bold]%s: %s%s[reset]"),
|
||||
dispAddr,
|
||||
operation,
|
||||
stateIdSuffix,
|
||||
))
|
||||
}
|
||||
|
||||
key := addr.String()
|
||||
uiState := uiResourceState{
|
||||
@ -129,7 +134,9 @@ func (h *UiHook) PreApply(addr addrs.AbsResourceInstance, gen states.Generation,
|
||||
h.resourcesLock.Unlock()
|
||||
|
||||
// Start goroutine that shows progress
|
||||
go h.stillApplying(uiState)
|
||||
if op != uiResourceNoOp {
|
||||
go h.stillApplying(uiState)
|
||||
}
|
||||
|
||||
return terraform.HookActionContinue, nil
|
||||
}
|
||||
@ -201,6 +208,9 @@ func (h *UiHook) PostApply(addr addrs.AbsResourceInstance, gen states.Generation
|
||||
msg = "Creation complete"
|
||||
case uiResourceRead:
|
||||
msg = "Read complete"
|
||||
case uiResourceNoOp:
|
||||
// We don't make any announcements about no-op changes
|
||||
return terraform.HookActionContinue, nil
|
||||
case uiResourceUnknown:
|
||||
return terraform.HookActionContinue, nil
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hcled"
|
||||
"github.com/hashicorp/hcl/v2/hclparse"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/hashicorp/terraform/internal/lang/marks"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
@ -95,6 +96,10 @@ type DiagnosticSnippet struct {
|
||||
// Values is a sorted slice of expression values which may be useful in
|
||||
// understanding the source of an error in a complex expression.
|
||||
Values []DiagnosticExpressionValue `json:"values"`
|
||||
|
||||
// FunctionCall is information about a function call whose failure is
|
||||
// being reported by this diagnostic, if any.
|
||||
FunctionCall *DiagnosticFunctionCall `json:"function_call,omitempty"`
|
||||
}
|
||||
|
||||
// DiagnosticExpressionValue represents an HCL traversal string (e.g.
|
||||
@ -107,6 +112,20 @@ type DiagnosticExpressionValue struct {
|
||||
Statement string `json:"statement"`
|
||||
}
|
||||
|
||||
// DiagnosticFunctionCall represents a function call whose information is
|
||||
// being included as part of a diagnostic snippet.
|
||||
type DiagnosticFunctionCall struct {
|
||||
// CalledAs is the full name that was used to call this function,
|
||||
// potentially including namespace prefixes if the function does not belong
|
||||
// to the default function namespace.
|
||||
CalledAs string `json:"called_as"`
|
||||
|
||||
// Signature is a description of the signature of the function that was
|
||||
// called, if any. Might be omitted if we're reporting that a call failed
|
||||
// because the given function name isn't known, for example.
|
||||
Signature *Function `json:"signature,omitempty"`
|
||||
}
|
||||
|
||||
// NewDiagnostic takes a tfdiags.Diagnostic and a map of configuration sources,
|
||||
// and returns a Diagnostic struct.
|
||||
func NewDiagnostic(diag tfdiags.Diagnostic, sources map[string][]byte) *Diagnostic {
|
||||
@ -252,6 +271,8 @@ func NewDiagnostic(diag tfdiags.Diagnostic, sources map[string][]byte) *Diagnost
|
||||
vars := expr.Variables()
|
||||
values := make([]DiagnosticExpressionValue, 0, len(vars))
|
||||
seen := make(map[string]struct{}, len(vars))
|
||||
includeUnknown := tfdiags.DiagnosticCausedByUnknown(diag)
|
||||
includeSensitive := tfdiags.DiagnosticCausedBySensitive(diag)
|
||||
Traversals:
|
||||
for _, traversal := range vars {
|
||||
for len(traversal) > 1 {
|
||||
@ -273,14 +294,35 @@ func NewDiagnostic(diag tfdiags.Diagnostic, sources map[string][]byte) *Diagnost
|
||||
}
|
||||
switch {
|
||||
case val.HasMark(marks.Sensitive):
|
||||
// We won't say anything at all about sensitive values,
|
||||
// because we might give away something that was
|
||||
// sensitive about them.
|
||||
// We only mention a sensitive value if the diagnostic
|
||||
// we're rendering is explicitly marked as being
|
||||
// caused by sensitive values, because otherwise
|
||||
// readers tend to be misled into thinking the error
|
||||
// is caused by the sensitive value even when it isn't.
|
||||
if !includeSensitive {
|
||||
continue Traversals
|
||||
}
|
||||
// Even when we do mention one, we keep it vague
|
||||
// in order to minimize the chance of giving away
|
||||
// whatever was sensitive about it.
|
||||
value.Statement = "has a sensitive value"
|
||||
case !val.IsKnown():
|
||||
// We'll avoid saying anything about unknown or
|
||||
// "known after apply" unless the diagnostic is
|
||||
// explicitly marked as being caused by unknown
|
||||
// values, because otherwise readers tend to be
|
||||
// misled into thinking the error is caused by the
|
||||
// unknown value even when it isn't.
|
||||
if ty := val.Type(); ty != cty.DynamicPseudoType {
|
||||
value.Statement = fmt.Sprintf("is a %s, known only after apply", ty.FriendlyName())
|
||||
if includeUnknown {
|
||||
value.Statement = fmt.Sprintf("is a %s, known only after apply", ty.FriendlyName())
|
||||
} else {
|
||||
value.Statement = fmt.Sprintf("is a %s", ty.FriendlyName())
|
||||
}
|
||||
} else {
|
||||
if !includeUnknown {
|
||||
continue Traversals
|
||||
}
|
||||
value.Statement = "will be known only after apply"
|
||||
}
|
||||
default:
|
||||
@ -294,7 +336,24 @@ func NewDiagnostic(diag tfdiags.Diagnostic, sources map[string][]byte) *Diagnost
|
||||
return values[i].Traversal < values[j].Traversal
|
||||
})
|
||||
diagnostic.Snippet.Values = values
|
||||
|
||||
if callInfo := tfdiags.ExtraInfo[hclsyntax.FunctionCallDiagExtra](diag); callInfo != nil && callInfo.CalledFunctionName() != "" {
|
||||
calledAs := callInfo.CalledFunctionName()
|
||||
baseName := calledAs
|
||||
if idx := strings.LastIndex(baseName, "::"); idx >= 0 {
|
||||
baseName = baseName[idx+2:]
|
||||
}
|
||||
callInfo := &DiagnosticFunctionCall{
|
||||
CalledAs: calledAs,
|
||||
}
|
||||
if f, ok := ctx.Functions[calledAs]; ok {
|
||||
callInfo.Signature = DescribeFunction(baseName, f)
|
||||
}
|
||||
diagnostic.Snippet.FunctionCall = callInfo
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -412,6 +412,7 @@ func TestNewDiagnostic(t *testing.T) {
|
||||
}),
|
||||
},
|
||||
},
|
||||
Extra: diagnosticCausedBySensitive(true),
|
||||
},
|
||||
&Diagnostic{
|
||||
Severity: "error",
|
||||
@ -445,6 +446,61 @@ func TestNewDiagnostic(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"error with source code subject and expression referring to sensitive value when not caused by sensitive values": {
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Wrong noises",
|
||||
Detail: "Biological sounds are not allowed",
|
||||
Subject: &hcl.Range{
|
||||
Filename: "test.tf",
|
||||
Start: hcl.Pos{Line: 2, Column: 9, Byte: 42},
|
||||
End: hcl.Pos{Line: 2, Column: 26, Byte: 59},
|
||||
},
|
||||
Expression: hcltest.MockExprTraversal(hcl.Traversal{
|
||||
hcl.TraverseRoot{Name: "var"},
|
||||
hcl.TraverseAttr{Name: "boop"},
|
||||
hcl.TraverseIndex{Key: cty.StringVal("hello!")},
|
||||
}),
|
||||
EvalContext: &hcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"var": cty.ObjectVal(map[string]cty.Value{
|
||||
"boop": cty.MapVal(map[string]cty.Value{
|
||||
"hello!": cty.StringVal("bleurgh").Mark(marks.Sensitive),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
&Diagnostic{
|
||||
Severity: "error",
|
||||
Summary: "Wrong noises",
|
||||
Detail: "Biological sounds are not allowed",
|
||||
Range: &DiagnosticRange{
|
||||
Filename: "test.tf",
|
||||
Start: Pos{
|
||||
Line: 2,
|
||||
Column: 9,
|
||||
Byte: 42,
|
||||
},
|
||||
End: Pos{
|
||||
Line: 2,
|
||||
Column: 26,
|
||||
Byte: 59,
|
||||
},
|
||||
},
|
||||
Snippet: &DiagnosticSnippet{
|
||||
Context: strPtr(`resource "test_resource" "test"`),
|
||||
Code: (` foo = var.boop["hello!"]`),
|
||||
StartLine: (2),
|
||||
HighlightStartOffset: (8),
|
||||
HighlightEndOffset: (25),
|
||||
Values: []DiagnosticExpressionValue{
|
||||
// The sensitive value is filtered out because this is
|
||||
// not a sensitive-value-related diagnostic message.
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"error with source code subject and expression referring to a collection containing a sensitive value": {
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
@ -525,6 +581,7 @@ func TestNewDiagnostic(t *testing.T) {
|
||||
}),
|
||||
},
|
||||
},
|
||||
Extra: diagnosticCausedByUnknown(true),
|
||||
},
|
||||
&Diagnostic{
|
||||
Severity: "error",
|
||||
@ -582,6 +639,7 @@ func TestNewDiagnostic(t *testing.T) {
|
||||
}),
|
||||
},
|
||||
},
|
||||
Extra: diagnosticCausedByUnknown(true),
|
||||
},
|
||||
&Diagnostic{
|
||||
Severity: "error",
|
||||
@ -615,6 +673,61 @@ func TestNewDiagnostic(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"error with source code subject and unknown expression of unknown type when not caused by unknown values": {
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Wrong noises",
|
||||
Detail: "Biological sounds are not allowed",
|
||||
Subject: &hcl.Range{
|
||||
Filename: "test.tf",
|
||||
Start: hcl.Pos{Line: 2, Column: 9, Byte: 42},
|
||||
End: hcl.Pos{Line: 2, Column: 26, Byte: 59},
|
||||
},
|
||||
Expression: hcltest.MockExprTraversal(hcl.Traversal{
|
||||
hcl.TraverseRoot{Name: "var"},
|
||||
hcl.TraverseAttr{Name: "boop"},
|
||||
hcl.TraverseIndex{Key: cty.StringVal("hello!")},
|
||||
}),
|
||||
EvalContext: &hcl.EvalContext{
|
||||
Variables: map[string]cty.Value{
|
||||
"var": cty.ObjectVal(map[string]cty.Value{
|
||||
"boop": cty.MapVal(map[string]cty.Value{
|
||||
"hello!": cty.UnknownVal(cty.DynamicPseudoType),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
&Diagnostic{
|
||||
Severity: "error",
|
||||
Summary: "Wrong noises",
|
||||
Detail: "Biological sounds are not allowed",
|
||||
Range: &DiagnosticRange{
|
||||
Filename: "test.tf",
|
||||
Start: Pos{
|
||||
Line: 2,
|
||||
Column: 9,
|
||||
Byte: 42,
|
||||
},
|
||||
End: Pos{
|
||||
Line: 2,
|
||||
Column: 26,
|
||||
Byte: 59,
|
||||
},
|
||||
},
|
||||
Snippet: &DiagnosticSnippet{
|
||||
Context: strPtr(`resource "test_resource" "test"`),
|
||||
Code: (` foo = var.boop["hello!"]`),
|
||||
StartLine: (2),
|
||||
HighlightStartOffset: (8),
|
||||
HighlightEndOffset: (25),
|
||||
Values: []DiagnosticExpressionValue{
|
||||
// The unknown value is filtered out because this is
|
||||
// not an unknown-value-related diagnostic message.
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"error with source code subject with multiple expression values": {
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
@ -666,6 +779,7 @@ func TestNewDiagnostic(t *testing.T) {
|
||||
}),
|
||||
},
|
||||
},
|
||||
Extra: diagnosticCausedBySensitive(true),
|
||||
},
|
||||
&Diagnostic{
|
||||
Severity: "error",
|
||||
@ -813,3 +927,25 @@ func TestNewDiagnostic(t *testing.T) {
|
||||
// are fields which are pointer-to-string to ensure that the rendered JSON
|
||||
// results in `null` for an empty value, rather than `""`.
|
||||
func strPtr(s string) *string { return &s }
|
||||
|
||||
// diagnosticCausedByUnknown is a testing helper for exercising our logic
|
||||
// for selectively showing unknown values alongside our source snippets for
|
||||
// diagnostics that are explicitly marked as being caused by unknown values.
|
||||
type diagnosticCausedByUnknown bool
|
||||
|
||||
var _ tfdiags.DiagnosticExtraBecauseUnknown = diagnosticCausedByUnknown(true)
|
||||
|
||||
func (e diagnosticCausedByUnknown) DiagnosticCausedByUnknown() bool {
|
||||
return bool(e)
|
||||
}
|
||||
|
||||
// diagnosticCausedBySensitive is a testing helper for exercising our logic
|
||||
// for selectively showing sensitive values alongside our source snippets for
|
||||
// diagnostics that are explicitly marked as being caused by sensitive values.
|
||||
type diagnosticCausedBySensitive bool
|
||||
|
||||
var _ tfdiags.DiagnosticExtraBecauseSensitive = diagnosticCausedBySensitive(true)
|
||||
|
||||
func (e diagnosticCausedBySensitive) DiagnosticCausedBySensitive() bool {
|
||||
return bool(e)
|
||||
}
|
||||
|
112
internal/command/views/json/function.go
Normal file
112
internal/command/views/json/function.go
Normal file
@ -0,0 +1,112 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
// Function is a description of the JSON representation of the signature of
|
||||
// a function callable from the Terraform language.
|
||||
type Function struct {
|
||||
// Name is the leaf name of the function, without any namespace prefix.
|
||||
Name string `json:"name"`
|
||||
|
||||
Params []FunctionParam `json:"params"`
|
||||
VariadicParam *FunctionParam `json:"variadic_param,omitempty"`
|
||||
|
||||
// ReturnType is type constraint which is a static approximation of the
|
||||
// possibly-dynamic return type of the function.
|
||||
ReturnType json.RawMessage `json:"return_type"`
|
||||
|
||||
Description string `json:"description,omitempty"`
|
||||
DescriptionKind string `json:"description_kind,omitempty"`
|
||||
}
|
||||
|
||||
// FunctionParam represents a single parameter to a function, as represented
|
||||
// by type Function.
|
||||
type FunctionParam struct {
|
||||
// Name is a name for the function which is used primarily for
|
||||
// documentation purposes, because function arguments are positional
|
||||
// and therefore don't appear directly in configuration source code.
|
||||
Name string `json:"name"`
|
||||
|
||||
// Type is a type constraint which is a static approximation of the
|
||||
// possibly-dynamic type of the parameter. Particular functions may
|
||||
// have additional requirements that a type constraint alone cannot
|
||||
// represent.
|
||||
Type json.RawMessage `json:"type"`
|
||||
|
||||
// Maybe some of the other fields in function.Parameter would be
|
||||
// interesting to describe here too, but we'll wait to see if there
|
||||
// is a use-case first.
|
||||
|
||||
Description string `json:"description,omitempty"`
|
||||
DescriptionKind string `json:"description_kind,omitempty"`
|
||||
}
|
||||
|
||||
// DescribeFunction returns a description of the signature of the given cty
|
||||
// function, as a pointer to this package's serializable type Function.
|
||||
func DescribeFunction(name string, f function.Function) *Function {
|
||||
ret := &Function{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
params := f.Params()
|
||||
ret.Params = make([]FunctionParam, len(params))
|
||||
typeCheckArgs := make([]cty.Type, len(params), len(params)+1)
|
||||
for i, param := range params {
|
||||
ret.Params[i] = describeFunctionParam(¶m)
|
||||
typeCheckArgs[i] = param.Type
|
||||
}
|
||||
if varParam := f.VarParam(); varParam != nil {
|
||||
descParam := describeFunctionParam(varParam)
|
||||
ret.VariadicParam = &descParam
|
||||
typeCheckArgs = append(typeCheckArgs, varParam.Type)
|
||||
}
|
||||
|
||||
retType, err := f.ReturnType(typeCheckArgs)
|
||||
if err != nil {
|
||||
// Getting an error when type-checking with exactly the type constraints
|
||||
// the function called for is weird, so we'll just treat it as if it
|
||||
// has a dynamic return type instead, for our purposes here.
|
||||
// One reason this can happen is for a function which has a variadic
|
||||
// parameter but has logic inside it which considers it invalid to
|
||||
// specify exactly one argument for that parameter (since that's what
|
||||
// we did in typeCheckArgs as an approximation of a valid call above.)
|
||||
retType = cty.DynamicPseudoType
|
||||
}
|
||||
|
||||
if raw, err := retType.MarshalJSON(); err != nil {
|
||||
// Again, we'll treat any errors as if the function is dynamically
|
||||
// typed because it would be weird to get here.
|
||||
ret.ReturnType = json.RawMessage(`"dynamic"`)
|
||||
} else {
|
||||
ret.ReturnType = json.RawMessage(raw)
|
||||
}
|
||||
|
||||
// We don't currently have any sense of descriptions for functions and
|
||||
// their parameters, so we'll just leave those fields unpopulated for now.
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func describeFunctionParam(p *function.Parameter) FunctionParam {
|
||||
ret := FunctionParam{
|
||||
Name: p.Name,
|
||||
}
|
||||
|
||||
if raw, err := p.Type.MarshalJSON(); err != nil {
|
||||
// We'll treat any errors as if the function is dynamically
|
||||
// typed because it would be weird to get here.
|
||||
ret.Type = json.RawMessage(`"dynamic"`)
|
||||
} else {
|
||||
ret.Type = json.RawMessage(raw)
|
||||
}
|
||||
|
||||
// We don't currently have any sense of descriptions for functions and
|
||||
// their parameters, so we'll just leave those fields unpopulated for now.
|
||||
|
||||
return ret
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user