diff --git a/.gitignore b/.gitignore index 66fd13c9..4528db9f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ # Dependency directories (remove the comment below to include it) # vendor/ +*.sw? +terraform-provider-env0 +attic diff --git a/.vscode/Markdown.code-snippets b/.vscode/Markdown.code-snippets new file mode 100644 index 00000000..2f9a1638 --- /dev/null +++ b/.vscode/Markdown.code-snippets @@ -0,0 +1,64 @@ +{ + "Data source": { + "prefix": "dat", + "body": [ + "### `${1}` data source", + "", + "$0", + "", + "#### Example usage", + "", + "```terraform", + "data \"$1\" \"$1\" {", + "}", + "```", + "", + "#### Argument reference", + "", + "The following arguments are supported:", + "", + "- `name` - (Required) - Name ;", + "", + "#### Attributes reference", + "", + "In addition to all arguments above, the following attributes are exported:", + "", + "- `name` - The name of the organization;", + "", + "[^ Back to all resources](#resources)", + "" + ], + "description": "Terraform data source documentation" + }, + "Resource": { + "prefix": "res", + "body": [ + "### `${1}` resource", + "", + "$0", + "", + "#### Example usage", + "", + "```terraform", + "resource \"$1\" \"$1\" {", + "}", + "```", + "", + "#### Argument reference", + "", + "The following arguments are supported:", + "", + "- `name` - (Required) - Name ;", + "", + "#### Attributes reference", + "", + "In addition to all arguments above, the following attributes are exported:", + "", + "- `name` - The name of the organization;", + "", + "[^ Back to all resources](#resources)", + "" + ], + "description": "Terraform resource documentation" + }, +} \ No newline at end of file diff --git a/README.md b/README.md index 88d9a5b6..82286c9d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,330 @@ # terraform-provider-env0 + Terraform provider to interact with env0 + +The full list of supported resources is available [here](#resources). + +## Example usage + +```terraform +provider "env0" {} + +data "env0_project" "default_project" { + name = "Default Organization Project" +} + +resource "env0_template" "example" { + name = "example" + description = "Example template" + repository = "https://github.com/env0/templates" + path = "aws/hello-world" + project_ids = [data.env0_project.default_project.id] +} + +resource "env0_configuration_variable" "in_a_template" { + name = "VARIABLE_NAME" + value = "some value" + template_id = env0_template.tested1.id +} +``` + +you must also include a provider requirement somewhere in your terraform code: + +```terraform +terraform { + required_providers { + env0 = { + source = "terraform-registry.env0.com/" + } + } +} +``` + +## Setup + +First, generate an `api_key` and `api_secret` from the organization settings page. +See [here](https://docs.env0.com/reference#authentication). + +These can be provided by one of two methods. First method consists of setting `ENV0_API_KEY` and `ENV0_API_SECRET` environment variables, and just declaring the provider with no parameters: + +```terraform +provider "env0" {} +``` + +The second method would be to specify these fields as parameters to the provider: + +```terraform +variable "env0_api_key" {} +variable "env0_api_secret" {} + +provider "env0" { + api_key = var.env0_api_key + api_secret = var.env0_api_secret +} +``` + +## Resources + +The env0 Terraform provider provides the following building blocks: + +- `env0_organization` - [data source](#env0_organization-data-source) +- `env0_project` - [data source](#env0_project-data-source) and [resource](#env0_project-resource) +- `env0_configuration_variable` - [data source](#env0_configuration_variable-data-source) and [resource](#env0_configuration_variable-resource) +- `env0_template` - [data source](#env0_template-data-source) and [resource](#env0_template-resource) + +### `env0_template` resource + +Define a new template in the organization + +#### Example usage + +```terraform +data "env0_project" "default_project" { + name = "Default Organization Project" +} +resource "env0_template" "example" { + name = "example" + description = "Example template" + repository = "https://github.com/env0/templates" + path = "aws/hello-world" + project_ids = [data.env0_project.default_project.id] +} +``` + +#### Argument reference + +The following arguments are supported: + +- `name` - (Required) - name to give the template; +- `description` - (Optional) - description for the template; +- `repository` - (Required) - git repository for the template source code; +- `path` - (Optional, default "/") - terraform / terragrunt file folder inside source code; +- `revision` - (Optional) - source code revision (branch / tag) to use; +- `type` - (Optional, default "terraform") - `terraform` or `terragrunt`; +- `project_ids` - (Optional) - a list of which projects may access this template (id of project); +- `retries_on_deploy` - (Optional) - number of times to retry when deploying an environment based on this template (between 1 and 3) +- `retry_on_deploy_only_when_matches_regex` - (Optional) - if specified, will only retry (on deploy) if error matches specified regex; +- `retries_on_destroy` - (Optional) - number of times to retry when destroying an environment based on this template (between 1 and 3) +- `retry_on_destroy_only_when_matches_regex` - (Optional) - if specified, will only retry (on destroy) if error matches specified regex; + +#### Attributes reference + +There are no additional attributes other than the arguments above. + +[^ Back to all resources](#resources) + +### `env0_configuration_variable` resource + +A configuration variable is either an environment variable or a terraform variable. Configuration variables can configuration at the organization scope, project scope, template scope or environment scope. If two variables exists with the same name in two different scope, the more specific of the scopes is the value that will be used. + +#### Example usage + +```terraform +resource "env0_configuration_variable" "example" { + name = "ENVIRONMENT_VARIABLE_NAME" + value = "example value" +} +``` + +#### Argument reference + +The following arguments are supported: + +- `name` - (Required) - Name of the variable; +- `value` - (Required) - Value for the variable; +- `is_sensitive` - (Optional, default false) - set variable to be sensitive; +- `type` - (Optional, default 'environment') - either `environment` or `terraform`; +- `enum` - (Optional) - list of strings, for possible values allowed for this variable, when overriding the value through the UI; +- `project_id` - (Optional, mutually exclusive) - define the variable under the project scope (by default, variable are created under the organization scope); +- `template_id` - (Optional, mutually exclusive) - define the variable under the template scope; +- `environment_id` - (Optional, mutually exclusive) - define the variable under the environment scope; + +#### Attributes reference + +In addition to all arguments above, the following attributes are exported: + +- `name` - The name of the organization; + +[^ Back to all resources](#resources) + + +### `env0_organization` data source + +Each api key is associated with a single organization, so this resource can be used to fetch +that organization metadata. + +#### Example usage + +```terraform +data "env0_organization" "my_organization" {} + +output "organization_name" { + value = data.env0_organization.my_organization.name +} +``` + +#### Argument reference + +No argument are supported + +#### Attributes reference + +In addition to all arguments above, the following attributes are exported: + +- `name` - The name of the organization; +- `role` - The role of the api key in the organization; +- `is_self_hosted` - Is the organizaton self hosted; + +[^ Back to all resources](#resources) + +### `env0_project` data source + +Fetch metadata associated with a existing project. + +#### Example usage + +```terraform +data "env0_project" "default_project" { + name = "Default Organization Project" +} + +output "project_id" { + value = data.env0_project.default_project.id +} +``` + +#### Argument reference + +The following arguments are supported: + +- `id` - (Required if name is not set) - Fetch project by the project id; +- `name` - (Required if id not set, mutally exclusive) - Look for the first project that matches said name; + +#### Attributes reference + +In addition to all arguments above, the following attributes are exported: + +- `role` - The role of the api_key in this project; + +[^ Back to all resources](#resources) + +### `env0_template` data source + +Fetch metadata of already defined template. + +#### Example usage + +```terraform +data "env0_template" "example" { + name = "Template Name" +} + +output "template_id" { + value = data.env0_template.example_id +} +``` + +#### Argument reference + +The following arguments are supported: + +- `id` - (Required if name is not set) - Fetch template by the template id; +- `name` - (Required if id not set, mutally exclusive) - Look for the first template that matches said name; + +#### Attributes reference + +In addition to all arguments above, the following attributes are exported: + +- `repository` - template source code repository url; +- `path` - terraform / terrgrunt folder inside source code repository; +- `revision` - source code revision (branch / tag) to use; +- `type` - `terraform` or `terragrunt`; +- `project_ids` - which projects may access this template (id of project); +- `retries_on_deploy` - number of times to retry when deploying an environment based on this template; +- `retry_on_deploy_only_when_matches_regex` - will only retry (on deploy) if error matches specified regex; +- `retries_on_destroy` - number of times to retry when destroying an environment based on this template; +- `retry_on_destroy_only_when_matches_regex` - will only retry (on destroy) if error matches specified regex; + +[^ Back to all resources](#resources) + +### `env0_configuration_variable` data source + +A configuration variable is either an environment variable or a terraform variable. Configuration variables can configuration at the organization scope, project scope, template scope or environment scope. If two variables exists with the same name in two different scope, the more specific of the scopes is the value that will be used. + +This data source allows fetching existing configuration variables, and their values. Note that +fetching sensitive configuration variables will result in "******" as the variable value. + +#### Example usage + +```terraform +data "env0_configuration_variable" "aws_default_region" { + name = "AWS_DEFAULT_REGION" +} + +output "aws_default_region" { + value = data.env0_configuration_variable.aws_default_region.value +} +``` + +#### Argument reference + +The following arguments are supported: + +- `id` - (Required, mutually exclusive) - the id of the variable; +- `name` - (Required, mutually exclusive) - the variable name; + +#### Attributes reference + +In addition to all arguments above, the following attributes are exported: + +- `value` - value of the variable. will be '*********' if configuration variable is sensitive; +- `is_sensitive` - `true` if configuration variable is sensitive + +[^ Back to all resources](#resources) + +## Dev setup + +To build locally, you can use the `./build.sh` script. +The rest of the steps below are relevant if you would like to use the provider outside of the test harness (the test harness performs the steps described here). + +To use local binary version, you'll need to create a local terraform provider repository. +The simplest way to do so would be to create a folder on your disk. +Under that folder, copy the built provider binary to `terraform-registry.env0.com/env0/env0/6.6.6/linux_amd64/terraform-provider-env0` (note to replace linux_amd64 if using a different platform). +Then, create a `terraform.rc` file (location doesn't matter). +The content of said file should look like so: + +``` +provider_installation { + filesystem_mirror { + path = "" + include = ["terraform-registry.env0.com/*/*"] + } + direct {} +} +``` + +Finally, set an environment variable `TF_CLI_CONFIG_FILE` to point to the `terraform.rc` file created. +After that, `terraform init` should be able to locate the provider on disk. +To define this variable only when running terraform once, you can, in bash shell: + +```bash +TF_CLI_CONFIG_FILE= terraform init +``` + +## Testing + +If you have `ENV0_API_KEY` and `ENV0_API_SECRET` environment variables defined, after building the provider locally, just run `go run tests/harness.go` to run all the tests. Make sure to run from the project root folder. + +Use `go run tests/harness.go 003_configuration_variable` to run a specific test. +The last argument can also be specified as a full path, e.g., `tests/003_configuration_variable/`. + +Each tests performs the following steps: + +- `terraform init` +- `terraform apply -auto-approve -var second_run=0` +- `terraform apply -auto-approve -var second_run=1` +- `terraform outputs -json` - and verifies expected outputs from `expected_outputs.json` +- `terraform destroy` + +The harness has two convineint modes to help while developing: If an environment variable `DESTROY_MODE` exists and it's value is `NO_DESTROY`, the harness will avoid calling `terraform destroy`, allowing the developer to inspect the resources created, through the dashboard, for example. +Afterwards, when cleanup is required, just set `DESTROY_MODE` to `DESTROY_ONLY` and *only* `terraform destroy` will run. diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..d451922f --- /dev/null +++ b/build.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -e +go fmt +(cd env0tfprovider && go fmt) +(cd env0apiclient && go fmt) +CGO_ENABLED=0 go build -a -tags netgo -ldflags '-w -extldflags "-static"' -o terraform-provider-env0 diff --git a/env0apiclient/apiclient.go b/env0apiclient/apiclient.go new file mode 100644 index 00000000..7c092076 --- /dev/null +++ b/env0apiclient/apiclient.go @@ -0,0 +1,26 @@ +package env0apiclient + +import ( + "errors" + "net/http" +) + +type ApiClient struct { + Endpoint string + ApiKey string + ApiSecret string + client *http.Client + cachedOrganizationId string +} + +func (self *ApiClient) Organization() (Organization, error) { + var result []Organization + err := self.getJSON("/organizations", nil, &result) + if err != nil { + return Organization{}, err + } + if len(result) != 1 { + return Organization{}, errors.New("Server responded with a too many organizations") + } + return result[0], nil +} diff --git a/env0apiclient/configurationvariable.go b/env0apiclient/configurationvariable.go new file mode 100644 index 00000000..567493f6 --- /dev/null +++ b/env0apiclient/configurationvariable.go @@ -0,0 +1,105 @@ +package env0apiclient + +import ( + "errors" + "net/url" +) + +func (self *ApiClient) ConfigurationVariables(scope Scope, scopeId string) ([]ConfigurationVariable, error) { + organizationId, err := self.organizationId() + if err != nil { + return nil, err + } + var result []ConfigurationVariable + params := url.Values{} + params.Add("organizationId", organizationId) + switch { + case scope == ScopeGlobal: + case scope == ScopeTemplate: + params.Add("blueprintId", scopeId) + case scope == ScopeProject: + params.Add("projectId", scopeId) + case scope == ScopeEnvironment: + params.Add("environmentId", scopeId) + case scope == ScopeDeployment: + return nil, errors.New("No api to fetch configuration variables by deployment") + case scope == ScopeDeploymentLog: + params.Add("deploymentLogId", scopeId) + } + err = self.getJSON("/configuration", params, &result) + if err != nil { + return []ConfigurationVariable{}, err + } + return result, nil +} + +func (self *ApiClient) ConfigurationVariableCreate(name string, value string, isSensitive bool, scope Scope, scopeId string, type_ ConfigurationVariableType, enumValues []string) (ConfigurationVariable, error) { + if scope == ScopeDeploymentLog || scope == ScopeDeployment { + return ConfigurationVariable{}, errors.New("Must not create variable on scope deployment / deploymentLog") + } + organizationId, err := self.organizationId() + if err != nil { + return ConfigurationVariable{}, err + } + var result ConfigurationVariable + request := map[string]interface{}{ + "name": name, + "value": value, + "isSensitive": isSensitive, + "scope": scope, + "type": type_, + "organizationId": organizationId, + } + if scope != ScopeGlobal { + request["scopeId"] = scopeId + } + if enumValues != nil { + request["schema"] = map[string]interface{}{ + "type": "string", + "enum": enumValues, + } + } + err = self.postJSON("/configuration", request, &result) + if err != nil { + return ConfigurationVariable{}, err + } + return result, nil +} + +func (self *ApiClient) ConfigurationVariableDelete(id string) error { + return self.delete("/configuration/" + id) +} + +func (self *ApiClient) ConfigurationVariableUpdate(id string, name string, value string, isSensitive bool, scope Scope, scopeId string, type_ ConfigurationVariableType, enumValues []string) (ConfigurationVariable, error) { + if scope == ScopeDeploymentLog || scope == ScopeDeployment { + return ConfigurationVariable{}, errors.New("Must not create variable on scope deployment / deploymentLog") + } + organizationId, err := self.organizationId() + if err != nil { + return ConfigurationVariable{}, err + } + var result ConfigurationVariable + request := map[string]interface{}{ + "id": id, + "name": name, + "value": value, + "isSensitive": isSensitive, + "scope": scope, + "type": type_, + "organizationId": organizationId, + } + if scope != ScopeGlobal { + request["scopeId"] = scopeId + } + if enumValues != nil { + request["schema"] = map[string]interface{}{ + "type": "string", + "enum": enumValues, + } + } + err = self.postJSON("/configuration", request, &result) + if err != nil { + return ConfigurationVariable{}, err + } + return result, nil +} diff --git a/env0apiclient/httpverbs.go b/env0apiclient/httpverbs.go new file mode 100644 index 00000000..0e2e1398 --- /dev/null +++ b/env0apiclient/httpverbs.go @@ -0,0 +1,86 @@ +package env0apiclient + +import ( + "bytes" + "errors" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +func (self *ApiClient) normalizeEndpoint() { + for strings.HasSuffix(self.Endpoint, "/") { + self.Endpoint = self.Endpoint[:len(self.Endpoint)-1] + } +} + +func (self *ApiClient) post(path string, payload []byte) ([]byte, error) { + self.normalizeEndpoint() + req, err := http.NewRequest(http.MethodPost, self.Endpoint+path, bytes.NewBuffer(payload)) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/json") + return self.do(req) +} + +func (self *ApiClient) put(path string, payload []byte) ([]byte, error) { + self.normalizeEndpoint() + req, err := http.NewRequest(http.MethodPut, self.Endpoint+path, bytes.NewBuffer(payload)) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/json") + return self.do(req) +} + +func (self *ApiClient) get(path string, params url.Values) ([]byte, error) { + self.normalizeEndpoint() + if params != nil { + path += "?" + params.Encode() + } + req, err := http.NewRequest(http.MethodGet, self.Endpoint+path, nil) + if err != nil { + return nil, err + } + return self.do(req) +} + +func (self *ApiClient) delete(path string) error { + self.normalizeEndpoint() + req, err := http.NewRequest(http.MethodDelete, self.Endpoint+path, nil) + if err != nil { + return err + } + _, err = self.do(req) + return err +} + +func (self *ApiClient) do(req *http.Request) ([]byte, error) { + req.SetBasicAuth(self.ApiKey, self.ApiSecret) + req.Header.Add("Accept", "application/json") + + if self.client == nil { + self.client = &http.Client{} + } + resp, err := self.client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode == 204 { + return nil, nil + } + if resp.StatusCode != 200 && resp.StatusCode != 201 { + return nil, errors.New(resp.Status + ": " + string(body)) + } + + return body, nil +} diff --git a/env0apiclient/httpverbswithjson.go b/env0apiclient/httpverbswithjson.go new file mode 100644 index 00000000..8e6fbb3f --- /dev/null +++ b/env0apiclient/httpverbswithjson.go @@ -0,0 +1,50 @@ +package env0apiclient + +import ( + "encoding/json" + "net/url" +) + +func (self *ApiClient) postJSON(path string, request interface{}, response interface{}) error { + serialized, err := json.Marshal(request) + if err != nil { + return err + } + body, err := self.post(path, serialized) + if err != nil { + return err + } + err = json.Unmarshal(body, response) + if err != nil { + return err + } + return nil +} + +func (self *ApiClient) putJSON(path string, request interface{}, response interface{}) error { + serialized, err := json.Marshal(request) + if err != nil { + return err + } + body, err := self.put(path, serialized) + if err != nil { + return err + } + err = json.Unmarshal(body, response) + if err != nil { + return err + } + return nil +} + +func (self *ApiClient) getJSON(path string, params url.Values, response interface{}) error { + body, err := self.get(path, params) + if err != nil { + return err + } + err = json.Unmarshal(body, response) + if err != nil { + return err + } + return nil +} diff --git a/env0apiclient/logic.go b/env0apiclient/logic.go new file mode 100644 index 00000000..6a365c17 --- /dev/null +++ b/env0apiclient/logic.go @@ -0,0 +1,13 @@ +package env0apiclient + +func (self *ApiClient) organizationId() (string, error) { + if self.cachedOrganizationId != "" { + return self.cachedOrganizationId, nil + } + organization, err := self.Organization() + if err != nil { + return "", nil + } + self.cachedOrganizationId = organization.Id + return self.cachedOrganizationId, nil +} diff --git a/env0apiclient/model.go b/env0apiclient/model.go new file mode 100644 index 00000000..d446dcc7 --- /dev/null +++ b/env0apiclient/model.go @@ -0,0 +1,145 @@ +package env0apiclient + +type Organization struct { + Id string `json:"id"` + Name string `json:"name"` + CreatedBy string `json:"createdBy"` + Role string `json:"role"` + IsSelfHosted bool `json:"isSelfHosted"` +} + +type User struct { + CreatedAt string `json:"created_at"` + Email string `json:"email"` + FamilyName string `json:"family_name"` + GivenName string `json:"given_name"` + Name string `json:"name"` + Picture string `json:"picture"` + UserId string `json:"user_id"` + LastLogin string `json:"last_login"` + AppMetadata map[string]interface{} `json:"app_metadata"` +} + +type Project struct { + IsArchived bool `json:"isArchived"` + OrganizationId string `json:"organizationId"` + UpdatedAt string `json:"updatedAt"` + CreatedAt string `json:"createdAt"` + Id string `json:"id"` + Name string `json:"name"` + CreatedBy string `json:"createdBy"` + Role string `json:"role"` + CreatedByUser User `json:"createdByUser"` +} + +type ConfigurationVariableSchema struct { + Type string `json:"string"` + Enum []string `json:"enum"` +} + +type ConfigurationVariable struct { + Value string `json:"value"` + OrganizationId string `json:"organizationId"` + UserId string `json:"userId"` + IsSensitive bool `json:"isSensitive"` + Scope string `json:"scope"` + Id string `json:"id"` + Name string `json:"name"` + Type int64 `json:"type"` + Schema ConfigurationVariableSchema `json:"schema"` +} + +type Scope string + +const ( + ScopeGlobal Scope = "GLOBAL" + ScopeTemplate Scope = "BLUEPRINT" + ScopeProject Scope = "PROJECT" + ScopeEnvironment Scope = "ENVIRONMENT" + ScopeDeployment Scope = "DEPLOYMENT" + ScopeDeploymentLog Scope = "DEPLOYMENT_LOG" +) + +type ConfigurationVariableType int + +const ( + ConfigurationVariableTypeEnvironment ConfigurationVariableType = 0 + ConfigurationVariableTypeTerraform ConfigurationVariableType = 1 +) + +type TemplateRetryOn struct { + Times int `json:"times,omitempty"` + ErrorRegex string `json:"errorRegex,omitempty"` +} + +type TemplateRetry struct { + OnDeploy *TemplateRetryOn `json:"onDeploy,omitempty"` + OnDestroy *TemplateRetryOn `json:"onDestroy,omitempty"` +} + +type TemplateType string + +const ( + TemplateTypeTerraform TemplateType = "terraform" + TemplateTypeTerragrunt TemplateType = "terragrunt" +) + +type TemplateSshKey struct { + Id string `json:"id"` + Name string `json:"name"` +} + +type TemplateCreatePayload struct { + Retry *TemplateRetry `json:"retry,omitempty"` + SshKeys []TemplateSshKey `json:"sshKeys,omitempty"` + Type TemplateType `json:"type"` + Description string `json:"description,omitempty"` + Name string `json:"name"` + Repository string `json:"repository"` + Path string `json:"path,omitempty"` + IsGitLab bool `json:"isGitLab"` + TokenName string `json:"tokenName"` + TokenId string `json:"tokenId"` + GithubInstallationId int `json:"githubInstallationId"` + Revision string `json:"revision"` + ProjectIds []string `json:"projectIds,omitempty"` + OrganizationId string `json:"organizationId"` +} + +type Template struct { + Author User `json:"author"` + AuthorId string `json:"authorId"` + CreatedAt string `json:"createdAt"` + Href string `json:"href"` + Id string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + OrganizationId string `json:"organizationId"` + Path string `json:"path"` + Revision string `json:"revision"` + ProjectId string `json:"projectId"` + ProjectIds []string `json:"projectIds"` + Repository string `json:"repository"` + Retry TemplateRetry `json:"retry"` + SshKeys []TemplateSshKey `json:"sshKeys"` + Type string `json:"type"` + UpdatedAt string `json:"updatedAt"` +} + +type SshKey struct { + User User `json:"user"` + UserId string `json:"userId"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` + Id string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + OrganizationId string `json:"organizationId"` + Value string `json:"value"` +} + +type SshKeyCreatePayload struct { + Name string `json:"name"` + OrganizationId string `json:"organizationId"` + Value string `json:"value"` +} diff --git a/env0apiclient/project.go b/env0apiclient/project.go new file mode 100644 index 00000000..4755b67a --- /dev/null +++ b/env0apiclient/project.go @@ -0,0 +1,43 @@ +package env0apiclient + +import ( + "net/url" +) + +func (self *ApiClient) Projects() ([]Project, error) { + organizationId, err := self.organizationId() + if err != nil { + return nil, err + } + var result []Project + params := url.Values{} + params.Add("organizationId", organizationId) + err = self.getJSON("/projects", params, &result) + if err != nil { + return []Project{}, err + } + return result, nil +} + +func (self *ApiClient) Project(id string) (Project, error) { + var result Project + err := self.getJSON("/projects/"+id, nil, &result) + if err != nil { + return Project{}, err + } + return result, nil +} + +func (self *ApiClient) ProjectCreate(name string) (Project, error) { + var result Project + request := map[string]interface{}{"name": name} + err := self.postJSON("/projects", request, &result) + if err != nil { + return Project{}, err + } + return result, nil +} + +func (self *ApiClient) ProjectDelete(id string) error { + return self.delete("/projects/" + id) +} diff --git a/env0apiclient/sshkey.go b/env0apiclient/sshkey.go new file mode 100644 index 00000000..7add27e8 --- /dev/null +++ b/env0apiclient/sshkey.go @@ -0,0 +1,49 @@ +package env0apiclient + +import ( + "errors" + "net/url" +) + +func (self *ApiClient) SshKeyCreate(payload SshKeyCreatePayload) (SshKey, error) { + if payload.Name == "" { + return SshKey{}, errors.New("Must specify ssh key name on creation") + } + if payload.Value == "" { + return SshKey{}, errors.New("Must specify ssh key value (private key in PEM format) on creation") + } + if payload.OrganizationId != "" { + return SshKey{}, errors.New("Must not specify organizationId") + } + organizationId, err := self.organizationId() + if err != nil { + return SshKey{}, nil + } + payload.OrganizationId = organizationId + + var result SshKey + err = self.postJSON("/ssh-keys", payload, &result) + if err != nil { + return SshKey{}, err + } + return result, nil +} + +func (self *ApiClient) SshKeyDelete(id string) error { + return self.delete("/ssh-keys/" + id) +} + +func (self *ApiClient) SshKeys() ([]SshKey, error) { + organizationId, err := self.organizationId() + if err != nil { + return nil, err + } + var result []SshKey + params := url.Values{} + params.Add("organizationId", organizationId) + err = self.getJSON("/ssh-keys", params, &result) + if err != nil { + return nil, err + } + return result, err +} diff --git a/env0apiclient/template.go b/env0apiclient/template.go new file mode 100644 index 00000000..fc3cbf2c --- /dev/null +++ b/env0apiclient/template.go @@ -0,0 +1,80 @@ +package env0apiclient + +//templates are actually called "blueprints" in some parts of the API, this layer +//attempts to abstract this detail away - all the users of api client should +//only use "template", no mention of blueprint + +import ( + "errors" + "net/url" +) + +func (self *ApiClient) TemplateCreate(payload TemplateCreatePayload) (Template, error) { + if payload.Name == "" { + return Template{}, errors.New("Must specify template name on creation") + } + if payload.OrganizationId != "" { + return Template{}, errors.New("Must not specify organizationId") + } + organizationId, err := self.organizationId() + if err != nil { + return Template{}, nil + } + payload.OrganizationId = organizationId + + var result Template + err = self.postJSON("/blueprints", payload, &result) + if err != nil { + return Template{}, err + } + return result, nil +} + +func (self *ApiClient) Template(id string) (Template, error) { + var result Template + err := self.getJSON("/blueprints/"+id, nil, &result) + if err != nil { + return Template{}, err + } + return result, nil +} + +func (self *ApiClient) TemplateDelete(id string) error { + return self.delete("/blueprints/" + id) +} + +func (self *ApiClient) TemplateUpdate(id string, payload TemplateCreatePayload) (Template, error) { + if payload.Name == "" { + return Template{}, errors.New("Must specify template name on creation") + } + if payload.OrganizationId != "" { + return Template{}, errors.New("Must not specify organizationId") + } + organizationId, err := self.organizationId() + if err != nil { + return Template{}, err + } + payload.OrganizationId = organizationId + + var result Template + err = self.putJSON("/blueprints/"+id, payload, &result) + if err != nil { + return Template{}, err + } + return result, nil +} + +func (self *ApiClient) Templates() ([]Template, error) { + organizationId, err := self.organizationId() + if err != nil { + return nil, err + } + var result []Template + params := url.Values{} + params.Add("organizationId", organizationId) + err = self.getJSON("/blueprints", params, &result) + if err != nil { + return nil, err + } + return result, err +} diff --git a/env0tfprovider/data_configuration_variable.go b/env0tfprovider/data_configuration_variable.go new file mode 100644 index 00000000..c40674f0 --- /dev/null +++ b/env0tfprovider/data_configuration_variable.go @@ -0,0 +1,152 @@ +package env0tfprovider + +import ( + "context" + + "github.com/env0/terraform-provider-env0/env0apiclient" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataConfigurationVariable() *schema.Resource { + return &schema.Resource{ + ReadContext: dataConfigurationVariableRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "the name of the configuration variable", + Optional: true, + ExactlyOneOf: []string{"name", "id"}, + }, + "type": { + Type: schema.TypeString, + Description: "'terraform' or 'environment'. If specified as an argument, limits searching by variable name only to variables of this type.", + Optional: true, + }, + "id": { + Type: schema.TypeString, + Description: "id of the configuration variable", + Optional: true, + ExactlyOneOf: []string{"name", "id"}, + }, + "project_id": { + Type: schema.TypeString, + Description: "search for the variable under this project, not globally", + Optional: true, + ConflictsWith: []string{"template_id", "environment_id", "deployment_log_id"}, + }, + "template_id": { + Type: schema.TypeString, + Description: "search for the variable under this template, not globally", + Optional: true, + ConflictsWith: []string{"project_id", "environment_id", "deployment_log_id"}, + }, + "environment_id": { + Type: schema.TypeString, + Description: "search for the variable under this environment, not globally", + Optional: true, + ConflictsWith: []string{"template_id", "project_id", "deployment_log_id"}, + }, + "deployment_log_id": { + Type: schema.TypeString, + Description: "search for the variable under this deployment log, not globally", + Optional: true, + ConflictsWith: []string{"template_id", "environment_id", "project_id"}, + }, + "value": { + Type: schema.TypeString, + Description: "value stored in the variable", + Computed: true, + }, + "is_sensitive": { + Type: schema.TypeBool, + Description: "is the variable defined as sensitive", + Computed: true, + }, + "scope": { + Type: schema.TypeString, + Description: "scope of the variable", + Computed: true, + }, + }, + } +} + +func dataConfigurationVariableRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + scope := env0apiclient.ScopeGlobal + scopeId := "" + if projectId, ok := d.GetOk("project_id"); ok { + scope = env0apiclient.ScopeProject + scopeId = projectId.(string) + } + if templateId, ok := d.GetOk("template_id"); ok { + scope = env0apiclient.ScopeTemplate + scopeId = templateId.(string) + } + if environmentId, ok := d.GetOk("environment_id"); ok { + scope = env0apiclient.ScopeEnvironment + scopeId = environmentId.(string) + } + if deploymentLogId, ok := d.GetOk("deployment_log_id"); ok { + scope = env0apiclient.ScopeDeploymentLog + scopeId = deploymentLogId.(string) + } + variables, err := apiClient.ConfigurationVariables(scope, scopeId) + if err != nil { + return diag.Errorf("Could not query variables: %v", err) + } + + id, idOk := d.GetOk("id") + name, nameOk := d.GetOk("name") + type_ := int64(-1) + if typeString, ok := d.GetOk("type"); ok { + if !nameOk { + return diag.Errorf("Specify 'type' only when searching configuration variables by 'name' (not by 'id')") + } + switch typeString.(string) { + case "environment": + type_ = int64(env0apiclient.ConfigurationVariableTypeEnvironment) + case "terraform": + type_ = int64(env0apiclient.ConfigurationVariableTypeTerraform) + default: + return diag.Errorf("Invalid value for 'type': %s. can be either 'environment' or 'terraform'", typeString.(string)) + } + } + var variable env0apiclient.ConfigurationVariable + for _, candidate := range variables { + if idOk && candidate.Id == id.(string) { + variable = candidate + break + } + if nameOk && candidate.Name == name.(string) { + if type_ != -1 { + if candidate.Type != type_ { + continue + } + } + variable = candidate + break + } + } + if variable.Id == "" { + return diag.Errorf("Could not find variable") + } + + d.SetId(variable.Id) + d.Set("name", variable.Name) + d.Set("value", variable.Value) + d.Set("is_sensitive", variable.IsSensitive) + d.Set("scope", variable.Scope) + if variable.Type == int64(env0apiclient.ConfigurationVariableTypeEnvironment) { + d.Set("type", "environment") + } else if variable.Type == int64(env0apiclient.ConfigurationVariableTypeTerraform) { + d.Set("type", "terraform") + } else { + return diag.Errorf("Unknown variable type: %d", int(variable.Type)) + } + + return nil +} diff --git a/env0tfprovider/data_organization.go b/env0tfprovider/data_organization.go new file mode 100644 index 00000000..3656f3d4 --- /dev/null +++ b/env0tfprovider/data_organization.go @@ -0,0 +1,55 @@ +package env0tfprovider + +import ( + "context" + + "github.com/env0/terraform-provider-env0/env0apiclient" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataOrganization() *schema.Resource { + return &schema.Resource{ + ReadContext: dataOrganizationRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "the name of the organization", + Computed: true, + }, + "created_by": { + Type: schema.TypeString, + Description: "textual description of the entity who created the organization", + Computed: true, + }, + "role": { + Type: schema.TypeString, + Description: "role of the authenticated user (through api key) in the organization", + Computed: true, + }, + "is_self_hosted": { + Type: schema.TypeBool, + Description: "is the organization self hosted", + Computed: true, + }, + }, + } +} + +func dataOrganizationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + organization, err := apiClient.Organization() + if err != nil { + return diag.Errorf("Could not query organization: %v", err) + } + + d.SetId(organization.Id) + d.Set("name", organization.Name) + d.Set("created_by", organization.CreatedBy) + d.Set("role", organization.Role) + d.Set("is_self_hosted", organization.IsSelfHosted) + + return nil +} diff --git a/env0tfprovider/data_project.go b/env0tfprovider/data_project.go new file mode 100644 index 00000000..e352dc64 --- /dev/null +++ b/env0tfprovider/data_project.go @@ -0,0 +1,79 @@ +package env0tfprovider + +import ( + "context" + + "github.com/env0/terraform-provider-env0/env0apiclient" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataProject() *schema.Resource { + return &schema.Resource{ + ReadContext: dataProjectRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "the name of the project", + Optional: true, + ExactlyOneOf: []string{"name", "id"}, + }, + "id": { + Type: schema.TypeString, + Description: "id of the project", + Optional: true, + ExactlyOneOf: []string{"name", "id"}, + }, + "created_by": { + Type: schema.TypeString, + Description: "textual description of the entity who created the project", + Computed: true, + }, + "role": { + Type: schema.TypeString, + Description: "role of the authenticated user (through api key) in the project", + Computed: true, + }, + }, + } +} + +func dataProjectRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + var err error + var project env0apiclient.Project + + id, ok := d.GetOk("id") + if ok { + project, err = apiClient.Project(id.(string)) + if err != nil { + return diag.Errorf("Could not query project by id: %v", err) + } + } else { + name, ok := d.GetOk("name") + if !ok { + return diag.Errorf("Either 'name' or 'id' must be specified") + } + projects, err := apiClient.Projects() + if err != nil { + return diag.Errorf("Could not query project by name: %v", err) + } + for _, candidate := range projects { + if candidate.Name == name.(string) { + project = candidate + break + } + } + if project.Id == "" { + return diag.Errorf("Could not find a project with name: %s", name) + } + } + + d.SetId(project.Id) + d.Set("name", project.Name) + d.Set("created_by", project.CreatedBy) + d.Set("role", project.Role) + + return nil +} diff --git a/env0tfprovider/data_sshkey.go b/env0tfprovider/data_sshkey.go new file mode 100644 index 00000000..c701d3bc --- /dev/null +++ b/env0tfprovider/data_sshkey.go @@ -0,0 +1,73 @@ +package env0tfprovider + +import ( + "context" + + "github.com/env0/terraform-provider-env0/env0apiclient" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSshKey() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSshKeyRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "the name of the ssh key", + Optional: true, + ExactlyOneOf: []string{"name", "id"}, + }, + "id": { + Type: schema.TypeString, + Description: "id of the ssh key", + Optional: true, + ExactlyOneOf: []string{"name", "id"}, + }, + }, + } +} + +func dataSshKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + name, nameSpecified := d.GetOk("name") + var sshKey env0apiclient.SshKey + if nameSpecified { + sshKeys, err := apiClient.SshKeys() + if err != nil { + return diag.Errorf("Could not query ssh keys: %v", err) + } + for _, candidate := range sshKeys { + if candidate.Name == name { + sshKey = candidate + } + } + if sshKey.Name == "" { + return diag.Errorf("Could not find an env0 ssh key with name %s", name) + } + } else { + id, idSpecified := d.GetOk("id") + if !idSpecified { + return diag.Errorf("At lease one of 'id', 'name' must be specified") + } + sshKeys, err := apiClient.SshKeys() + if err != nil { + return diag.Errorf("Could not query ssh keys: %v", err) + } + for _, candidate := range sshKeys { + if candidate.Id == id.(string) { + sshKey = candidate + } + } + if sshKey.Name == "" { + return diag.Errorf("Could not find an env0 ssh key with id %s", id) + } + } + + d.SetId(sshKey.Id) + d.Set("name", sshKey.Name) + + return nil +} diff --git a/env0tfprovider/data_template.go b/env0tfprovider/data_template.go new file mode 100644 index 00000000..0a014c3e --- /dev/null +++ b/env0tfprovider/data_template.go @@ -0,0 +1,145 @@ +package env0tfprovider + +import ( + "context" + + "github.com/env0/terraform-provider-env0/env0apiclient" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataTemplate() *schema.Resource { + return &schema.Resource{ + ReadContext: dataTemplateRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "the name of the template", + Optional: true, + ExactlyOneOf: []string{"name", "id"}, + }, + "id": { + Type: schema.TypeString, + Description: "id of the template", + Optional: true, + ExactlyOneOf: []string{"name", "id"}, + }, + "repository": { + Type: schema.TypeString, + Description: "template source code repository url", + Computed: true, + }, + "path": { + Type: schema.TypeString, + Description: "terraform / terrgrunt folder inside source code repository", + Computed: true, + }, + "revision": { + Type: schema.TypeString, + Description: "source code revision (branch / tag) to use", + Computed: true, + }, + "type": { + Type: schema.TypeString, + Description: "'terraform' or 'terragrunt'", + Computed: true, + }, + "project_ids": { + Type: schema.TypeList, + Description: "which projects may access this template (id of project)", + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Description: "env0_project.id for each project", + }, + }, + "ssh_key_names": { + Type: schema.TypeList, + Description: "which ssh keys are used for accessing git over ssh", + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Description: "env0_ssh_key.name for each project", + }, + }, + "retries_on_deploy": { + Type: schema.TypeInt, + Description: "number of times to retry when deploying an environment based on this template", + Computed: true, + }, + "retry_on_deploy_only_when_matches_regex": { + Type: schema.TypeString, + Description: "if specified, will only retry (on deploy) if error matches specified regex", + Computed: true, + }, + "retries_on_destroy": { + Type: schema.TypeInt, + Description: "number of times to retry when destroying an environment based on this template", + Computed: true, + }, + "retry_on_destroy_only_when_matches_regex": { + Type: schema.TypeString, + Description: "if specified, will only retry (on destroy) if error matches specified regex", + Computed: true, + }, + }, + } +} + +func dataTemplateRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + name, nameSpecified := d.GetOk("name") + var template env0apiclient.Template + var err error + if nameSpecified { + templates, err := apiClient.Templates() + if err != nil { + return diag.Errorf("Could not query templates: %v", err) + } + for _, candidate := range templates { + if candidate.Name == name { + template = candidate + } + } + if template.Name == "" { + return diag.Errorf("Could not find an env0 template with name %s", name) + } + } else { + template, err = apiClient.Template(d.Get("id").(string)) + if err != nil { + return diag.Errorf("Could not query template: %v", err) + } + } + + d.SetId(template.Id) + d.Set("name", template.Name) + d.Set("repository", template.Repository) + d.Set("path", template.Path) + d.Set("revision", template.Revision) + d.Set("type", template.Type) + d.Set("project_ids", template.ProjectIds) + sshKeyNames := []string{} + for _, sshKey := range template.SshKeys { + sshKeyNames = append(sshKeyNames, sshKey.Name) + } + d.Set("ssh_key_names", sshKeyNames) + if template.Retry.OnDeploy != nil { + d.Set("retries_on_deploy", template.Retry.OnDeploy.Times) + d.Set("retry_on_deploy_only_when_matches_regex", template.Retry.OnDeploy.ErrorRegex) + } else { + d.Set("retries_on_deploy", 0) + d.Set("retry_on_deploy_only_when_matches_regex", "") + } + if template.Retry.OnDestroy != nil { + d.Set("retries_on_destroy", template.Retry.OnDestroy.Times) + d.Set("retry_on_destroy_only_when_matches_regex", template.Retry.OnDestroy.ErrorRegex) + } else { + d.Set("retries_on_destroy", 0) + d.Set("retry_on_destroy_only_when_matches_regex", "") + } + + //TODO: sshkeys + return nil +} diff --git a/env0tfprovider/provider.go b/env0tfprovider/provider.go new file mode 100644 index 00000000..d725c450 --- /dev/null +++ b/env0tfprovider/provider.go @@ -0,0 +1,67 @@ +package env0tfprovider + +import ( + "errors" + + "github.com/env0/terraform-provider-env0/env0apiclient" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func Provider() *schema.Provider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "api_endpoint": { + Type: schema.TypeString, + Description: "override epi endpoint (used for testing)", + DefaultFunc: schema.EnvDefaultFunc("ENV0_API_ENDPOINT", "https://api.env0.com/"), + Optional: true, + Sensitive: false, + }, + "api_key": { + Type: schema.TypeString, + Description: "env0 api key (https://docs.env0.com/reference#authentication)", + DefaultFunc: schema.EnvDefaultFunc("ENV0_API_KEY", nil), + Optional: true, + Sensitive: true, + }, + "api_secret": { + Type: schema.TypeString, + Description: "env0 api key secret", + DefaultFunc: schema.EnvDefaultFunc("ENV0_API_SECRET", nil), + Optional: true, + Sensitive: true, + }, + }, + DataSourcesMap: map[string]*schema.Resource{ + "env0_organization": dataOrganization(), + "env0_project": dataProject(), + "env0_configuration_variable": dataConfigurationVariable(), + "env0_template": dataTemplate(), + "env0_ssh_key": dataSshKey(), + }, + ResourcesMap: map[string]*schema.Resource{ + "env0_project": resourceProject(), + "env0_configuration_variable": resourceConfigurationVariable(), + "env0_template": resourceTemplate(), + "env0_ssh_key": resourceSshKey(), + }, + ConfigureFunc: configureProvider, + } +} + +func configureProvider(d *schema.ResourceData) (interface{}, error) { + apiKey, ok := d.GetOk("api_key") + if !ok { + return nil, errors.New("either api_key must be provided, or ENV0_API_KEY environment variable set") + } + apiSecret, ok := d.GetOk("api_secret") + if !ok { + return nil, errors.New("either api_secret must be provided or ENV0_API_SECRET environment variable set") + } + + return &env0apiclient.ApiClient{ + Endpoint: d.Get("api_endpoint").(string), + ApiKey: apiKey.(string), + ApiSecret: apiSecret.(string), + }, nil +} diff --git a/env0tfprovider/resource_configuration_variable.go b/env0tfprovider/resource_configuration_variable.go new file mode 100644 index 00000000..2070cd8b --- /dev/null +++ b/env0tfprovider/resource_configuration_variable.go @@ -0,0 +1,205 @@ +package env0tfprovider + +import ( + "context" + "errors" + + "github.com/env0/terraform-provider-env0/env0apiclient" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceConfigurationVariable() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceConfigurationVariableCreate, + ReadContext: resourceConfigurationVariableRead, + UpdateContext: resourceConfigurationVariableUpdate, + DeleteContext: resourceConfigurationVariableDelete, + + Importer: &schema.ResourceImporter{StateContext: resourceConfigurationVariableImport}, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "name to give the configuration variable", + Required: true, + }, + "value": { + Type: schema.TypeString, + Description: "value for the configuration variable", + Required: true, + }, + "is_sensitive": { + Type: schema.TypeBool, + Description: "is the variable sensitive, defaults to false", + Optional: true, + Default: false, + ForceNew: true, + }, + "project_id": { + Type: schema.TypeString, + Description: "create the variable under this project, not globally", + Optional: true, + ConflictsWith: []string{"template_id", "environment_id"}, + }, + "template_id": { + Type: schema.TypeString, + Description: "create the variable under this template, not globally", + Optional: true, + ConflictsWith: []string{"project_id", "environment_id"}, + }, + "environment_id": { + Type: schema.TypeString, + Description: "create the variable under this environment, not globally", + Optional: true, + ConflictsWith: []string{"template_id", "project_id"}, + }, + "type": { + Type: schema.TypeString, + Description: "default 'environment'. set to 'terraform' to create a terraform variable", + Optional: true, + Default: "environment", + }, + "enum": { + Type: schema.TypeList, + Description: "limit possible values to values from this list", + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Description: "name to give the configuration variable", + }, + }, + }, + } +} + +func whichScope(d *schema.ResourceData) (env0apiclient.Scope, string) { + scope := env0apiclient.ScopeGlobal + scopeId := "" + if projectId, ok := d.GetOk("project_id"); ok { + scope = env0apiclient.ScopeProject + scopeId = projectId.(string) + } + if templateId, ok := d.GetOk("template_id"); ok { + scope = env0apiclient.ScopeTemplate + scopeId = templateId.(string) + } + if environmentId, ok := d.GetOk("environment_id"); ok { + scope = env0apiclient.ScopeEnvironment + scopeId = environmentId.(string) + } + + return scope, scopeId +} + +func resourceConfigurationVariableCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + scope, scopeId := whichScope(d) + name := d.Get("name").(string) + value := d.Get("value").(string) + isSensitive := d.Get("is_sensitive").(bool) + typeAsString := d.Get("type").(string) + var type_ env0apiclient.ConfigurationVariableType + switch typeAsString { + case "environment": + type_ = env0apiclient.ConfigurationVariableTypeEnvironment + case "terraform": + type_ = env0apiclient.ConfigurationVariableTypeTerraform + default: + return diag.Errorf("'type' can only receive either 'environment' or 'terraform': %s", typeAsString) + } + var enumValues []string = nil + if specified, ok := d.GetOk("enum_values"); ok { + enumValues = specified.([]string) + } + configurationVariable, err := apiClient.ConfigurationVariableCreate(name, value, isSensitive, scope, scopeId, type_, enumValues) + if err != nil { + return diag.Errorf("could not create configurationVariable: %v", err) + } + + d.SetId(configurationVariable.Id) + + return nil +} + +func resourceConfigurationVariableRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + id := d.Id() + scope, scopeId := whichScope(d) + variables, err := apiClient.ConfigurationVariables(scope, scopeId) + if err != nil { + return diag.Errorf("could not get configurationVariable: %v", err) + } + for _, variable := range variables { + if variable.Id == id { + d.Set("name", variable.Name) + d.Set("value", variable.Value) + d.Set("is_sensitive", variable.IsSensitive) + if variable.Type == int64(env0apiclient.ConfigurationVariableTypeTerraform) { + d.Set("type", "terraform") + } else { + d.Set("type", "environment") + } + return nil + } + } + return diag.Errorf("variable %s not found (under this scope): %v", id, err) +} + +func resourceConfigurationVariableUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + id := d.Id() + scope, scopeId := whichScope(d) + name := d.Get("name").(string) + value := d.Get("value").(string) + isSensitive := d.Get("is_sensitive").(bool) + typeAsString := d.Get("type").(string) + var type_ env0apiclient.ConfigurationVariableType + switch typeAsString { + case "environment": + type_ = env0apiclient.ConfigurationVariableTypeEnvironment + case "terraform": + type_ = env0apiclient.ConfigurationVariableTypeTerraform + default: + return diag.Errorf("'type' can only receive either 'environment' or 'terraform': %s", typeAsString) + } + var enumValues []string = nil + if specified, ok := d.GetOk("enum_values"); ok { + enumValues = specified.([]string) + } + _, err := apiClient.ConfigurationVariableUpdate(id, name, value, isSensitive, scope, scopeId, type_, enumValues) + if err != nil { + return diag.Errorf("could not create configurationVariable: %v", err) + } + + return nil +} + +func resourceConfigurationVariableDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + id := d.Id() + err := apiClient.ConfigurationVariableDelete(id) + if err != nil { + return diag.Errorf("could not delete configurationVariable: %v", err) + } + return nil +} + +func resourceConfigurationVariableImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + return nil, errors.New("Not implemented") + // apiClient := meta.(*env0apiclient.ApiClient) + + // id := d.Id() + // configurationVariable, err := apiClient.ConfigurationVariable(id) + // if err != nil { + // return nil, err + // } + + // d.Set("name", configurationVariable.Name) + + // return []*schema.ResourceData{d}, nil +} diff --git a/env0tfprovider/resource_project.go b/env0tfprovider/resource_project.go new file mode 100644 index 00000000..25560069 --- /dev/null +++ b/env0tfprovider/resource_project.go @@ -0,0 +1,87 @@ +package env0tfprovider + +import ( + "context" + + "github.com/env0/terraform-provider-env0/env0apiclient" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceProject() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceProjectCreate, + ReadContext: resourceProjectRead, + UpdateContext: resourceProjectUpdate, + DeleteContext: resourceProjectDelete, + + Importer: &schema.ResourceImporter{StateContext: resourceProjectImport}, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "name to give the project", + Required: true, + }, + "id": { + Type: schema.TypeString, + Description: "id of the project", + Computed: true, + }, + }, + } +} + +func resourceProjectCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + name := d.Get("name").(string) + project, err := apiClient.ProjectCreate(name) + if err != nil { + return diag.Errorf("could not create project: %v", err) + } + + d.SetId(project.Id) + + return nil +} + +func resourceProjectRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + id := d.Id() + _, err := apiClient.Project(id) + if err != nil { + return diag.Errorf("could not get project: %v", err) + } + return nil +} + +func resourceProjectUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return diag.Errorf("not implemented") +} + +func resourceProjectDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + id := d.Id() + err := apiClient.ProjectDelete(id) + if err != nil { + return diag.Errorf("could not delete project: %v", err) + } + return nil +} + +func resourceProjectImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + apiClient := meta.(*env0apiclient.ApiClient) + + id := d.Id() + project, err := apiClient.Project(id) + if err != nil { + return nil, err + } + + d.Set("name", project.Name) + + return []*schema.ResourceData{d}, nil +} diff --git a/env0tfprovider/resource_sshkey.go b/env0tfprovider/resource_sshkey.go new file mode 100644 index 00000000..49190b11 --- /dev/null +++ b/env0tfprovider/resource_sshkey.go @@ -0,0 +1,98 @@ +package env0tfprovider + +import ( + "context" + "errors" + + "github.com/env0/terraform-provider-env0/env0apiclient" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceSshKey() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceSshKeyCreate, + ReadContext: resourceSshKeyRead, + DeleteContext: resourceSshKeyDelete, + + Importer: &schema.ResourceImporter{StateContext: resourceSshKeyImport}, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "name to give the ssh key", + Required: true, + ForceNew: true, + }, + "value": { + Type: schema.TypeString, + Description: "value is a private key in PEM format (first line usually looks like -----BEGIN OPENSSH PRIVATE KEY-----)", + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceSshKeyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + request := env0apiclient.SshKeyCreatePayload{ + Name: d.Get("name").(string), + Value: d.Get("value").(string), + } + sshKey, err := apiClient.SshKeyCreate(request) + if err != nil { + return diag.Errorf("could not create ssh key: %v", err) + } + + d.SetId(sshKey.Id) + + return nil +} + +func resourceSshKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + sshKeys, err := apiClient.SshKeys() + if err != nil { + return diag.Errorf("could not query ssh keys: %v", err) + } + found := false + for _, candidate := range sshKeys { + if candidate.Id == d.Id() { + found = true + } + } + if !found { + return diag.Errorf("ssh key %s not found", d.Id()) + } + + return nil +} + +func resourceSshKeyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + id := d.Id() + err := apiClient.SshKeyDelete(id) + if err != nil { + return diag.Errorf("could not delete ssh key: %v", err) + } + return nil +} + +func resourceSshKeyImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + return nil, errors.New("Not implemented") + // apiClient := meta.(*env0apiclient.ApiClient) + + // id := d.Id() + // ssh key, err := apiClient.SshKey(id) + // if err != nil { + // return nil, err + // } + + // d.Set("name", ssh key.Name) + + // return []*schema.ResourceData{d}, nil +} diff --git a/env0tfprovider/resource_template.go b/env0tfprovider/resource_template.go new file mode 100644 index 00000000..7e41f0ae --- /dev/null +++ b/env0tfprovider/resource_template.go @@ -0,0 +1,253 @@ +package env0tfprovider + +import ( + "context" + "errors" + + "github.com/env0/terraform-provider-env0/env0apiclient" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceTemplate() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceTemplateCreate, + ReadContext: resourceTemplateRead, + UpdateContext: resourceTemplateUpdate, + DeleteContext: resourceTemplateDelete, + + Importer: &schema.ResourceImporter{StateContext: resourceTemplateImport}, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "name to give the template", + Required: true, + }, + "description": { + Type: schema.TypeString, + Description: "description for the template", + Optional: true, + }, + "repository": { + Type: schema.TypeString, + Description: "git repository for the template source code", + Required: true, + }, + "path": { + Type: schema.TypeString, + Description: "terraform / terragrunt file folder inside source code", + Optional: true, + }, + "revision": { + Type: schema.TypeString, + Description: "source code revision (branch / tag) to use", + Optional: true, + }, + "type": { + Type: schema.TypeString, + Description: "'terraform' or 'terragrunt'", + Optional: true, + Default: "terraform", + }, + "project_ids": { + Type: schema.TypeList, + Description: "which projects may access this template (id of project)", + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Description: "env0_project.id for each project", + }, + }, + "ssh_key_names": { + Type: schema.TypeList, + Description: "names of env0 defined ssh keys to use when accessing git over ssh", + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Description: "env0_ssh_key.name for each ssh key", + }, + }, + "retries_on_deploy": { + Type: schema.TypeInt, + Description: "number of times to retry when deploying an environment based on this template", + Optional: true, + }, + "retry_on_deploy_only_when_matches_regex": { + Type: schema.TypeString, + Description: "if specified, will only retry (on deploy) if error matches specified regex", + Optional: true, + }, + "retries_on_destroy": { + Type: schema.TypeInt, + Description: "number of times to retry when destroying an environment based on this template", + Optional: true, + }, + "retry_on_destroy_only_when_matches_regex": { + Type: schema.TypeString, + Description: "if specified, will only retry (on destroy) if error matches specified regex", + Optional: true, + }, + }, + } +} + +func templateCreatePayloadFromParameters(d *schema.ResourceData) (env0apiclient.TemplateCreatePayload, diag.Diagnostics) { + result := env0apiclient.TemplateCreatePayload{ + Name: d.Get("name").(string), + Repository: d.Get("repository").(string), + } + if description, ok := d.GetOk("description"); ok { + result.Description = description.(string) + } + if path, ok := d.GetOk("path"); ok { + result.Path = path.(string) + } + if revision, ok := d.GetOk("revision"); ok { + result.Revision = revision.(string) + } + if type_, ok := d.GetOk("type"); ok { + if type_ == string(env0apiclient.TemplateTypeTerraform) { + result.Type = env0apiclient.TemplateTypeTerraform + } else if type_ == string(env0apiclient.TemplateTypeTerragrunt) { + result.Type = env0apiclient.TemplateTypeTerragrunt + } else { + return env0apiclient.TemplateCreatePayload{}, diag.Errorf("'type' can either be 'terraform' or 'terragrunt': %s", type_) + } + } + if projectIds, ok := d.GetOk("project_ids"); ok { + result.ProjectIds = []string{} + for _, projectId := range projectIds.([]interface{}) { + result.ProjectIds = append(result.ProjectIds, projectId.(string)) + } + } + if sshKeyNames, ok := d.GetOk("ssh_key_names"); ok { + result.SshKeys = []env0apiclient.TemplateSshKey{} + for _, sshKeyName := range sshKeyNames.([]interface{}) { + result.SshKeys = append(result.SshKeys, env0apiclient.TemplateSshKey{Name: sshKeyName.(string)}) + } + } + onDeployRetries, hasRetriesOnDeploy := d.GetOk("retries_on_deploy") + if hasRetriesOnDeploy { + if result.Retry == nil { + result.Retry = &env0apiclient.TemplateRetry{} + } + result.Retry.OnDeploy = &env0apiclient.TemplateRetryOn{ + Times: onDeployRetries.(int), + } + } + if retryOnDeployOnlyIfMatchesRegex, ok := d.GetOk("retry_on_deploy_only_if_matches_regex"); ok { + if !hasRetriesOnDeploy { + return env0apiclient.TemplateCreatePayload{}, diag.Errorf("may only specify 'retry_on_deploy_only_if_matches_regex'") + } + result.Retry.OnDeploy.ErrorRegex = retryOnDeployOnlyIfMatchesRegex.(string) + } + + onDestroyRetries, hasRetriesOnDestroy := d.GetOk("retries_on_destroy") + if hasRetriesOnDestroy { + if result.Retry == nil { + result.Retry = &env0apiclient.TemplateRetry{} + } + result.Retry.OnDestroy = &env0apiclient.TemplateRetryOn{ + Times: onDestroyRetries.(int), + } + } + if retryOnDestroyOnlyIfMatchesRegex, ok := d.GetOk("retry_on_destroy_only_if_matches_regex"); ok { + if !hasRetriesOnDestroy { + return env0apiclient.TemplateCreatePayload{}, diag.Errorf("may only specify 'retry_on_destroy_only_if_matches_regex'") + } + result.Retry.OnDestroy.ErrorRegex = retryOnDestroyOnlyIfMatchesRegex.(string) + } + return result, nil +} + +func resourceTemplateCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + request, problem := templateCreatePayloadFromParameters(d) + if problem != nil { + return problem + } + template, err := apiClient.TemplateCreate(request) + if err != nil { + return diag.Errorf("could not create template: %v", err) + } + + d.SetId(template.Id) + + return nil +} + +func resourceTemplateRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + template, err := apiClient.Template(d.Id()) + if err != nil { + return diag.Errorf("could not get template: %v", err) + } + + d.Set("name", template.Name) + d.Set("description", template.Description) + d.Set("repository", template.Repository) + d.Set("path", template.Path) + d.Set("revision", template.Revision) + d.Set("type", template.Type) + d.Set("project_ids", template.ProjectIds) + if template.Retry.OnDeploy != nil { + d.Set("retries_on_deploy", template.Retry.OnDeploy.Times) + d.Set("retry_on_deploy_only_when_matches_regex", template.Retry.OnDeploy.ErrorRegex) + } else { + d.Set("retries_on_deploy", 0) + d.Set("retry_on_deploy_only_when_matches_regex", "") + } + if template.Retry.OnDestroy != nil { + d.Set("retries_on_destroy", template.Retry.OnDestroy.Times) + d.Set("retry_on_destroy_only_when_matches_regex", template.Retry.OnDestroy.ErrorRegex) + } else { + d.Set("retries_on_destroy", 0) + d.Set("retry_on_destroy_only_when_matches_regex", "") + } + + return nil +} + +func resourceTemplateUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + request, problem := templateCreatePayloadFromParameters(d) + if problem != nil { + return problem + } + _, err := apiClient.TemplateUpdate(d.Id(), request) + if err != nil { + return diag.Errorf("could not update template: %v", err) + } + + return nil +} + +func resourceTemplateDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*env0apiclient.ApiClient) + + id := d.Id() + err := apiClient.TemplateDelete(id) + if err != nil { + return diag.Errorf("could not delete template: %v", err) + } + return nil +} + +func resourceTemplateImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + return nil, errors.New("Not implemented") + // apiClient := meta.(*env0apiclient.ApiClient) + + // id := d.Id() + // template, err := apiClient.Template(id) + // if err != nil { + // return nil, err + // } + + // d.Set("name", template.Name) + + // return []*schema.ResourceData{d}, nil +} diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 00000000..3c0cf813 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,7 @@ +.terraform.lock.hcl +.terraform/ +terraform.tfstate +terraform.tfstate.backup +crash.log +terraform.rc +fake_registry diff --git a/examples/001_data_organization/conf.tf b/examples/001_data_organization/conf.tf new file mode 100644 index 00000000..58dc45e8 --- /dev/null +++ b/examples/001_data_organization/conf.tf @@ -0,0 +1,13 @@ +terraform { + backend "local" { + } + required_providers { + env0 = { + source = "terraform-registry.env0.com/env0/env0" + } + } +} + +provider "env0" {} + +variable "second_run" {} diff --git a/examples/001_data_organization/expected_outputs.json b/examples/001_data_organization/expected_outputs.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/examples/001_data_organization/expected_outputs.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/examples/001_data_organization/main.tf b/examples/001_data_organization/main.tf new file mode 100644 index 00000000..98c422ab --- /dev/null +++ b/examples/001_data_organization/main.tf @@ -0,0 +1,5 @@ +data "env0_organization" "my_organization" {} + +output "organization_name" { + value = data.env0_organization.my_organization.name +} diff --git a/examples/002_project/conf.tf b/examples/002_project/conf.tf new file mode 100644 index 00000000..58dc45e8 --- /dev/null +++ b/examples/002_project/conf.tf @@ -0,0 +1,13 @@ +terraform { + backend "local" { + } + required_providers { + env0 = { + source = "terraform-registry.env0.com/env0/env0" + } + } +} + +provider "env0" {} + +variable "second_run" {} diff --git a/examples/002_project/expected_outputs.json b/examples/002_project/expected_outputs.json new file mode 100644 index 00000000..7de3f3ce --- /dev/null +++ b/examples/002_project/expected_outputs.json @@ -0,0 +1,3 @@ +{ + "default_project_name": "Default Organization Project" +} \ No newline at end of file diff --git a/examples/002_project/main.tf b/examples/002_project/main.tf new file mode 100644 index 00000000..cfdcc409 --- /dev/null +++ b/examples/002_project/main.tf @@ -0,0 +1,16 @@ +data "env0_project" "default" { + name = "Default Organization Project" +} + +data "env0_project" "default2" { + depends_on = [data.env0_project.default] + id = data.env0_project.default.id +} + +output "default_project_id" { + value = data.env0_project.default.id +} + +output "default_project_name" { + value = data.env0_project.default2.name +} diff --git a/examples/003_configuration_variable/conf.tf b/examples/003_configuration_variable/conf.tf new file mode 100644 index 00000000..58dc45e8 --- /dev/null +++ b/examples/003_configuration_variable/conf.tf @@ -0,0 +1,13 @@ +terraform { + backend "local" { + } + required_providers { + env0 = { + source = "terraform-registry.env0.com/env0/env0" + } + } +} + +provider "env0" {} + +variable "second_run" {} diff --git a/examples/003_configuration_variable/expected_outputs.json b/examples/003_configuration_variable/expected_outputs.json new file mode 100644 index 00000000..26461bd8 --- /dev/null +++ b/examples/003_configuration_variable/expected_outputs.json @@ -0,0 +1,3 @@ +{ + "tested1_value": "fake value 1 after update" +} \ No newline at end of file diff --git a/examples/003_configuration_variable/main.tf b/examples/003_configuration_variable/main.tf new file mode 100644 index 00000000..595f10ce --- /dev/null +++ b/examples/003_configuration_variable/main.tf @@ -0,0 +1,41 @@ +data "env0_configuration_variable" "region" { + name = "AWS_DEFAULT_REGION" +} +output "region_value" { + value = data.env0_configuration_variable.region.value +} +output "region_id" { + value = data.env0_configuration_variable.region.id +} + + +data "env0_project" "default" { + name = "Default Organization Project" +} +data "env0_configuration_variable" "region_in_project" { + name = "AWS_DEFAULT_REGION" + project_id = data.env0_project.default.id +} +output "region_in_project_value" { + value = data.env0_configuration_variable.region_in_project.value +} +output "region_in_project_id" { + value = data.env0_configuration_variable.region_in_project.id +} + +resource "env0_configuration_variable" "tested1" { + name = "tested1" + value = "fake value 1 ${var.second_run ? "after update" : ""}" +} +data "env0_configuration_variable" "tested1" { + name = "tested1" + depends_on = [env0_configuration_variable.tested1] +} + +output "tested1_value" { + value = data.env0_configuration_variable.tested1.value +} + +data "env0_configuration_variable" "tested2" { + id = env0_configuration_variable.tested1.id +} diff --git a/examples/004_template/conf.tf b/examples/004_template/conf.tf new file mode 100644 index 00000000..58dc45e8 --- /dev/null +++ b/examples/004_template/conf.tf @@ -0,0 +1,13 @@ +terraform { + backend "local" { + } + required_providers { + env0 = { + source = "terraform-registry.env0.com/env0/env0" + } + } +} + +provider "env0" {} + +variable "second_run" {} diff --git a/examples/004_template/expected_outputs.json b/examples/004_template/expected_outputs.json new file mode 100644 index 00000000..a6e4970c --- /dev/null +++ b/examples/004_template/expected_outputs.json @@ -0,0 +1,6 @@ +{ + "tested2_template_type": "terraform", + "tested2_template_name": "tested1", + "tested2_template_repository": "https://github.com/shlomimatichin/env0-template-jupyter-gpu", + "tested2_template_path": "second" +} \ No newline at end of file diff --git a/examples/004_template/main.tf b/examples/004_template/main.tf new file mode 100644 index 00000000..6001639f --- /dev/null +++ b/examples/004_template/main.tf @@ -0,0 +1,52 @@ +data "env0_project" "default_project" { + name = "Default Organization Project" +} + +resource "env0_template" "tested1" { + name = "tested1" + description = "Tested 1 description" + type = "terraform" + repository = "https://github.com/shlomimatichin/env0-template-jupyter-gpu" + path = var.second_run ? "second" : "" + project_ids = [data.env0_project.default_project.id] + retries_on_deploy = 3 + retry_on_deploy_only_when_matches_regex = "abc" + retries_on_destroy = 1 +} + +resource "env0_configuration_variable" "in_a_template" { + name = "fake_key" + value = "fake value" + template_id = env0_template.tested1.id +} + +resource "env0_configuration_variable" "in_a_template2" { + name = "fake_key_2" + value = "fake value 2" + template_id = env0_template.tested1.id + type = "terraform" +} + +data "env0_template" "tested2" { + depends_on = [env0_template.tested1] + name = "tested1" +} +output "tested2_template_id" { + value = data.env0_template.tested2.id +} +output "tested2_template_type" { + value = data.env0_template.tested2.type +} +output "tested2_template_name" { + value = data.env0_template.tested2.name +} +output "tested2_template_repository" { + value = data.env0_template.tested2.repository +} +output "tested2_template_path" { + value = data.env0_template.tested2.path +} + +data "env0_template" "tested3" { + id = env0_template.tested1.id +} diff --git a/examples/005_ssh_key/conf.tf b/examples/005_ssh_key/conf.tf new file mode 100644 index 00000000..58dc45e8 --- /dev/null +++ b/examples/005_ssh_key/conf.tf @@ -0,0 +1,13 @@ +terraform { + backend "local" { + } + required_providers { + env0 = { + source = "terraform-registry.env0.com/env0/env0" + } + } +} + +provider "env0" {} + +variable "second_run" {} diff --git a/examples/005_ssh_key/expected_outputs.json b/examples/005_ssh_key/expected_outputs.json new file mode 100644 index 00000000..f0c0fe8e --- /dev/null +++ b/examples/005_ssh_key/expected_outputs.json @@ -0,0 +1,3 @@ +{ + "name": "test key" +} \ No newline at end of file diff --git a/examples/005_ssh_key/main.tf b/examples/005_ssh_key/main.tf new file mode 100644 index 00000000..6eb2689b --- /dev/null +++ b/examples/005_ssh_key/main.tf @@ -0,0 +1,32 @@ +resource "tls_private_key" "throwaway" { + algorithm = "RSA" +} +output "public_key_you_need_to_add_to_github_ssh_keys" { + value = tls_private_key.throwaway.public_key_openssh +} + +resource "env0_ssh_key" "tested" { + name = "test key" + value = tls_private_key.throwaway.private_key_pem +} + +data "env0_ssh_key" "tested" { + name = "test key" + depends_on = [env0_ssh_key.tested] +} + +data "env0_ssh_key" "tested2" { + id = env0_ssh_key.tested.id +} + +output "name" { + value = data.env0_ssh_key.tested2.name +} + +resource "env0_template" "usage" { + name = "use a ssh key" + description = "use a ssh key" + type = "terraform" + repository = "https://github.com/shlomimatichin/env0-template-jupyter-gpu" + ssh_key_names = [env0_ssh_key.tested.name] +} diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..1db5da54 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/env0/terraform-provider-env0 + +go 1.14 + +require ( + github.com/hashicorp/terraform-plugin-sdk/v2 v2.4.4 + github.com/pkg/errors v0.8.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..00b44dd1 --- /dev/null +++ b/go.sum @@ -0,0 +1,522 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +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/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= +github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +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/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= +github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= +github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= +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/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +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/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fluxio/iohelpers v0.0.0-20160419043813-3a4dd67a94d2/go.mod h1:c7sGIpDbBo0JZZ1tKyC1p5smWf8QcUjK4bFtZjHAecg= +github.com/fluxio/multierror v0.0.0-20160419044231-9c68d39025e5/go.mod h1:BEUDl7FG1cc76sM0J0x8dqr6RhiL4uqvk6oFkwuNyuM= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +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.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM= +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-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +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= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +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/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= +github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= +github.com/hashicorp/go-getter v1.4.0/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY= +github.com/hashicorp/go-getter v1.4.2-0.20200106182914-9813cbd4eb02/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY= +github.com/hashicorp/go-getter v1.5.0/go.mod h1:a7z7NPPfNQpJWcn4rSWFtdrSldqLdLPEF3d8nFMsSLM= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.15.0 h1:qMuK0wxsoW4D0ddCCYwPSTm4KQv1X1ke3WmPWZ0Mvsk= +github.com/hashicorp/go-hclog v0.15.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0= +github.com/hashicorp/go-plugin v1.4.0 h1:b0O7rs5uiJ99Iu9HugEzsM67afboErkHUWddUSpUO3A= +github.com/hashicorp/go-plugin v1.4.0/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +github.com/hashicorp/go-version v1.2.1/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/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl/v2 v2.3.0 h1:iRly8YaMwTBAKhn1Ybk7VSdzbnopghktCD031P8ggUE= +github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/terraform-exec v0.10.0/go.mod h1:tOT8j1J8rP05bZBGWXfMyU3HkLi1LWyqL3Bzsc3CJjo= +github.com/hashicorp/terraform-exec v0.13.0/go.mod h1:SGhto91bVRlgXQWcJ5znSz+29UZIa8kpBbkGwQ+g9E8= +github.com/hashicorp/terraform-json v0.5.0/go.mod h1:eAbqb4w0pSlRmdvl8fOyHAi/+8jnkVYN28gJkSJrLhU= +github.com/hashicorp/terraform-json v0.8.0/go.mod h1:3defM4kkMfttwiE7VakJDwCd4R+umhSQnvJwORXbprE= +github.com/hashicorp/terraform-plugin-go v0.2.1 h1:EW/R8bB2Zbkjmugzsy1d27yS8/0454b3MtYHkzOknqA= +github.com/hashicorp/terraform-plugin-go v0.2.1/go.mod h1:10V6F3taeDWVAoLlkmArKttR3IULlRWFAGtQIQTIDr4= +github.com/hashicorp/terraform-plugin-sdk v1.16.0 h1:NrkXMRjHErUPPTHQkZ6JIn6bByiJzGnlJzH1rVdNEuE= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.0.3/go.mod h1:oz4kkpfTJ/hA2VMD0WpITTd3yPDGpT4uN7CiKdre/YI= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.4.4 h1:6k0WcxFgVqF/GUFHPvAH8FIrCkoA1RInXzSxhkKamPg= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.4.4/go.mod h1:z+cMZ0iswzZOahBJ3XmNWgWkVnAd2bl8g+FhyyuPDH4= +github.com/hashicorp/terraform-plugin-test/v2 v2.1.2/go.mod h1:jerO5mrd+jVNALy8aiq+VZOg/CR8T2T1QR3jd6JKGOI= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +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= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +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/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mitchellh/cli v1.1.1/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.4 h1:ZU1VNC02qyufSZsjjs7+khruk2fKvbQ3TwRV/IBCeFA= +github.com/mitchellh/go-testing-interface v1.0.4/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack v4.0.1+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/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= +github.com/zclconf/go-cty v1.2.1 h1:vGMsygfmeCl4Xb6OA5U5XVAaQZ69FvoG7X2jUtQujb8= +github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +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= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/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= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +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/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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/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= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +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-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= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/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-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= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +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/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= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +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-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +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= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +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-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 h1:YFTFpQhgvrLrmxtiIncJxFXeCyq84ixuKWVCaCAi9Oc= +google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +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= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +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/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.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +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/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/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go new file mode 100644 index 00000000..f415ef0e --- /dev/null +++ b/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/env0/terraform-provider-env0/env0tfprovider" + "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: env0tfprovider.Provider, + }) +} diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 00000000..3c0cf813 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,7 @@ +.terraform.lock.hcl +.terraform/ +terraform.tfstate +terraform.tfstate.backup +crash.log +terraform.rc +fake_registry diff --git a/tests/harness.go b/tests/harness.go new file mode 100644 index 00000000..12df3ec4 --- /dev/null +++ b/tests/harness.go @@ -0,0 +1,225 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "path" + "strings" +) + +func main() { + printTerraformVersion() + makeSureRunningFromProjectRoot() + testNames := testNamesFromCommandLineArguments() + log.Println(len(testNames), " tests to run") + buildFakeTerraformRegistry() + destroyMode := os.Getenv("DESTROY_MODE") + for _, testName := range testNames { + if destroyMode == "DESTROY_ONLY" { + terraformDestory(testName) + } else { + success := runTest(testName, destroyMode != "NO_DESTROY") + if !success { + log.Fatalln("Halting due to test failure") + } + } + } +} + +func runTest(testName string, destroy bool) bool { + testDir := "examples/" + testName + toDelete := []string{ + ".terraform", + ".terraform.lock.hcl", + "terraform.tfstate", + "terraform.tfstate.backup", + "terraform.rc", + } + for _, oneToDelete := range toDelete { + os.RemoveAll(path.Join(testDir, oneToDelete)) + } + + log.Println("Running test ", testName) + _, err := terraformCommand(testName, "init") + if err != nil { + return false + } + terraformCommand(testName, "fmt") + if destroy { + defer terraformDestory(testName) + } + _, err = terraformCommand(testName, "apply", "-auto-approve", "-var", "second_run=0") + if err != nil { + return false + } + _, err = terraformCommand(testName, "apply", "-auto-approve", "-var", "second_run=1") + if err != nil { + return false + } + expectedOutputs, err := readExpectedOutputs(testName) + if err != nil { + return false + } + outputsBytes, err := terraformCommand(testName, "output", "-json") + if err != nil { + return false + } + outputs, err := bytesOfJsonToStringMap(outputsBytes) + if err != nil { + return false + } + for key, expectedValue := range expectedOutputs { + value, ok := outputs[key] + if !ok { + log.Println("Expected terraform output ", key, " but no such output was created") + return false + } + if value != expectedValue { + log.Println("Expected output ", key, " value to be ", expectedValue, " but found ", value) + return false + } + log.Printf("Verified expected '%s'='%s' in %s", key, value, testName) + } + if destroy { + _, err = terraformCommand(testName, "destroy", "-auto-approve") + if err != nil { + return false + } + } + log.Println("Successfully finished running test ", testName) + return true +} + +func readExpectedOutputs(testName string) (map[string]string, error) { + expectedBytes, err := ioutil.ReadFile(path.Join("examples", testName, "expected_outputs.json")) + if err != nil { + log.Println("Test folder for ", testName, " does not contain expected_outputs.json", err) + return nil, err + } + return bytesOfJsonToStringMap(expectedBytes) +} + +func bytesOfJsonToStringMap(data []byte) (map[string]string, error) { + var stringMapUncasted map[string]interface{} + err := json.Unmarshal(data, &stringMapUncasted) + if err != nil { + log.Println("Unable to parse expected_outputs.json:", err) + return nil, err + } + result := map[string]string{} + for key, valueUncasted := range stringMapUncasted { + switch value := valueUncasted.(type) { + case string: + result[key] = value + case map[string]interface{}: + result[key] = value["value"].(string) + } + } + return result, nil +} + +func terraformDestory(testName string) { + log.Println("Running destroy to clean up in", testName) + destroy := exec.Command("terraform", "destroy", "-auto-approve") + destroy.Dir = "examples/" + testName + destroy.CombinedOutput() + log.Println("Done running terraform destroy in", testName) +} + +func terraformCommand(testName string, arg ...string) ([]byte, error) { + cmd := exec.Command("terraform", arg...) + cmd.Dir = "examples/" + testName + log.Println("Running terraform ", arg, " in ", testName) + outputBytes, err := cmd.CombinedOutput() + output := string(outputBytes) + if err != nil { + log.Println("error running terraform ", arg, " in ", testName, " error: ", err, " output: ", output) + } else { + log.Println("Completed successfully terraform ", arg, " in ", testName) + } + return outputBytes, err +} + +func printTerraformVersion() { + versionString, err := exec.Command("terraform", "version").Output() + if err != nil { + log.Fatalln("Unable to invoke terraform. Perhaps it's not in PATH?", err) + } + log.Println("Terraform version: ", string(versionString)) +} + +func makeSureRunningFromProjectRoot() { + if _, err := os.Stat("tests"); err != nil { + log.Fatalln("Please `go run` from root folder") + } +} + +func testNamesFromCommandLineArguments() []string { + testNames := []string{} + if len(os.Args) > 1 { + for _, testName := range os.Args[1:] { + if strings.HasPrefix(testName, "examples/") { + testName = testName[len("examples/"):] + } + if strings.HasSuffix(testName, "/") { + testName = testName[:len(testName)-1] + } + testNames = append(testNames, testName) + } + } else { + allFilesUnderTests, err := ioutil.ReadDir("examples") + if err != nil { + log.Fatalln("Unable to list 'tests' folder", err) + } + for _, file := range allFilesUnderTests { + if strings.HasPrefix(file.Name(), "0") { + testNames = append(testNames, file.Name()) + } + } + } + return testNames +} + +func buildFakeTerraformRegistry() { + registry_dir := "tests/fake_registry/terraform-registry.env0.com/env0/env0/6.6.6/linux_amd64" + err := os.MkdirAll(registry_dir, 0755) + if err != nil { + log.Fatalln("Unable to create registry folder ", registry_dir, " error: ", err) + } + data, err := ioutil.ReadFile("terraform-provider-env0") + if err != nil { + log.Fatalln("Unable to read provider binary: did you build it?", err) + } + err = ioutil.WriteFile(registry_dir+"/terraform-provider-env0", data, 0755) + if err != nil { + log.Fatalln("Unable to write: ", err) + } + + cwd, err := os.Getwd() + if err != nil { + log.Fatalln("Unable to get current working dir", err) + } + terraformRc := fmt.Sprintf(` +provider_installation { + filesystem_mirror { + path = "%s/tests/fake_registry" + include = ["terraform-registry.env0.com/*/*"] + } + direct { + exclude = ["terraform-registry.env0.com/*/*"] + } +}`, cwd) + err = ioutil.WriteFile("tests/terraform.rc", []byte(terraformRc), 0644) + if err != nil { + log.Fatalln("Unable to write: ", err) + } + + err = os.Setenv("TF_CLI_CONFIG_FILE", path.Join(cwd, "tests", "terraform.rc")) + if err != nil { + log.Fatalln("Unable to set env:", err) + } +}