From d80dd5867f7a405d18a50e868406e02b8494600e Mon Sep 17 00:00:00 2001 From: Pascal Hofmann Date: Fri, 31 Mar 2023 14:41:08 +0200 Subject: [PATCH] Add resource ec_snapshot_repository This updates elastic/cloud-sdk-go to v1.12.1 because of `snaprepoapi.S3Config.PathStyleAccess` which was added there. --- docs/resources/ec_snapshot_repository.md | 74 +++ .../snapshotrepositoryresource/create.go | 98 ++++ .../snapshotrepositoryresource/delete.go | 54 ++ .../snapshotrepositoryresource/read.go | 144 +++++ .../resource_test.go | 542 ++++++++++++++++++ .../snapshotrepositoryresource/schema.go | 196 +++++++ .../snapshotrepositoryresource/update.go | 96 ++++ ec/provider.go | 2 + go.mod | 4 +- go.sum | 11 +- 10 files changed, 1216 insertions(+), 5 deletions(-) create mode 100644 docs/resources/ec_snapshot_repository.md create mode 100644 ec/ecresource/snapshotrepositoryresource/create.go create mode 100644 ec/ecresource/snapshotrepositoryresource/delete.go create mode 100644 ec/ecresource/snapshotrepositoryresource/read.go create mode 100644 ec/ecresource/snapshotrepositoryresource/resource_test.go create mode 100644 ec/ecresource/snapshotrepositoryresource/schema.go create mode 100644 ec/ecresource/snapshotrepositoryresource/update.go diff --git a/docs/resources/ec_snapshot_repository.md b/docs/resources/ec_snapshot_repository.md new file mode 100644 index 000000000..41173f729 --- /dev/null +++ b/docs/resources/ec_snapshot_repository.md @@ -0,0 +1,74 @@ +--- +page_title: "ec_snapshot_repository Resource - terraform-provider-ec" +description: |- + Manages Elastic Cloud Enterprise snapshot repositories. +--- + +# ec_snapshot_repository (Resource) + +Manages Elastic Cloud Enterprise snapshot repositories. + +~> **This resource can only be used with Elastic Cloud Enterprise** For Elastic Cloud SaaS please use the [elasticstack_elasticsearch_snapshot_repository](https://registry.terraform.io/providers/elastic/elasticstack/latest/docs/resources/elasticsearch_snapshot_repository) resource from the [Elastic Stack terraform provider](https://registry.terraform.io/providers/elastic/elasticstack/latest). + +## Example Usage + +```hcl +resource "ec_snapshot_repository" "this" { + name = "my-snapshot-repository" + s3 = { + bucket = "my-bucket" + access_key = "my-access-key" + secret_key = "my-secret-key" + } +} +``` + + +## Schema + +### Required + +- `name` (String) The name of the snapshot repository configuration. + +### Optional + +- `generic` (Attributes) Generic repository settings. (see [below for nested schema](#nestedatt--generic)) +- `s3` (Attributes) S3 repository settings. (see [below for nested schema](#nestedatt--s3)) + +### Read-Only + +- `id` (String) Unique identifier of this resource. + + +### Nested Schema for `generic` + +Required: + +- `settings` (String) (Required) An arbitrary JSON object containing the repository settings. +- `type` (String) (Required) Repository type + + + +### Nested Schema for `s3` + +Required: + +- `bucket` (String) (Required) Name of the S3 bucket to use for snapshots. + +Optional: + +- `access_key` (String) An S3 access key. If set, the secret_key setting must also be specified. If unset, the client will use the instance or container role instead. +- `secret_key` (String, Sensitive) An S3 secret key. If set, the access_key setting must also be specified. +- `server_side_encryption` (Boolean) When set to true files are encrypted on server side using AES256 algorithm. Defaults to false. +- `endpoint` (String) The S3 service endpoint to connect to. This defaults to s3.amazonaws.com but the AWS documentation lists alternative S3 endpoints. If you are using an S3-compatible service then you should set this to the service’s endpoint. +- `path_style_access` (Boolean) Whether to force the use of the path style access pattern. If true, the path style access pattern will be used. If false, the access pattern will be automatically determined by the AWS Java SDK (See AWS documentation for details). Defaults to false. +- `region` (String) Allows specifying the signing region to use. Specifying this setting manually should not be necessary for most use cases. Generally, the SDK will correctly guess the signing region to use. It should be considered an expert level setting to support S3-compatible APIs that require v4 signatures and use a region other than the default us-east-1. Defaults to empty string which means that the SDK will try to automatically determine the correct signing region. + + +## Import + +You can import snapshot repositories using the `name`, for example: + +``` +$ terraform import ec_snapshot_repository.this my-snapshot-repository +``` diff --git a/ec/ecresource/snapshotrepositoryresource/create.go b/ec/ecresource/snapshotrepositoryresource/create.go new file mode 100644 index 000000000..98c3b0892 --- /dev/null +++ b/ec/ecresource/snapshotrepositoryresource/create.go @@ -0,0 +1,98 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package snapshotrepositoryresource + +import ( + "context" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/elastic/cloud-sdk-go/pkg/api/platformapi/snaprepoapi" + "github.com/elastic/cloud-sdk-go/pkg/util" +) + +func (r *Resource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + if !resourceReady(r, &response.Diagnostics) { + return + } + + var newState modelV0 + + diags := request.Plan.Get(ctx, &newState) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + var repositoryType string + var repositoryConfig util.Validator + if newState.S3 != nil { + repositoryType = "s3" + repositoryConfig = snaprepoapi.S3Config{ + Region: newState.S3.Region.Value, + Bucket: newState.S3.Bucket.Value, + AccessKey: newState.S3.AccessKey.Value, + SecretKey: newState.S3.SecretKey.Value, + ServerSideEncryption: newState.S3.ServerSideEncryption.Value, + Endpoint: newState.S3.Endpoint.Value, + PathStyleAccess: newState.S3.PathStyleAccess.Value, + } + } else { + var err error + repositoryType = newState.Generic.Type.Value + repositoryConfig, err = snaprepoapi.ParseGenericConfig(strings.NewReader(newState.Generic.Settings.Value)) + if err != nil { + response.Diagnostics.AddError(err.Error(), err.Error()) + return + } + } + + err := snaprepoapi.Set( + snaprepoapi.SetParams{ + API: r.client, + Region: "ece-region", // This resource is only usable for ECE installations. Thus, we can default to ece-region. + Name: newState.Name.Value, + Type: repositoryType, + Config: repositoryConfig, + }, + ) + if err != nil { + response.Diagnostics.AddError(err.Error(), err.Error()) + return + } + + newState.ID = newState.Name + + found, diags := r.read(newState.ID.Value, &newState) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + if !found { + response.Diagnostics.AddError( + "Failed to read snapshot repository after create.", + "Failed to read snapshot repository after create.", + ) + response.State.RemoveResource(ctx) + return + } + + // Finally, set the state + response.Diagnostics.Append(response.State.Set(ctx, newState)...) +} diff --git a/ec/ecresource/snapshotrepositoryresource/delete.go b/ec/ecresource/snapshotrepositoryresource/delete.go new file mode 100644 index 000000000..b452ed631 --- /dev/null +++ b/ec/ecresource/snapshotrepositoryresource/delete.go @@ -0,0 +1,54 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package snapshotrepositoryresource + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/elastic/cloud-sdk-go/pkg/api/apierror" + "github.com/elastic/cloud-sdk-go/pkg/api/platformapi/snaprepoapi" +) + +// Delete will delete an existing snapshot repository +func (r *Resource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + if !resourceReady(r, &response.Diagnostics) { + return + } + + var state modelV0 + + diags := request.State.Get(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + err := snaprepoapi.Delete(snaprepoapi.DeleteParams{ + API: r.client, + Region: "ece-region", // This resource is only usable for ECE installations. Thus, we can default to ece-region. + Name: state.Name.Value, + }) + if err != nil { + if !apierror.IsRuntimeStatusCode(err, 404) { + response.Diagnostics.AddError(err.Error(), err.Error()) + } + return + } +} diff --git a/ec/ecresource/snapshotrepositoryresource/read.go b/ec/ecresource/snapshotrepositoryresource/read.go new file mode 100644 index 000000000..12e1bc919 --- /dev/null +++ b/ec/ecresource/snapshotrepositoryresource/read.go @@ -0,0 +1,144 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package snapshotrepositoryresource + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/elastic/cloud-sdk-go/pkg/api/apierror" + "github.com/elastic/cloud-sdk-go/pkg/api/platformapi/snaprepoapi" + "github.com/elastic/cloud-sdk-go/pkg/models" +) + +func (r *Resource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + if !resourceReady(r, &response.Diagnostics) { + return + } + + var newState modelV0 + + diags := request.State.Get(ctx, &newState) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + found, diags := r.read(newState.ID.Value, &newState) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + if !found { + response.State.RemoveResource(ctx) + return + } + + // Finally, set the state + response.Diagnostics.Append(response.State.Set(ctx, newState)...) +} + +func (r *Resource) read(id string, state *modelV0) (found bool, diags diag.Diagnostics) { + res, err := snaprepoapi.Get(snaprepoapi.GetParams{ + API: r.client, + Region: "ece-region", // This resource is only usable for ECE installations. Thus, we can default to ece-region. + Name: id, + }) + if err != nil { + if apierror.IsRuntimeStatusCode(err, 404) { + return false, diags + } + diags.AddError("failed reading snapshot repository", err.Error()) + return true, diags + } + + diags.Append(modelToState(res, state)...) + return true, diags +} + +func modelToState(model *models.RepositoryConfig, state *modelV0) diag.Diagnostics { + var diags diag.Diagnostics + + if model.RepositoryName != nil { + state.Name = types.String{Value: *model.RepositoryName} + } + + config, _ := model.Config.(map[string]interface{}) + if repositoryType, ok := config["type"]; ok && repositoryType != nil { + if settingsInterface, ok := config["settings"]; ok && settingsInterface != nil { + settings := settingsInterface.(map[string]interface{}) + // Parse into S3 schema if possible, but fall back to Generic when custom settings have been used. + if repositoryType.(string) == "s3" && containsOnlyKnownS3Settings(settings) { + if state.S3 == nil { + state.S3 = &s3RepositoryV0{} + } + if region, ok := settings["region"]; ok && region != nil { + state.S3.Region = types.String{Value: region.(string)} + } + if bucket, ok := settings["bucket"]; ok && bucket != nil { + state.S3.Bucket = types.String{Value: bucket.(string)} + } + if accessKey, ok := settings["access_key"]; ok && accessKey != nil { + state.S3.AccessKey = types.String{Value: accessKey.(string)} + } + if secretKey, ok := settings["secret_key"]; ok && secretKey != nil { + state.S3.SecretKey = types.String{Value: secretKey.(string)} + } + if serverSideEncryption, ok := settings["server_side_encryption"]; ok && serverSideEncryption != nil { + state.S3.ServerSideEncryption = types.Bool{Value: serverSideEncryption.(bool)} + } + if endpoint, ok := settings["endpoint"]; ok && endpoint != nil { + state.S3.Endpoint = types.String{Value: endpoint.(string)} + } + if pathStyleAccess, ok := settings["path_style_access"]; ok && pathStyleAccess != nil { + state.S3.PathStyleAccess = types.Bool{Value: pathStyleAccess.(bool)} + } + } else { + if state.Generic == nil { + state.Generic = &genericRepositoryV0{} + } + state.Generic.Type = types.String{Value: repositoryType.(string)} + jsonSettings, err := json.Marshal(settings) + if err != nil { + diags.AddError( + fmt.Sprintf("failed reading snapshot repository: unable to marshal settings - %s", err), + fmt.Sprintf("failed reading snapshot repository: unable to marshal settings - %s", err), + ) + } else { + state.Generic.Settings = types.String{Value: string(jsonSettings)} + } + } + } + } + return diags +} + +func containsOnlyKnownS3Settings(settings map[string]interface{}) bool { + attributes := s3Schema().Attributes.GetAttributes() + for key := range settings { + if _, ok := attributes[key]; !ok { + return false + } + } + return true +} diff --git a/ec/ecresource/snapshotrepositoryresource/resource_test.go b/ec/ecresource/snapshotrepositoryresource/resource_test.go new file mode 100644 index 000000000..ace7d0275 --- /dev/null +++ b/ec/ecresource/snapshotrepositoryresource/resource_test.go @@ -0,0 +1,542 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package snapshotrepositoryresource_test + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + r "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/elastic/cloud-sdk-go/pkg/api" + "github.com/elastic/cloud-sdk-go/pkg/api/mock" + provider "github.com/elastic/terraform-provider-ec/ec" +) + +func TestResourceSnapshotRepository(t *testing.T) { + r.UnitTest(t, r.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactoriesWithMockClient( + api.NewMock( + createResponse(s3Json1), + readResponse(s3Json1), + readResponse(s3Json1), + readResponse(s3Json1), + readResponse(s3Json1), + readResponse(s3Json1), + readResponse(s3Json1), + updateResponse(s3Json2), + readResponse(s3Json2), + readResponse(s3Json2), + readResponse(s3Json2), + readResponse(s3Json2), + updateResponse(genericJson), + readResponse(genericJson), + readResponse(genericJson), + readResponse(genericJson), + readResponse(genericJson), + deleteResponse(), + ), + ), + Steps: []r.TestStep{ + { // Create resource + Config: awsSnapshotRepository1, + Check: checkS3Resource1(), + }, + { // Ensure that it can be successfully read + PlanOnly: true, + Config: awsSnapshotRepository1, + Check: checkS3Resource1(), + }, + { // Ensure that it can be properly imported + ImportState: true, + ImportStateVerify: true, + ResourceName: "ec_snapshot_repository.this", + }, + { // Ensure that it can be successfully updated + Config: awsSnapshotRepository2, + Check: checkS3Resource2(), + }, + { // Ensure that it can be properly imported + ImportState: true, + ImportStateVerify: true, + ResourceName: "ec_snapshot_repository.this", + }, + { // Ensure that generic repositories work too + Config: genericSnapshotRepository, + Check: checkGenericResource(), + }, + { // Ensure that it can be properly imported + ImportState: true, + ImportStateVerify: true, + ResourceName: "ec_snapshot_repository.this", + }, + { // Delete resource + Destroy: true, + Config: genericSnapshotRepository, + }, + }, + }) +} +func TestResourceSnapshotRepositoryCreateGeneric(t *testing.T) { + r.UnitTest(t, r.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactoriesWithMockClient( + api.NewMock( + createResponse(genericJson), + readResponse(genericJson), + readResponse(genericJson), + readResponse(genericJson), + deleteResponse(), + ), + ), + Steps: []r.TestStep{ + { // Create resource + Config: genericSnapshotRepository, + Check: checkGenericResource(), + }, + { // Delete resource + Destroy: true, + Config: genericSnapshotRepository, + }, + }, + }) +} + +func TestResourceSnapshotRepository_failedCreate(t *testing.T) { + r.UnitTest(t, r.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactoriesWithMockClient( + api.NewMock( + failedCreateOrUpdateResponse(s3Json1), + ), + ), + Steps: []r.TestStep{ + { + Config: awsSnapshotRepository1, + ExpectError: regexp.MustCompile(`internal.server.error: There was an internal server error`), + }, + }, + }) +} + +func TestResourceSnapshotRepository_failedReadAfterCreate(t *testing.T) { + r.UnitTest(t, r.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactoriesWithMockClient( + api.NewMock( + createResponse(s3Json1), + failedReadResponse(), + deleteResponse(), + ), + ), + Steps: []r.TestStep{ + { + Config: awsSnapshotRepository1, + ExpectError: regexp.MustCompile(`failed reading snapshot repository`), + }, + }, + }) +} + +func TestResourceSnapshotRepository_notFoundAfterCreate(t *testing.T) { + r.UnitTest(t, r.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactoriesWithMockClient( + api.NewMock( + createResponse(s3Json1), + notFoundReadResponse(), + ), + ), + Steps: []r.TestStep{ + { + Config: awsSnapshotRepository1, + ExpectError: regexp.MustCompile(`Failed to read snapshot repository after create.`), + }, + }, + }) +} + +func TestResourceSnapshotRepository_notFoundAfterUpdate(t *testing.T) { + r.UnitTest(t, r.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactoriesWithMockClient( + api.NewMock( + createResponse(s3Json1), + readResponse(s3Json1), + readResponse(s3Json1), + readResponse(s3Json1), + updateResponse(s3Json2), + notFoundReadResponse(), + deleteResponse(), // required for cleanup + ), + ), + Steps: []r.TestStep{ + { // Create resource + Config: awsSnapshotRepository1, + Check: checkS3Resource1(), + }, + { // Update resource + Config: awsSnapshotRepository2, + ExpectError: regexp.MustCompile(`Failed to read snapshot repository after update.`), + }, + }, + }) +} + +func TestResourceSnapshotRepository_notFoundAfterRead(t *testing.T) { + r.UnitTest(t, r.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactoriesWithMockClient( + api.NewMock( + createResponse(s3Json1), + readResponse(s3Json1), + notFoundReadResponse(), + ), + ), + Steps: []r.TestStep{ + { // Create resource + Config: awsSnapshotRepository1, + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestResourceSnapshotRepository_failedRead(t *testing.T) { + r.UnitTest(t, r.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactoriesWithMockClient( + api.NewMock( + createResponse(s3Json1), + readResponse(s3Json1), + failedReadResponse(), + deleteResponse(), // required for cleanup + ), + ), + Steps: []r.TestStep{ + { + Config: awsSnapshotRepository1, + Check: checkS3Resource1(), + ExpectError: regexp.MustCompile(`internal.server.error: There was an internal server error`), + }, + }, + }) +} + +func TestResourceSnapshotRepository_failedUpdate(t *testing.T) { + r.UnitTest(t, r.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactoriesWithMockClient( + api.NewMock( + createResponse(s3Json1), + readResponse(s3Json1), + readResponse(s3Json1), + readResponse(s3Json1), + failedCreateOrUpdateResponse(s3Json2), + deleteResponse(), // required for cleanup + ), + ), + Steps: []r.TestStep{ + { // Create resource + Config: awsSnapshotRepository1, + Check: checkS3Resource1(), + }, + { // Update resource + Config: awsSnapshotRepository2, + ExpectError: regexp.MustCompile(`internal.server.error: There was an internal server error`), + }, + }, + }) +} + +func TestResourceSnapshotRepository_failedReadAfterUpdate(t *testing.T) { + r.UnitTest(t, r.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactoriesWithMockClient( + api.NewMock( + createResponse(s3Json1), + readResponse(s3Json1), + readResponse(s3Json1), + readResponse(s3Json1), + updateResponse(s3Json2), + failedReadResponse(), + deleteResponse(), + ), + ), + Steps: []r.TestStep{ + { + Config: awsSnapshotRepository1, + Check: checkS3Resource1(), + }, + { // Update resource + Config: awsSnapshotRepository2, + ExpectError: regexp.MustCompile(`internal.server.error: There was an internal server error`), + }, + }, + }) +} + +func TestResourceSnapshotRepository_gracefulDeletion(t *testing.T) { + r.UnitTest(t, r.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactoriesWithMockClient( + api.NewMock( + createResponse(s3Json1), + readResponse(s3Json1), + readResponse(s3Json1), + readResponse(s3Json1), + alreadyDeletedResponse(), + ), + ), + Steps: []r.TestStep{ + { // Create resource + Config: awsSnapshotRepository1, + Check: checkS3Resource1(), + }, + { // Delete resource + Destroy: true, + Config: awsSnapshotRepository1, + }, + }, + }) +} + +func TestResourceSnapshotRepository_failedDeletion(t *testing.T) { + r.UnitTest(t, r.TestCase{ + ProtoV6ProviderFactories: protoV6ProviderFactoriesWithMockClient( + api.NewMock( + createResponse(s3Json1), + readResponse(s3Json1), + readResponse(s3Json1), + readResponse(s3Json1), + failedDeletionResponse(), + deleteResponse(), // required for cleanup + ), + ), + Steps: []r.TestStep{ + { + Config: awsSnapshotRepository1, + }, + { + Destroy: true, + Config: awsSnapshotRepository1, + ExpectError: regexp.MustCompile(`internal.server.error: There was an internal server error`), + }, + }, + }) +} + +const awsSnapshotRepository1 = ` + resource "ec_snapshot_repository" "this" { + name = "my-snapshot-repository" + s3 = { + region = "us-east-1" + bucket = "my-bucket" + access_key = "my-access-key" + secret_key = "my-secret-key" + server_side_encryption = true + endpoint = "s3.amazonaws.com" + path_style_access = true + } + } +` +const awsSnapshotRepository2 = ` + resource "ec_snapshot_repository" "this" { + name = "my-snapshot-repository" + s3 = { + region = "us-west-1" + bucket = "my-bucket2" + access_key = "my-access-key2" + secret_key = "my-secret-key2" + endpoint = "s3.us-west-1.amazonaws.com" + path_style_access = false + } + } +` + +const genericSnapshotRepository = ` + resource "ec_snapshot_repository" "this" { + name = "my-snapshot-repository" + generic = { + type = "azure" + settings = jsonencode({ + bucket = "my-bucket" + client = "my_alternate_client" + compress = false + }) + } + } +` + +const s3Json1 = `{"settings":{"region":"us-east-1","bucket":"my-bucket","access_key":"my-access-key","secret_key":"my-secret-key","server_side_encryption":true,"endpoint":"s3.amazonaws.com","path_style_access":true},"type":"s3"}` +const s3Json2 = `{"settings":{"region":"us-west-1","bucket":"my-bucket2","access_key":"my-access-key2","secret_key":"my-secret-key2","endpoint":"s3.us-west-1.amazonaws.com"},"type":"s3"}` +const genericJson = `{"settings":{"bucket":"my-bucket","client":"my_alternate_client","compress":false},"type":"azure"}` + +func checkS3Resource1() r.TestCheckFunc { + resource := "ec_snapshot_repository.this" + return r.ComposeAggregateTestCheckFunc( + r.TestCheckResourceAttr(resource, "id", "my-snapshot-repository"), + r.TestCheckResourceAttr(resource, "name", "my-snapshot-repository"), + + r.TestCheckResourceAttr(resource, "s3.region", "us-east-1"), + r.TestCheckResourceAttr(resource, "s3.bucket", "my-bucket"), + r.TestCheckResourceAttr(resource, "s3.access_key", "my-access-key"), + r.TestCheckResourceAttr(resource, "s3.secret_key", "my-secret-key"), + r.TestCheckResourceAttr(resource, "s3.server_side_encryption", "true"), + r.TestCheckResourceAttr(resource, "s3.endpoint", "s3.amazonaws.com"), + r.TestCheckResourceAttr(resource, "s3.path_style_access", "true"), + ) +} +func checkS3Resource2() r.TestCheckFunc { + resource := "ec_snapshot_repository.this" + return r.ComposeAggregateTestCheckFunc( + r.TestCheckResourceAttr(resource, "id", "my-snapshot-repository"), + r.TestCheckResourceAttr(resource, "name", "my-snapshot-repository"), + r.TestCheckResourceAttr(resource, "s3.bucket", "my-bucket2"), + r.TestCheckResourceAttr(resource, "s3.access_key", "my-access-key2"), + r.TestCheckResourceAttr(resource, "s3.secret_key", "my-secret-key2"), + r.TestCheckResourceAttr(resource, "s3.endpoint", "s3.us-west-1.amazonaws.com"), + r.TestCheckResourceAttr(resource, "s3.path_style_access", "false"), + r.TestCheckResourceAttr(resource, "s3.region", "us-west-1"), + ) +} +func checkGenericResource() r.TestCheckFunc { + resource := "ec_snapshot_repository.this" + return r.ComposeAggregateTestCheckFunc( + r.TestCheckResourceAttr(resource, "id", "my-snapshot-repository"), + r.TestCheckResourceAttr(resource, "name", "my-snapshot-repository"), + r.TestCheckResourceAttr(resource, "generic.type", "azure"), + r.TestCheckResourceAttr(resource, "generic.settings", "{\"bucket\":\"my-bucket\",\"client\":\"my_alternate_client\",\"compress\":false}"), + ) +} + +func createResponse(json string) mock.Response { + return mock.New200ResponseAssertion( + &mock.RequestAssertion{ + Host: api.DefaultMockHost, + Header: api.DefaultWriteMockHeaders, + Method: "PUT", + Path: "/api/v1/regions/ece-region/platform/configuration/snapshots/repositories/my-snapshot-repository", + Body: mock.NewStringBody(json + "\n"), + }, + mock.NewStringBody(json), + ) +} + +func updateResponse(json string) mock.Response { + return mock.New200ResponseAssertion( + &mock.RequestAssertion{ + Host: api.DefaultMockHost, + Header: api.DefaultWriteMockHeaders, + Method: "PUT", + Path: "/api/v1/regions/ece-region/platform/configuration/snapshots/repositories/my-snapshot-repository", + Body: mock.NewStringBody(json + "\n"), + }, + mock.NewStringBody(json), + ) +} + +func failedCreateOrUpdateResponse(json string) mock.Response { + return mock.New500ResponseAssertion( + &mock.RequestAssertion{ + Host: api.DefaultMockHost, + Header: api.DefaultWriteMockHeaders, + Method: "PUT", + Path: "/api/v1/regions/ece-region/platform/configuration/snapshots/repositories/my-snapshot-repository", + Body: mock.NewStringBody(json + "\n"), + }, + mock.SampleInternalError().Response.Body, + ) +} + +func readResponse(json string) mock.Response { + return mock.New200ResponseAssertion( + &mock.RequestAssertion{ + Host: api.DefaultMockHost, + Header: api.DefaultReadMockHeaders, + Method: "GET", + Path: "/api/v1/regions/ece-region/platform/configuration/snapshots/repositories/my-snapshot-repository", + }, + mock.NewStringBody(`{ + "repository_name" : "my-snapshot-repository", + "config" : `+json+` + +}`), + ) +} + +func failedReadResponse() mock.Response { + return mock.New500ResponseAssertion( + &mock.RequestAssertion{ + Host: api.DefaultMockHost, + Header: api.DefaultReadMockHeaders, + Method: "GET", + Path: "/api/v1/regions/ece-region/platform/configuration/snapshots/repositories/my-snapshot-repository", + }, + mock.SampleInternalError().Response.Body, + ) +} + +func notFoundReadResponse() mock.Response { + return mock.New404ResponseAssertion( + &mock.RequestAssertion{ + Host: api.DefaultMockHost, + Header: api.DefaultReadMockHeaders, + Method: "GET", + Path: "/api/v1/regions/ece-region/platform/configuration/snapshots/repositories/my-snapshot-repository", + }, + mock.NewStringBody(`{"errors":[{"code":"root.resource_not_found","message":"The requested resource could not be found"}]}`), + ) +} + +func deleteResponse() mock.Response { + return mock.New200ResponseAssertion( + &mock.RequestAssertion{ + Host: api.DefaultMockHost, + Header: api.DefaultReadMockHeaders, + Method: "DELETE", + Path: "/api/v1/regions/ece-region/platform/configuration/snapshots/repositories/my-snapshot-repository", + }, + mock.NewStringBody(`{}`), + ) +} + +func alreadyDeletedResponse() mock.Response { + return mock.New404ResponseAssertion( + &mock.RequestAssertion{ + Host: api.DefaultMockHost, + Header: api.DefaultReadMockHeaders, + Method: "DELETE", + Path: "/api/v1/regions/ece-region/platform/configuration/snapshots/repositories/my-snapshot-repository", + }, + mock.NewStringBody(`{ }`), + ) +} + +func failedDeletionResponse() mock.Response { + mock.SampleInternalError() + return mock.New500ResponseAssertion( + &mock.RequestAssertion{ + Host: api.DefaultMockHost, + Header: api.DefaultReadMockHeaders, + Method: "DELETE", + Path: "/api/v1/regions/ece-region/platform/configuration/snapshots/repositories/my-snapshot-repository", + }, + mock.SampleInternalError().Response.Body, + ) +} + +func protoV6ProviderFactoriesWithMockClient(client *api.API) map[string]func() (tfprotov6.ProviderServer, error) { + return map[string]func() (tfprotov6.ProviderServer, error){ + "ec": func() (tfprotov6.ProviderServer, error) { + return providerserver.NewProtocol6(provider.ProviderWithClient(client, "unit-tests"))(), nil + }, + } +} diff --git a/ec/ecresource/snapshotrepositoryresource/schema.go b/ec/ecresource/snapshotrepositoryresource/schema.go new file mode 100644 index 000000000..2626eafe7 --- /dev/null +++ b/ec/ecresource/snapshotrepositoryresource/schema.go @@ -0,0 +1,196 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package snapshotrepositoryresource + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/elastic/cloud-sdk-go/pkg/api" + "github.com/elastic/terraform-provider-ec/ec/internal" + "github.com/elastic/terraform-provider-ec/ec/internal/planmodifier" +) + +// Ensure provider defined types fully satisfy framework interfaces +var _ resource.Resource = &Resource{} +var _ resource.ResourceWithConfigure = &Resource{} +var _ resource.ResourceWithImportState = &Resource{} +var _ resource.ResourceWithConfigValidators = &Resource{} + +func (r *Resource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "id": { + Type: types.StringType, + MarkdownDescription: "Unique identifier of this resource.", + Computed: true, + PlanModifiers: tfsdk.AttributePlanModifiers{ + resource.UseStateForUnknown(), + }, + }, + "name": { + Type: types.StringType, + Description: "The name of the snapshot repository configuration.", + Required: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + resource.RequiresReplace(), + }, + }, + "generic": genericSchema(), + "s3": s3Schema(), + }, + }, nil +} + +func s3Schema() tfsdk.Attribute { + return tfsdk.Attribute{ + Description: "S3 repository settings.", + Optional: true, + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "region": { + Type: types.StringType, + Description: "Allows specifying the signing region to use. Specifying this setting manually should not be necessary for most use cases. Generally, the SDK will correctly guess the signing region to use. It should be considered an expert level setting to support S3-compatible APIs that require v4 signatures and use a region other than the default us-east-1. Defaults to empty string which means that the SDK will try to automatically determine the correct signing region.", + Optional: true, + }, + "bucket": { + Type: types.StringType, + Description: "(Required) Name of the S3 bucket to use for snapshots.", + Required: true, + }, + "access_key": { + Type: types.StringType, + Description: "An S3 access key. If set, the secret_key setting must also be specified. If unset, the client will use the instance or container role instead.", + Optional: true, + }, + "secret_key": { + Type: types.StringType, + Description: "An S3 secret key. If set, the access_key setting must also be specified.", + Optional: true, + Sensitive: true, + }, + "server_side_encryption": { + Type: types.BoolType, + Description: "When set to true files are encrypted on server side using AES256 algorithm. Defaults to false.", + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifier.DefaultValue(types.Bool{Value: false}), + }, + }, + "endpoint": { + Type: types.StringType, + Description: "The S3 service endpoint to connect to. This defaults to s3.amazonaws.com but the AWS documentation lists alternative S3 endpoints. If you are using an S3-compatible service then you should set this to the service’s endpoint.", + Optional: true, + }, + "path_style_access": { + Type: types.BoolType, + Description: "Whether to force the use of the path style access pattern. If true, the path style access pattern will be used. If false, the access pattern will be automatically determined by the AWS Java SDK (See AWS documentation for details). Defaults to false.", + Optional: true, + Computed: true, + PlanModifiers: []tfsdk.AttributePlanModifier{ + planmodifier.DefaultValue(types.Bool{Value: false}), + }, + }, + }), + } +} + +func genericSchema() tfsdk.Attribute { + return tfsdk.Attribute{ + Description: "Generic repository settings.", + Optional: true, + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "type": { + Type: types.StringType, + Description: "(Required) Repository type", + Required: true, + }, + "settings": { + Type: types.StringType, + Description: "(Required) An arbitrary JSON object containing the repository settings.", + Required: true, + }, + }), + } +} + +type Resource struct { + client *api.API +} + +func (r *Resource) ConfigValidators(ctx context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + resourcevalidator.ExactlyOneOf( + path.MatchRoot("generic"), + path.MatchRoot("s3"), + ), + } +} + +func (r *Resource) Configure(ctx context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) { + client, diags := internal.ConvertProviderData(request.ProviderData) + response.Diagnostics.Append(diags...) + r.client = client +} + +func (r *Resource) Metadata(ctx context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = request.ProviderTypeName + "_snapshot_repository" +} + +func (r *Resource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), request, response) +} + +func resourceReady(r *Resource, dg *diag.Diagnostics) bool { + if r.client == nil { + dg.AddError( + "Unconfigured API Client", + "Expected configured API client. Please report this issue to the provider developers.", + ) + + return false + } + return true +} + +type modelV0 struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + S3 *s3RepositoryV0 `tfsdk:"s3"` + Generic *genericRepositoryV0 `tfsdk:"generic"` +} + +type s3RepositoryV0 struct { + Region types.String `tfsdk:"region"` + Bucket types.String `tfsdk:"bucket"` + AccessKey types.String `tfsdk:"access_key"` + SecretKey types.String `tfsdk:"secret_key"` + ServerSideEncryption types.Bool `tfsdk:"server_side_encryption"` + Endpoint types.String `tfsdk:"endpoint"` + PathStyleAccess types.Bool `tfsdk:"path_style_access"` +} +type genericRepositoryV0 struct { + Type types.String `tfsdk:"type"` + Settings types.String `tfsdk:"settings"` +} diff --git a/ec/ecresource/snapshotrepositoryresource/update.go b/ec/ecresource/snapshotrepositoryresource/update.go new file mode 100644 index 000000000..f39a6b1ea --- /dev/null +++ b/ec/ecresource/snapshotrepositoryresource/update.go @@ -0,0 +1,96 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package snapshotrepositoryresource + +import ( + "context" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/elastic/cloud-sdk-go/pkg/api/platformapi/snaprepoapi" + "github.com/elastic/cloud-sdk-go/pkg/util" +) + +func (r *Resource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + if !resourceReady(r, &response.Diagnostics) { + return + } + + var newState modelV0 + + diags := request.Plan.Get(ctx, &newState) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + var repositoryType string + var repositoryConfig util.Validator + if newState.S3 != nil { + repositoryType = "s3" + repositoryConfig = snaprepoapi.S3Config{ + Region: newState.S3.Region.Value, + Bucket: newState.S3.Bucket.Value, + AccessKey: newState.S3.AccessKey.Value, + SecretKey: newState.S3.SecretKey.Value, + ServerSideEncryption: newState.S3.ServerSideEncryption.Value, + Endpoint: newState.S3.Endpoint.Value, + PathStyleAccess: newState.S3.PathStyleAccess.Value, + } + } else { + var err error + repositoryType = newState.Generic.Type.Value + repositoryConfig, err = snaprepoapi.ParseGenericConfig(strings.NewReader(newState.Generic.Settings.Value)) + if err != nil { + response.Diagnostics.AddError(err.Error(), err.Error()) + return + } + } + + err := snaprepoapi.Set( + snaprepoapi.SetParams{ + API: r.client, + Region: "ece-region", // This resource is only usable for ECE installations. Thus, we can default to ece-region. + Name: newState.Name.Value, + Type: repositoryType, + Config: repositoryConfig, + }, + ) + if err != nil { + response.Diagnostics.AddError(err.Error(), err.Error()) + return + } + + found, diags := r.read(newState.ID.Value, &newState) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + if !found { + response.Diagnostics.AddError( + "Failed to read snapshot repository after update.", + "Failed to read snapshot repository after update.", + ) + response.State.RemoveResource(ctx) + return + } + + // Finally, set the state + response.Diagnostics.Append(response.State.Set(ctx, newState)...) +} diff --git a/ec/provider.go b/ec/provider.go index c09717488..47c77e707 100644 --- a/ec/provider.go +++ b/ec/provider.go @@ -37,6 +37,7 @@ import ( "github.com/elastic/terraform-provider-ec/ec/ecresource/deploymentresource" "github.com/elastic/terraform-provider-ec/ec/ecresource/elasticsearchkeystoreresource" "github.com/elastic/terraform-provider-ec/ec/ecresource/extensionresource" + "github.com/elastic/terraform-provider-ec/ec/ecresource/snapshotrepositoryresource" "github.com/elastic/terraform-provider-ec/ec/ecresource/trafficfilterassocresource" "github.com/elastic/terraform-provider-ec/ec/ecresource/trafficfilterresource" "github.com/elastic/terraform-provider-ec/ec/internal/util" @@ -100,6 +101,7 @@ func (p *Provider) Resources(ctx context.Context) []func() resource.Resource { func() resource.Resource { return &elasticsearchkeystoreresource.Resource{} }, func() resource.Resource { return &extensionresource.Resource{} }, func() resource.Resource { return &deploymentresource.Resource{} }, + func() resource.Resource { return &snapshotrepositoryresource.Resource{} }, func() resource.Resource { return &trafficfilterresource.Resource{} }, func() resource.Resource { return &trafficfilterassocresource.Resource{} }, } diff --git a/go.mod b/go.mod index 976e439bd..b5c624376 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( github.com/blang/semver v3.5.1+incompatible github.com/blang/semver/v4 v4.0.0 - github.com/elastic/cloud-sdk-go v1.10.0 + github.com/elastic/cloud-sdk-go v1.12.1 github.com/go-openapi/runtime v0.24.2 github.com/go-openapi/strfmt v0.21.3 github.com/hashicorp/terraform-plugin-framework v0.14.0 @@ -23,6 +23,7 @@ require ( github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.13.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect github.com/go-openapi/analysis v0.21.4 // indirect github.com/go-openapi/errors v0.20.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect @@ -62,6 +63,7 @@ require ( github.com/oklog/run v1.1.0 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect diff --git a/go.sum b/go.sum index 7019d770b..1796b9437 100644 --- a/go.sum +++ b/go.sum @@ -40,15 +40,15 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/elastic/cloud-sdk-go v1.10.0 h1:1WBUkP71ogoxynWfaGg5Bm8Z36F4tL3bjiu+e8YctkQ= -github.com/elastic/cloud-sdk-go v1.10.0/go.mod h1:BMx5iwmVwL8gpomLSMPI6gcvfWzrV4KsWSnbPlWwlrI= +github.com/elastic/cloud-sdk-go v1.12.1 h1:ZtpIM2X0UoXIxQJdihOOkWh2isf5nljOr7TtTQ/XHVs= +github.com/elastic/cloud-sdk-go v1.12.1/go.mod h1:YJfcOSFF/MS+o9dWZAUs+JYWfoeRWzAKmtUm27cbUzA= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -57,6 +57,7 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -298,6 +299,7 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= @@ -381,6 +383,7 @@ github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUr github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -400,7 +403,7 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=