From b9a0301d08677ae7f1d9c09cf1c888b0cf28865c Mon Sep 17 00:00:00 2001 From: snowhork Date: Sat, 26 Dec 2020 07:34:42 +0900 Subject: [PATCH 01/25] ec_extension: implement extension resource implement ec_extension resource and make example Closes #102 --- ec/ecresource/extensionresource/create.go | 46 +++++++ .../extensionresource/create_test.go | 92 ++++++++++++++ ec/ecresource/extensionresource/delete.go | 48 ++++++++ .../extensionresource/delete_test.go | 115 ++++++++++++++++++ ec/ecresource/extensionresource/read.go | 83 +++++++++++++ ec/ecresource/extensionresource/read_test.go | 115 ++++++++++++++++++ ec/ecresource/extensionresource/request.go | 115 ++++++++++++++++++ ec/ecresource/extensionresource/resource.go | 41 +++++++ ec/ecresource/extensionresource/schema.go | 64 ++++++++++ .../extensionresource/testutil_datastruct.go | 27 ++++ ec/ecresource/extensionresource/update.go | 45 +++++++ .../extensionresource/update_test.go | 92 ++++++++++++++ ec/provider.go | 2 + examples/extension/README.md | 12 ++ examples/extension/extension.tf | 27 ++++ examples/extension/files/content.json | 1 + examples/extension/files/content.json.zip | Bin 0 -> 381 bytes examples/extension/outputs.tf | 3 + 18 files changed, 928 insertions(+) create mode 100644 ec/ecresource/extensionresource/create.go create mode 100644 ec/ecresource/extensionresource/create_test.go create mode 100644 ec/ecresource/extensionresource/delete.go create mode 100644 ec/ecresource/extensionresource/delete_test.go create mode 100644 ec/ecresource/extensionresource/read.go create mode 100644 ec/ecresource/extensionresource/read_test.go create mode 100644 ec/ecresource/extensionresource/request.go create mode 100644 ec/ecresource/extensionresource/resource.go create mode 100644 ec/ecresource/extensionresource/schema.go create mode 100644 ec/ecresource/extensionresource/testutil_datastruct.go create mode 100644 ec/ecresource/extensionresource/update.go create mode 100644 ec/ecresource/extensionresource/update_test.go create mode 100644 examples/extension/README.md create mode 100644 examples/extension/extension.tf create mode 100644 examples/extension/files/content.json create mode 100644 examples/extension/files/content.json.zip create mode 100644 examples/extension/outputs.tf diff --git a/ec/ecresource/extensionresource/create.go b/ec/ecresource/extensionresource/create.go new file mode 100644 index 000000000..74f88463b --- /dev/null +++ b/ec/ecresource/extensionresource/create.go @@ -0,0 +1,46 @@ +// 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 extensionresource + +import ( + "context" + "github.com/elastic/cloud-sdk-go/pkg/api" + "github.com/elastic/cloud-sdk-go/pkg/multierror" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// Create will createResource a new deployment traffic filter ruleset +func createResource(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var client = meta.(*api.API) + + res, err := createRequest(client, d) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(*res.Payload.ID) + + if d.Get("file_path") != nil { + _, err = uploadRequest(client, d) + if err != nil { + return diag.FromErr(multierror.NewPrefixed("failed to upload file", err)) + } + } + return readResource(ctx, d, meta) +} diff --git a/ec/ecresource/extensionresource/create_test.go b/ec/ecresource/extensionresource/create_test.go new file mode 100644 index 000000000..2c33b134f --- /dev/null +++ b/ec/ecresource/extensionresource/create_test.go @@ -0,0 +1,92 @@ +// 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 extensionresource + +import ( + "context" + "testing" + + "github.com/elastic/cloud-sdk-go/pkg/api" + "github.com/elastic/cloud-sdk-go/pkg/api/mock" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/assert" + + "github.com/elastic/terraform-provider-ec/ec/internal/util" +) + +func Test_createResource(t *testing.T) { + tc500Err := util.NewResourceData(t, util.ResDataParams{ + ID: "12345678", + State: newExtension(), + Schema: newSchema(), + }) + wantTC500 := util.NewResourceData(t, util.ResDataParams{ + ID: "12345678", + State: newExtension(), + Schema: newSchema(), + }) + + type args struct { + ctx context.Context + d *schema.ResourceData + meta interface{} + } + tests := []struct { + name string + args args + want diag.Diagnostics + wantRD *schema.ResourceData + }{ + { + name: "returns an error when it receives a 500", + args: args{ + d: tc500Err, + meta: api.NewMock(mock.NewErrorResponse(500, mock.APIError{ + Code: "some", Message: "message", + })), + }, + want: diag.Diagnostics{ + { + Severity: diag.Error, + Summary: "api error: 1 error occurred:\n\t* some: message\n\n", + }, + }, + wantRD: wantTC500, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := createResource(tt.args.ctx, tt.args.d, tt.args.meta) + assert.Equal(t, tt.want, got) + var want interface{} + if tt.wantRD != nil { + if s := tt.wantRD.State(); s != nil { + want = s.Attributes + } + } + + var gotState interface{} + if s := tt.args.d.State(); s != nil { + gotState = s.Attributes + } + + assert.Equal(t, want, gotState) + }) + } +} diff --git a/ec/ecresource/extensionresource/delete.go b/ec/ecresource/extensionresource/delete.go new file mode 100644 index 000000000..706fedb3f --- /dev/null +++ b/ec/ecresource/extensionresource/delete.go @@ -0,0 +1,48 @@ +// 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 extensionresource + +import ( + "context" + "github.com/elastic/cloud-sdk-go/pkg/api" + "github.com/elastic/cloud-sdk-go/pkg/api/apierror" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func deleteResource(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var client = meta.(*api.API) + + _, err := deleteRequest(client, d) + + if err != nil { + if alreadyDestroyed(err) { + d.SetId("") + return nil + } + + return diag.FromErr(err) + } + + return nil +} + +func alreadyDestroyed(err error) bool { + // If the extension is already destroyed, API return 403. + return apierror.IsRuntimeStatusCode(err, 403) +} diff --git a/ec/ecresource/extensionresource/delete_test.go b/ec/ecresource/extensionresource/delete_test.go new file mode 100644 index 000000000..ebe6c7954 --- /dev/null +++ b/ec/ecresource/extensionresource/delete_test.go @@ -0,0 +1,115 @@ +// 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 extensionresource + +import ( + "context" + "testing" + + "github.com/elastic/cloud-sdk-go/pkg/api" + "github.com/elastic/cloud-sdk-go/pkg/api/mock" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/assert" + + "github.com/elastic/terraform-provider-ec/ec/internal/util" +) + +func Test_deleteResource(t *testing.T) { + tc500Err := util.NewResourceData(t, util.ResDataParams{ + ID: "12345678", + State: newExtension(), + Schema: newSchema(), + }) + wantTC500 := util.NewResourceData(t, util.ResDataParams{ + ID: "12345678", + State: newExtension(), + Schema: newSchema(), + }) + + tc403Err := util.NewResourceData(t, util.ResDataParams{ + ID: "12345678", + State: newExtension(), + Schema: newSchema(), + }) + wantTC403 := util.NewResourceData(t, util.ResDataParams{ + ID: "12345678", + State: newExtension(), + Schema: newSchema(), + }) + wantTC403.SetId("") + + type args struct { + ctx context.Context + d *schema.ResourceData + meta interface{} + } + tests := []struct { + name string + args args + want diag.Diagnostics + wantRD *schema.ResourceData + }{ + { + name: "returns an error when it receives a 500", + args: args{ + d: tc500Err, + meta: api.NewMock(mock.NewErrorResponse(500, mock.APIError{ + Code: "some", Message: "message", + })), + }, + want: diag.Diagnostics{ + { + Severity: diag.Error, + Summary: "api error: 1 error occurred:\n\t* some: message\n\n", + }, + }, + wantRD: wantTC500, + }, + { + name: "returns nil and unsets the state when the error is known", + args: args{ + d: tc403Err, + meta: api.NewMock(mock.NewErrorResponse(403, mock.APIError{ + Code: "some", Message: "message", + })), + }, + want: nil, + wantRD: wantTC403, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := deleteResource(tt.args.ctx, tt.args.d, tt.args.meta) + assert.Equal(t, tt.want, got) + var want interface{} + if tt.wantRD != nil { + if s := tt.wantRD.State(); s != nil { + want = s.Attributes + } + } + + var gotState interface{} + if s := tt.args.d.State(); s != nil { + gotState = s.Attributes + } + + assert.Equal(t, want, gotState) + }) + } +} diff --git a/ec/ecresource/extensionresource/read.go b/ec/ecresource/extensionresource/read.go new file mode 100644 index 000000000..2cf7ac9f5 --- /dev/null +++ b/ec/ecresource/extensionresource/read.go @@ -0,0 +1,83 @@ +// 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 extensionresource + +import ( + "context" + "errors" + "github.com/elastic/cloud-sdk-go/pkg/api" + "github.com/elastic/cloud-sdk-go/pkg/api/apierror" + "github.com/elastic/cloud-sdk-go/pkg/client/extensions" + "github.com/elastic/cloud-sdk-go/pkg/models" + "github.com/elastic/cloud-sdk-go/pkg/multierror" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func readResource(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var client = meta.(*api.API) + + res, err := readRequest(d, client) + + if err != nil { + if extensionNotFound(err) { + d.SetId("") + return nil + } + + return diag.FromErr(multierror.NewPrefixed("failed reading extension", err)) + } + + if err := modelToState(d, res.Payload); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func extensionNotFound(err error) bool { + // We're using the As() call since we do not care about the error value + // but do care about the error's contents type since it's an implicit 404. + var extensionNotFound *extensions.GetExtensionNotFound + if errors.As(err, &extensionNotFound) { + return true + } + + // We also check for the case where a 403 is thrown for ESS. + return apierror.IsRuntimeStatusCode(err, 403) +} + +func modelToState(d *schema.ResourceData, model *models.Extension) error { + if err := d.Set("name", model.Name); err != nil { + return err + } + + if err := d.Set("version", model.Version); err != nil { + return err + } + + if err := d.Set("extension_type", model.ExtensionType); err != nil { + return err + } + + if err := d.Set("description", model.Description); err != nil { + return err + } + + return nil +} diff --git a/ec/ecresource/extensionresource/read_test.go b/ec/ecresource/extensionresource/read_test.go new file mode 100644 index 000000000..beb87d328 --- /dev/null +++ b/ec/ecresource/extensionresource/read_test.go @@ -0,0 +1,115 @@ +// 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 extensionresource + +import ( + "context" + "testing" + + "github.com/elastic/cloud-sdk-go/pkg/api" + "github.com/elastic/cloud-sdk-go/pkg/api/mock" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/assert" + + "github.com/elastic/terraform-provider-ec/ec/internal/util" +) + +func Test_readResource(t *testing.T) { + tc500Err := util.NewResourceData(t, util.ResDataParams{ + ID: mock.ValidClusterID, + State: newExtension(), + Schema: newSchema(), + }) + wantTC500 := util.NewResourceData(t, util.ResDataParams{ + ID: mock.ValidClusterID, + State: newExtension(), + Schema: newSchema(), + }) + + tc404Err := util.NewResourceData(t, util.ResDataParams{ + ID: mock.ValidClusterID, + State: newExtension(), + Schema: newSchema(), + }) + wantTC404 := util.NewResourceData(t, util.ResDataParams{ + ID: mock.ValidClusterID, + State: newExtension(), + Schema: newSchema(), + }) + wantTC404.SetId("") + + type args struct { + ctx context.Context + d *schema.ResourceData + meta interface{} + } + tests := []struct { + name string + args args + want diag.Diagnostics + wantRD *schema.ResourceData + }{ + { + name: "returns an error when it receives a 500", + args: args{ + d: tc500Err, + meta: api.NewMock(mock.NewErrorResponse(500, mock.APIError{ + Code: "some", Message: "message", + })), + }, + want: diag.Diagnostics{ + { + Severity: diag.Error, + Summary: "failed reading extension: 1 error occurred:\n\t* api error: some: message\n\n", + }, + }, + wantRD: wantTC500, + }, + { + name: "returns nil and unsets the state when the error is known", + args: args{ + d: tc404Err, + meta: api.NewMock(mock.NewErrorResponse(404, mock.APIError{ + Code: "some", Message: "message", + })), + }, + want: nil, + wantRD: wantTC404, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := readResource(tt.args.ctx, tt.args.d, tt.args.meta) + assert.Equal(t, tt.want, got) + var want interface{} + if tt.wantRD != nil { + if s := tt.wantRD.State(); s != nil { + want = s.Attributes + } + } + + var gotState interface{} + if s := tt.args.d.State(); s != nil { + gotState = s.Attributes + } + + assert.Equal(t, want, gotState) + }) + } +} diff --git a/ec/ecresource/extensionresource/request.go b/ec/ecresource/extensionresource/request.go new file mode 100644 index 000000000..e3fe3d408 --- /dev/null +++ b/ec/ecresource/extensionresource/request.go @@ -0,0 +1,115 @@ +// 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 extensionresource + +import ( + "os" + + "github.com/elastic/cloud-sdk-go/pkg/api" + "github.com/elastic/cloud-sdk-go/pkg/api/apierror" + "github.com/elastic/cloud-sdk-go/pkg/client/extensions" + "github.com/elastic/cloud-sdk-go/pkg/models" + "github.com/elastic/cloud-sdk-go/pkg/multierror" + "github.com/go-openapi/runtime" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func readRequest(d *schema.ResourceData, client *api.API) (*extensions.GetExtensionOK, error) { + res, err := client.V1API.Extensions.GetExtension( + extensions.NewGetExtensionParams().WithExtensionID(d.Id()), + client.AuthWriter) + + if err != nil { + return nil, apierror.Wrap(err) + } + return res, nil +} + +func createRequest(client *api.API, d *schema.ResourceData) (*extensions.CreateExtensionCreated, error) { + name := d.Get("name").(string) + version := d.Get("version").(string) + extensionsType := d.Get("extension_type").(string) + description := d.Get("description").(string) + + body := &models.CreateExtensionRequest{ + Name: &name, + Version: &version, + ExtensionType: &extensionsType, + Description: description, + } + + res, err := client.V1API.Extensions.CreateExtension( + extensions.NewCreateExtensionParams().WithBody(body), + client.AuthWriter) + + if err != nil { + return nil, apierror.Wrap(err) + } + return res, nil +} + +func updateRequest(client *api.API, d *schema.ResourceData) (*extensions.UpdateExtensionOK, error) { + name := d.Get("name").(string) + version := d.Get("version").(string) + extensionsType := d.Get("extension_type").(string) + description := d.Get("description").(string) + + body := &models.UpdateExtensionRequest{ + Name: &name, + Version: &version, + ExtensionType: &extensionsType, + Description: description, + } + + res, err := client.V1API.Extensions.UpdateExtension( + extensions.NewUpdateExtensionParams().WithBody(body).WithExtensionID(d.Id()), + client.AuthWriter) + if err != nil { + return nil, apierror.Wrap(err) + } + + return res, nil +} + +func deleteRequest(client *api.API, d *schema.ResourceData) (*extensions.DeleteExtensionOK, error) { + res, err := client.V1API.Extensions.DeleteExtension( + extensions.NewDeleteExtensionParams().WithExtensionID(d.Id()), + client.AuthWriter) + if err != nil { + return nil, apierror.Wrap(err) + } + + return res, nil +} +func uploadRequest(client *api.API, d *schema.ResourceData) (*extensions.UploadExtensionOK, error) { + + reader, err := os.Open(d.Get("file_path").(string)) + if err != nil { + return nil, multierror.NewPrefixed("failed open file", err) + } + + res, err := client.V1API.Extensions.UploadExtension( + extensions.NewUploadExtensionParams().WithExtensionID(d.Id()). + WithFile(runtime.NamedReader(d.Get("file_path").(string), reader)), + client.AuthWriter) + if err != nil { + return nil, apierror.Wrap(err) + } + + return res, nil +} diff --git a/ec/ecresource/extensionresource/resource.go b/ec/ecresource/extensionresource/resource.go new file mode 100644 index 000000000..3dcdee81f --- /dev/null +++ b/ec/ecresource/extensionresource/resource.go @@ -0,0 +1,41 @@ +// 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 extensionresource + +import ( + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// Resource returns the ec_extension resource schema. +func Resource() *schema.Resource { + return &schema.Resource{ + Description: "Elastic Cloud deployment traffic filtering rules", + Schema: newSchema(), + + CreateContext: createResource, + ReadContext: readResource, + UpdateContext: updateResource, + DeleteContext: deleteResource, + + Timeouts: &schema.ResourceTimeout{ + Default: schema.DefaultTimeout(10 * time.Minute), + }, + } +} diff --git a/ec/ecresource/extensionresource/schema.go b/ec/ecresource/extensionresource/schema.go new file mode 100644 index 000000000..952d84213 --- /dev/null +++ b/ec/ecresource/extensionresource/schema.go @@ -0,0 +1,64 @@ +// 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 extensionresource + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func newSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "Required name of the ruleset", + Required: true, + }, + "description": { + Type: schema.TypeString, + Description: "Description for extension", + Required: true, + }, + "extension_type": { + Type: schema.TypeString, + Description: "Extension type. bundle or plugin", + Required: true, + }, + "version": { + Type: schema.TypeString, + Description: "Eleasticsearch version", + Required: true, + }, + "download_url": { + Type: schema.TypeString, + Description: "download url", + Optional: true, + }, + + // Uploading file bia API + "file_path": { + Type: schema.TypeString, + Description: "file path", + Optional: true, + }, + "file_hash": { + Type: schema.TypeString, + Description: "file hash", + Optional: true, + }, + } +} diff --git a/ec/ecresource/extensionresource/testutil_datastruct.go b/ec/ecresource/extensionresource/testutil_datastruct.go new file mode 100644 index 000000000..f7b69a20c --- /dev/null +++ b/ec/ecresource/extensionresource/testutil_datastruct.go @@ -0,0 +1,27 @@ +// 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 extensionresource + +func newExtension() map[string]interface{} { + return map[string]interface{}{ + "name": "my_extension", + "extension": "bundle", + "description": "my description", + "version": "*", + } +} diff --git a/ec/ecresource/extensionresource/update.go b/ec/ecresource/extensionresource/update.go new file mode 100644 index 000000000..2d000e164 --- /dev/null +++ b/ec/ecresource/extensionresource/update.go @@ -0,0 +1,45 @@ +// 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 extensionresource + +import ( + "context" + + "github.com/elastic/cloud-sdk-go/pkg/api" + "github.com/elastic/cloud-sdk-go/pkg/multierror" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func updateResource(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var client = meta.(*api.API) + + _, err := updateRequest(client, d) + if err != nil { + return diag.FromErr(err) + } + + if d.Get("file_path") != nil { + _, err = uploadRequest(client, d) + if err != nil { + return diag.FromErr(multierror.NewPrefixed("failed to upload file", err)) + } + } + + return readResource(ctx, d, meta) +} diff --git a/ec/ecresource/extensionresource/update_test.go b/ec/ecresource/extensionresource/update_test.go new file mode 100644 index 000000000..99f0bcece --- /dev/null +++ b/ec/ecresource/extensionresource/update_test.go @@ -0,0 +1,92 @@ +// 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 extensionresource + +import ( + "context" + "testing" + + "github.com/elastic/cloud-sdk-go/pkg/api" + "github.com/elastic/cloud-sdk-go/pkg/api/mock" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/assert" + + "github.com/elastic/terraform-provider-ec/ec/internal/util" +) + +func Test_updateResource(t *testing.T) { + tc500Err := util.NewResourceData(t, util.ResDataParams{ + ID: "12345678", + State: newExtension(), + Schema: newSchema(), + }) + wantTC500 := util.NewResourceData(t, util.ResDataParams{ + ID: "12345678", + State: newExtension(), + Schema: newSchema(), + }) + + type args struct { + ctx context.Context + d *schema.ResourceData + meta interface{} + } + tests := []struct { + name string + args args + want diag.Diagnostics + wantRD *schema.ResourceData + }{ + { + name: "returns an error when it receives a 500", + args: args{ + d: tc500Err, + meta: api.NewMock(mock.NewErrorResponse(500, mock.APIError{ + Code: "some", Message: "message", + })), + }, + want: diag.Diagnostics{ + { + Severity: diag.Error, + Summary: "api error: 1 error occurred:\n\t* some: message\n\n", + }, + }, + wantRD: wantTC500, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := updateResource(tt.args.ctx, tt.args.d, tt.args.meta) + assert.Equal(t, tt.want, got) + var want interface{} + if tt.wantRD != nil { + if s := tt.wantRD.State(); s != nil { + want = s.Attributes + } + } + + var gotState interface{} + if s := tt.args.d.State(); s != nil { + gotState = s.Attributes + } + + assert.Equal(t, want, gotState) + }) + } +} diff --git a/ec/provider.go b/ec/provider.go index 0d17a4990..43710aa72 100644 --- a/ec/provider.go +++ b/ec/provider.go @@ -29,6 +29,7 @@ import ( "github.com/elastic/terraform-provider-ec/ec/ecdatasource/deploymentsdatasource" "github.com/elastic/terraform-provider-ec/ec/ecdatasource/stackdatasource" "github.com/elastic/terraform-provider-ec/ec/ecresource/deploymentresource" + "github.com/elastic/terraform-provider-ec/ec/ecresource/extensionresource" "github.com/elastic/terraform-provider-ec/ec/ecresource/trafficfilterassocresource" "github.com/elastic/terraform-provider-ec/ec/ecresource/trafficfilterresource" ) @@ -71,6 +72,7 @@ func Provider() *schema.Provider { "ec_deployment": deploymentresource.Resource(), "ec_deployment_traffic_filter": trafficfilterresource.Resource(), "ec_deployment_traffic_filter_association": trafficfilterassocresource.Resource(), + "ec_extension": extensionresource.Resource(), }, } } diff --git a/examples/extension/README.md b/examples/extension/README.md new file mode 100644 index 000000000..40b8ec27e --- /dev/null +++ b/examples/extension/README.md @@ -0,0 +1,12 @@ +# Extension example + +This example shows how to create an Elastic Cloud extension using Terraform. +You can create the extension by `files/content.json` + +## Running the example + +To run the example, follow these steps: + +1. Build the provider by running `make install` from the main folder. +2. Run `terrafrom init` to initialize your Terraform CLI. +3. Run `terraform apply` to see how it works. diff --git a/examples/extension/extension.tf b/examples/extension/extension.tf new file mode 100644 index 000000000..16373ce11 --- /dev/null +++ b/examples/extension/extension.tf @@ -0,0 +1,27 @@ +terraform { + required_version = ">= 0.12.29" + + required_providers { + ec = { + source = "elastic/ec" + version = "0.1.0-beta" + } + } +} + +provider "ec" {} + +locals { + file_path = "./files/content.json.zip" +} + +# Create an Elastic Cloud Extension +resource "ec_extension" "example_extension" { + name = "my_extension" + description = "my extension" + version = "*" + extension_type = "bundle" + + file_path = local.file_path + file_hash = filebase64sha256(local.file_path) +} diff --git a/examples/extension/files/content.json b/examples/extension/files/content.json new file mode 100644 index 000000000..b18dda311 --- /dev/null +++ b/examples/extension/files/content.json @@ -0,0 +1 @@ +{"foo": "value", "bar": 3} \ No newline at end of file diff --git a/examples/extension/files/content.json.zip b/examples/extension/files/content.json.zip new file mode 100644 index 0000000000000000000000000000000000000000..41b9d74f470bb022e0558473f0940e04f394e5cc GIT binary patch literal 381 zcmWIWW@h1H00FVxGXm3!viSvpY!K#QkYPy9&nrpIE78j;&d&=C;bdT5z2r%J%kn4j zr4`%^j4a<685meZfNHCiGV;??m8=w$$`W%*Q3tv$0|4L?S#baW literal 0 HcmV?d00001 diff --git a/examples/extension/outputs.tf b/examples/extension/outputs.tf new file mode 100644 index 000000000..497f99678 --- /dev/null +++ b/examples/extension/outputs.tf @@ -0,0 +1,3 @@ +output "extension_id" { + value = ec_extension.example_extension.id +} From a6bcc372d0175616f5aace08e1feef4d74707577 Mon Sep 17 00:00:00 2001 From: snowhork Date: Tue, 5 Jan 2021 06:18:32 +0900 Subject: [PATCH 02/25] ec_extension: fix variable code style --- ec/ecresource/extensionresource/create.go | 3 ++- ec/ecresource/extensionresource/delete.go | 3 ++- ec/ecresource/extensionresource/read.go | 3 ++- ec/ecresource/extensionresource/update.go | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ec/ecresource/extensionresource/create.go b/ec/ecresource/extensionresource/create.go index 74f88463b..b452b5427 100644 --- a/ec/ecresource/extensionresource/create.go +++ b/ec/ecresource/extensionresource/create.go @@ -19,6 +19,7 @@ package extensionresource import ( "context" + "github.com/elastic/cloud-sdk-go/pkg/api" "github.com/elastic/cloud-sdk-go/pkg/multierror" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -27,7 +28,7 @@ import ( // Create will createResource a new deployment traffic filter ruleset func createResource(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var client = meta.(*api.API) + client := meta.(*api.API) res, err := createRequest(client, d) if err != nil { diff --git a/ec/ecresource/extensionresource/delete.go b/ec/ecresource/extensionresource/delete.go index 706fedb3f..222196d76 100644 --- a/ec/ecresource/extensionresource/delete.go +++ b/ec/ecresource/extensionresource/delete.go @@ -19,6 +19,7 @@ package extensionresource import ( "context" + "github.com/elastic/cloud-sdk-go/pkg/api" "github.com/elastic/cloud-sdk-go/pkg/api/apierror" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -26,7 +27,7 @@ import ( ) func deleteResource(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var client = meta.(*api.API) + client := meta.(*api.API) _, err := deleteRequest(client, d) diff --git a/ec/ecresource/extensionresource/read.go b/ec/ecresource/extensionresource/read.go index 2cf7ac9f5..707c39768 100644 --- a/ec/ecresource/extensionresource/read.go +++ b/ec/ecresource/extensionresource/read.go @@ -20,6 +20,7 @@ package extensionresource import ( "context" "errors" + "github.com/elastic/cloud-sdk-go/pkg/api" "github.com/elastic/cloud-sdk-go/pkg/api/apierror" "github.com/elastic/cloud-sdk-go/pkg/client/extensions" @@ -30,7 +31,7 @@ import ( ) func readResource(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var client = meta.(*api.API) + client := meta.(*api.API) res, err := readRequest(d, client) diff --git a/ec/ecresource/extensionresource/update.go b/ec/ecresource/extensionresource/update.go index 2d000e164..4895fe3f7 100644 --- a/ec/ecresource/extensionresource/update.go +++ b/ec/ecresource/extensionresource/update.go @@ -27,7 +27,7 @@ import ( ) func updateResource(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var client = meta.(*api.API) + client := meta.(*api.API) _, err := updateRequest(client, d) if err != nil { From 0238de70b5712700c034b26aa1f4046bf6e5c9c0 Mon Sep 17 00:00:00 2001 From: snowhork Date: Tue, 5 Jan 2021 06:22:45 +0900 Subject: [PATCH 03/25] ec_extension: fix typo --- ec/ecresource/extensionresource/schema.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ec/ecresource/extensionresource/schema.go b/ec/ecresource/extensionresource/schema.go index 952d84213..4b10ca9b9 100644 --- a/ec/ecresource/extensionresource/schema.go +++ b/ec/ecresource/extensionresource/schema.go @@ -49,7 +49,7 @@ func newSchema() map[string]*schema.Schema { Optional: true, }, - // Uploading file bia API + // Uploading file via API "file_path": { Type: schema.TypeString, Description: "file path", From 5e668255f23cabf76c4a4292644e1da899a229e2 Mon Sep 17 00:00:00 2001 From: snowhork Date: Tue, 5 Jan 2021 06:23:23 +0900 Subject: [PATCH 04/25] ec_extension: file_hash requires_with file_path --- ec/ecresource/extensionresource/schema.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ec/ecresource/extensionresource/schema.go b/ec/ecresource/extensionresource/schema.go index 4b10ca9b9..3ba5fce41 100644 --- a/ec/ecresource/extensionresource/schema.go +++ b/ec/ecresource/extensionresource/schema.go @@ -51,9 +51,10 @@ func newSchema() map[string]*schema.Schema { // Uploading file via API "file_path": { - Type: schema.TypeString, - Description: "file path", - Optional: true, + Type: schema.TypeString, + Description: "file path", + Optional: true, + RequiredWith: []string{"file_hash"}, }, "file_hash": { Type: schema.TypeString, From 87dee548d2aec0bca7e3bbb30226b3a9ac8a8c6d Mon Sep 17 00:00:00 2001 From: snowhork Date: Tue, 5 Jan 2021 06:31:37 +0900 Subject: [PATCH 05/25] ec_extension: use GetOk for key check --- ec/ecresource/extensionresource/create.go | 2 +- ec/ecresource/extensionresource/update.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ec/ecresource/extensionresource/create.go b/ec/ecresource/extensionresource/create.go index b452b5427..eb080a324 100644 --- a/ec/ecresource/extensionresource/create.go +++ b/ec/ecresource/extensionresource/create.go @@ -37,7 +37,7 @@ func createResource(ctx context.Context, d *schema.ResourceData, meta interface{ d.SetId(*res.Payload.ID) - if d.Get("file_path") != nil { + if _, ok := d.GetOk("file_path"); ok { _, err = uploadRequest(client, d) if err != nil { return diag.FromErr(multierror.NewPrefixed("failed to upload file", err)) diff --git a/ec/ecresource/extensionresource/update.go b/ec/ecresource/extensionresource/update.go index 4895fe3f7..32e2d0e58 100644 --- a/ec/ecresource/extensionresource/update.go +++ b/ec/ecresource/extensionresource/update.go @@ -34,7 +34,7 @@ func updateResource(ctx context.Context, d *schema.ResourceData, meta interface{ return diag.FromErr(err) } - if d.Get("file_path") != nil { + if _, ok := d.GetOk("file_path"); ok { _, err = uploadRequest(client, d) if err != nil { return diag.FromErr(multierror.NewPrefixed("failed to upload file", err)) From 112f43752f7a5ead3a7918e171c5df8a53f203b1 Mon Sep 17 00:00:00 2001 From: snowhork Date: Tue, 5 Jan 2021 06:34:26 +0900 Subject: [PATCH 06/25] ec_extension: check hash changed on update for avoiding reupload --- ec/ecresource/extensionresource/update.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ec/ecresource/extensionresource/update.go b/ec/ecresource/extensionresource/update.go index 32e2d0e58..6b4fbc7df 100644 --- a/ec/ecresource/extensionresource/update.go +++ b/ec/ecresource/extensionresource/update.go @@ -34,9 +34,8 @@ func updateResource(ctx context.Context, d *schema.ResourceData, meta interface{ return diag.FromErr(err) } - if _, ok := d.GetOk("file_path"); ok { - _, err = uploadRequest(client, d) - if err != nil { + if _, ok := d.GetOk("file_path"); ok && d.HasChange("file_hash") { + if _, err = uploadRequest(client, d); err != nil { return diag.FromErr(multierror.NewPrefixed("failed to upload file", err)) } } From 31a44fb91fcc5d34934bd99828eae20d732beb80 Mon Sep 17 00:00:00 2001 From: snowhork Date: Tue, 5 Jan 2021 07:13:53 +0900 Subject: [PATCH 07/25] ec_extension: fix request wrapper to return model --- ec/ecresource/extensionresource/create.go | 7 ++--- ec/ecresource/extensionresource/delete.go | 4 +-- ec/ecresource/extensionresource/read.go | 4 +-- ec/ecresource/extensionresource/request.go | 36 ++++++++++------------ ec/ecresource/extensionresource/update.go | 2 +- 5 files changed, 24 insertions(+), 29 deletions(-) diff --git a/ec/ecresource/extensionresource/create.go b/ec/ecresource/extensionresource/create.go index eb080a324..6c8c5506d 100644 --- a/ec/ecresource/extensionresource/create.go +++ b/ec/ecresource/extensionresource/create.go @@ -30,16 +30,15 @@ import ( func createResource(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*api.API) - res, err := createRequest(client, d) + model, err := createRequest(client, d) if err != nil { return diag.FromErr(err) } - d.SetId(*res.Payload.ID) + d.SetId(*model.ID) if _, ok := d.GetOk("file_path"); ok { - _, err = uploadRequest(client, d) - if err != nil { + if err := uploadRequest(client, d); err != nil { return diag.FromErr(multierror.NewPrefixed("failed to upload file", err)) } } diff --git a/ec/ecresource/extensionresource/delete.go b/ec/ecresource/extensionresource/delete.go index 222196d76..a9ef0cbaa 100644 --- a/ec/ecresource/extensionresource/delete.go +++ b/ec/ecresource/extensionresource/delete.go @@ -29,9 +29,7 @@ import ( func deleteResource(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*api.API) - _, err := deleteRequest(client, d) - - if err != nil { + if err := deleteRequest(client, d); err != nil { if alreadyDestroyed(err) { d.SetId("") return nil diff --git a/ec/ecresource/extensionresource/read.go b/ec/ecresource/extensionresource/read.go index 707c39768..714c60f69 100644 --- a/ec/ecresource/extensionresource/read.go +++ b/ec/ecresource/extensionresource/read.go @@ -33,7 +33,7 @@ import ( func readResource(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*api.API) - res, err := readRequest(d, client) + model, err := readRequest(d, client) if err != nil { if extensionNotFound(err) { @@ -44,7 +44,7 @@ func readResource(_ context.Context, d *schema.ResourceData, meta interface{}) d return diag.FromErr(multierror.NewPrefixed("failed reading extension", err)) } - if err := modelToState(d, res.Payload); err != nil { + if err := modelToState(d, model); err != nil { return diag.FromErr(err) } diff --git a/ec/ecresource/extensionresource/request.go b/ec/ecresource/extensionresource/request.go index e3fe3d408..d95cd1068 100644 --- a/ec/ecresource/extensionresource/request.go +++ b/ec/ecresource/extensionresource/request.go @@ -29,7 +29,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func readRequest(d *schema.ResourceData, client *api.API) (*extensions.GetExtensionOK, error) { +func readRequest(d *schema.ResourceData, client *api.API) (*models.Extension, error) { res, err := client.V1API.Extensions.GetExtension( extensions.NewGetExtensionParams().WithExtensionID(d.Id()), client.AuthWriter) @@ -37,10 +37,10 @@ func readRequest(d *schema.ResourceData, client *api.API) (*extensions.GetExtens if err != nil { return nil, apierror.Wrap(err) } - return res, nil + return res.Payload, nil } -func createRequest(client *api.API, d *schema.ResourceData) (*extensions.CreateExtensionCreated, error) { +func createRequest(client *api.API, d *schema.ResourceData) (*models.Extension, error) { name := d.Get("name").(string) version := d.Get("version").(string) extensionsType := d.Get("extension_type").(string) @@ -60,10 +60,10 @@ func createRequest(client *api.API, d *schema.ResourceData) (*extensions.CreateE if err != nil { return nil, apierror.Wrap(err) } - return res, nil + return res.Payload, nil } -func updateRequest(client *api.API, d *schema.ResourceData) (*extensions.UpdateExtensionOK, error) { +func updateRequest(client *api.API, d *schema.ResourceData) (*models.Extension, error) { name := d.Get("name").(string) version := d.Get("version").(string) extensionsType := d.Get("extension_type").(string) @@ -83,33 +83,31 @@ func updateRequest(client *api.API, d *schema.ResourceData) (*extensions.UpdateE return nil, apierror.Wrap(err) } - return res, nil + return res.Payload, nil } -func deleteRequest(client *api.API, d *schema.ResourceData) (*extensions.DeleteExtensionOK, error) { - res, err := client.V1API.Extensions.DeleteExtension( +func deleteRequest(client *api.API, d *schema.ResourceData) error { + if _, err := client.V1API.Extensions.DeleteExtension( extensions.NewDeleteExtensionParams().WithExtensionID(d.Id()), - client.AuthWriter) - if err != nil { - return nil, apierror.Wrap(err) + client.AuthWriter); err != nil { + return apierror.Wrap(err) } - return res, nil + return nil } -func uploadRequest(client *api.API, d *schema.ResourceData) (*extensions.UploadExtensionOK, error) { +func uploadRequest(client *api.API, d *schema.ResourceData) error { reader, err := os.Open(d.Get("file_path").(string)) if err != nil { - return nil, multierror.NewPrefixed("failed open file", err) + return multierror.NewPrefixed("failed open file", err) } - res, err := client.V1API.Extensions.UploadExtension( + if _, err := client.V1API.Extensions.UploadExtension( extensions.NewUploadExtensionParams().WithExtensionID(d.Id()). WithFile(runtime.NamedReader(d.Get("file_path").(string), reader)), - client.AuthWriter) - if err != nil { - return nil, apierror.Wrap(err) + client.AuthWriter); err != nil { + return apierror.Wrap(err) } - return res, nil + return nil } diff --git a/ec/ecresource/extensionresource/update.go b/ec/ecresource/extensionresource/update.go index 6b4fbc7df..4ec6ec9b3 100644 --- a/ec/ecresource/extensionresource/update.go +++ b/ec/ecresource/extensionresource/update.go @@ -35,7 +35,7 @@ func updateResource(ctx context.Context, d *schema.ResourceData, meta interface{ } if _, ok := d.GetOk("file_path"); ok && d.HasChange("file_hash") { - if _, err = uploadRequest(client, d); err != nil { + if err := uploadRequest(client, d); err != nil { return diag.FromErr(multierror.NewPrefixed("failed to upload file", err)) } } From 73dfb89b378d0edd356f699bbe31a87514f10f5b Mon Sep 17 00:00:00 2001 From: snowhork Date: Tue, 5 Jan 2021 07:18:04 +0900 Subject: [PATCH 08/25] ec_extension: fix upload request wrapper to use filePath variable --- ec/ecresource/extensionresource/request.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ec/ecresource/extensionresource/request.go b/ec/ecresource/extensionresource/request.go index d95cd1068..98a6cd39a 100644 --- a/ec/ecresource/extensionresource/request.go +++ b/ec/ecresource/extensionresource/request.go @@ -97,14 +97,15 @@ func deleteRequest(client *api.API, d *schema.ResourceData) error { } func uploadRequest(client *api.API, d *schema.ResourceData) error { - reader, err := os.Open(d.Get("file_path").(string)) + filePath := d.Get("file_path").(string) + reader, err := os.Open(filePath) if err != nil { return multierror.NewPrefixed("failed open file", err) } if _, err := client.V1API.Extensions.UploadExtension( extensions.NewUploadExtensionParams().WithExtensionID(d.Id()). - WithFile(runtime.NamedReader(d.Get("file_path").(string), reader)), + WithFile(runtime.NamedReader(filePath, reader)), client.AuthWriter); err != nil { return apierror.Wrap(err) } From 89f11470c7c9f48830383ff4f21e54f3ff4cb987 Mon Sep 17 00:00:00 2001 From: snowhork Date: Tue, 5 Jan 2021 15:41:43 +0900 Subject: [PATCH 09/25] ec_extension: fix request error handling and add success test case --- ec/ecresource/extensionresource/delete.go | 9 ++- .../extensionresource/delete_test.go | 32 ++++++-- ec/ecresource/extensionresource/read.go | 8 +- ec/ecresource/extensionresource/read_test.go | 34 ++++++++- .../test_extension_bundle.json | 1 + .../extensionresource/testutil_datastruct.go | 19 ++++- .../extensionresource/update_test.go | 74 +++++++++++++++++++ 7 files changed, 153 insertions(+), 24 deletions(-) create mode 100644 ec/ecresource/extensionresource/test_extension_bundle.json diff --git a/ec/ecresource/extensionresource/delete.go b/ec/ecresource/extensionresource/delete.go index a9ef0cbaa..1c2d4aabb 100644 --- a/ec/ecresource/extensionresource/delete.go +++ b/ec/ecresource/extensionresource/delete.go @@ -19,14 +19,15 @@ package extensionresource import ( "context" + "errors" "github.com/elastic/cloud-sdk-go/pkg/api" - "github.com/elastic/cloud-sdk-go/pkg/api/apierror" + "github.com/elastic/cloud-sdk-go/pkg/client/extensions" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func deleteResource(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func deleteResource(_ context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*api.API) if err := deleteRequest(client, d); err != nil { @@ -42,6 +43,6 @@ func deleteResource(ctx context.Context, d *schema.ResourceData, meta interface{ } func alreadyDestroyed(err error) bool { - // If the extension is already destroyed, API return 403. - return apierror.IsRuntimeStatusCode(err, 403) + var extensionNotFound *extensions.DeleteExtensionNotFound + return errors.As(err, &extensionNotFound) } diff --git a/ec/ecresource/extensionresource/delete_test.go b/ec/ecresource/extensionresource/delete_test.go index ebe6c7954..8e4f09a96 100644 --- a/ec/ecresource/extensionresource/delete_test.go +++ b/ec/ecresource/extensionresource/delete_test.go @@ -31,6 +31,17 @@ import ( ) func Test_deleteResource(t *testing.T) { + tc200 := util.NewResourceData(t, util.ResDataParams{ + ID: "12345678", + State: newExtension(), + Schema: newSchema(), + }) + wantTC200 := util.NewResourceData(t, util.ResDataParams{ + ID: "12345678", + State: newExtension(), + Schema: newSchema(), + }) + tc500Err := util.NewResourceData(t, util.ResDataParams{ ID: "12345678", State: newExtension(), @@ -42,17 +53,17 @@ func Test_deleteResource(t *testing.T) { Schema: newSchema(), }) - tc403Err := util.NewResourceData(t, util.ResDataParams{ + tc404Err := util.NewResourceData(t, util.ResDataParams{ ID: "12345678", State: newExtension(), Schema: newSchema(), }) - wantTC403 := util.NewResourceData(t, util.ResDataParams{ + wantTC404 := util.NewResourceData(t, util.ResDataParams{ ID: "12345678", State: newExtension(), Schema: newSchema(), }) - wantTC403.SetId("") + wantTC404.SetId("") type args struct { ctx context.Context @@ -65,6 +76,15 @@ func Test_deleteResource(t *testing.T) { want diag.Diagnostics wantRD *schema.ResourceData }{ + { + name: "returns nil when it receives a 200", + args: args{ + d: tc200, + meta: api.NewMock(mock.New200Response(nil)), + }, + want: nil, + wantRD: wantTC200, + }, { name: "returns an error when it receives a 500", args: args{ @@ -84,13 +104,13 @@ func Test_deleteResource(t *testing.T) { { name: "returns nil and unsets the state when the error is known", args: args{ - d: tc403Err, - meta: api.NewMock(mock.NewErrorResponse(403, mock.APIError{ + d: tc404Err, + meta: api.NewMock(mock.NewErrorResponse(404, mock.APIError{ Code: "some", Message: "message", })), }, want: nil, - wantRD: wantTC403, + wantRD: wantTC404, }, } for _, tt := range tests { diff --git a/ec/ecresource/extensionresource/read.go b/ec/ecresource/extensionresource/read.go index 714c60f69..920ac9baf 100644 --- a/ec/ecresource/extensionresource/read.go +++ b/ec/ecresource/extensionresource/read.go @@ -22,7 +22,6 @@ import ( "errors" "github.com/elastic/cloud-sdk-go/pkg/api" - "github.com/elastic/cloud-sdk-go/pkg/api/apierror" "github.com/elastic/cloud-sdk-go/pkg/client/extensions" "github.com/elastic/cloud-sdk-go/pkg/models" "github.com/elastic/cloud-sdk-go/pkg/multierror" @@ -55,12 +54,7 @@ func extensionNotFound(err error) bool { // We're using the As() call since we do not care about the error value // but do care about the error's contents type since it's an implicit 404. var extensionNotFound *extensions.GetExtensionNotFound - if errors.As(err, &extensionNotFound) { - return true - } - - // We also check for the case where a 403 is thrown for ESS. - return apierror.IsRuntimeStatusCode(err, 403) + return errors.As(err, &extensionNotFound) } func modelToState(d *schema.ResourceData, model *models.Extension) error { diff --git a/ec/ecresource/extensionresource/read_test.go b/ec/ecresource/extensionresource/read_test.go index beb87d328..479ee82de 100644 --- a/ec/ecresource/extensionresource/read_test.go +++ b/ec/ecresource/extensionresource/read_test.go @@ -23,6 +23,7 @@ import ( "github.com/elastic/cloud-sdk-go/pkg/api" "github.com/elastic/cloud-sdk-go/pkg/api/mock" + "github.com/elastic/cloud-sdk-go/pkg/models" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/assert" @@ -31,19 +32,30 @@ import ( ) func Test_readResource(t *testing.T) { + tc200 := util.NewResourceData(t, util.ResDataParams{ + ID: "12345678", + State: newExtension(), + Schema: newSchema(), + }) + wantTC200 := util.NewResourceData(t, util.ResDataParams{ + ID: "12345678", + State: newExtension(), + Schema: newSchema(), + }) + tc500Err := util.NewResourceData(t, util.ResDataParams{ - ID: mock.ValidClusterID, + ID: "12345678", State: newExtension(), Schema: newSchema(), }) wantTC500 := util.NewResourceData(t, util.ResDataParams{ - ID: mock.ValidClusterID, + ID: "12345678", State: newExtension(), Schema: newSchema(), }) tc404Err := util.NewResourceData(t, util.ResDataParams{ - ID: mock.ValidClusterID, + ID: "12345678", State: newExtension(), Schema: newSchema(), }) @@ -65,6 +77,20 @@ func Test_readResource(t *testing.T) { want diag.Diagnostics wantRD *schema.ResourceData }{ + { + name: "returns nil when it receives a 200", + args: args{ + d: tc200, + meta: api.NewMock(mock.New200StructResponse(models.Extension{ + Name: stringPtr("my_extension"), + ExtensionType: stringPtr("bundle"), + Description: "my description", + Version: stringPtr("*"), + })), + }, + want: nil, + wantRD: wantTC200, + }, { name: "returns an error when it receives a 500", args: args{ @@ -113,3 +139,5 @@ func Test_readResource(t *testing.T) { }) } } + +func stringPtr(s string) *string { return &s } diff --git a/ec/ecresource/extensionresource/test_extension_bundle.json b/ec/ecresource/extensionresource/test_extension_bundle.json new file mode 100644 index 000000000..18d7acf58 --- /dev/null +++ b/ec/ecresource/extensionresource/test_extension_bundle.json @@ -0,0 +1 @@ +{"foo": "bar"} \ No newline at end of file diff --git a/ec/ecresource/extensionresource/testutil_datastruct.go b/ec/ecresource/extensionresource/testutil_datastruct.go index f7b69a20c..642553c03 100644 --- a/ec/ecresource/extensionresource/testutil_datastruct.go +++ b/ec/ecresource/extensionresource/testutil_datastruct.go @@ -19,9 +19,20 @@ package extensionresource func newExtension() map[string]interface{} { return map[string]interface{}{ - "name": "my_extension", - "extension": "bundle", - "description": "my description", - "version": "*", + "name": "my_extension", + "extension_type": "bundle", + "description": "my description", + "version": "*", + } +} + +func newExtensionWithFilePath() map[string]interface{} { + return map[string]interface{}{ + "name": "my_extension", + "extension_type": "bundle", + "description": "my description", + "version": "*", + "file_path": "test_extension_bundle.json", + "file_hash": "abcd", } } diff --git a/ec/ecresource/extensionresource/update_test.go b/ec/ecresource/extensionresource/update_test.go index 99f0bcece..cadeb53b4 100644 --- a/ec/ecresource/extensionresource/update_test.go +++ b/ec/ecresource/extensionresource/update_test.go @@ -21,6 +21,8 @@ import ( "context" "testing" + "github.com/elastic/cloud-sdk-go/pkg/models" + "github.com/elastic/cloud-sdk-go/pkg/api" "github.com/elastic/cloud-sdk-go/pkg/api/mock" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -31,6 +33,28 @@ import ( ) func Test_updateResource(t *testing.T) { + tc200withoutFilePath := util.NewResourceData(t, util.ResDataParams{ + ID: "12345678", + State: newExtension(), + Schema: newSchema(), + }) + wantTC200withoutFilePath := util.NewResourceData(t, util.ResDataParams{ + ID: "12345678", + State: newExtension(), + Schema: newSchema(), + }) + + tc200withFilePath := util.NewResourceData(t, util.ResDataParams{ + ID: "12345678", + State: newExtensionWithFilePath(), + Schema: newSchema(), + }) + wantTC200withFilePath := util.NewResourceData(t, util.ResDataParams{ + ID: "12345678", + State: newExtensionWithFilePath(), + Schema: newSchema(), + }) + tc500Err := util.NewResourceData(t, util.ResDataParams{ ID: "12345678", State: newExtension(), @@ -53,6 +77,56 @@ func Test_updateResource(t *testing.T) { want diag.Diagnostics wantRD *schema.ResourceData }{ + { + name: "returns nil when it receives a 200 without file_path", + args: args{ + d: tc200withoutFilePath, + meta: api.NewMock( + mock.New200StructResponse(models.Extension{ // update request response + Name: stringPtr("my_extension"), + ExtensionType: stringPtr("bundle"), + Description: "my description", + Version: stringPtr("*"), + }), + mock.New200StructResponse(models.Extension{ // read request response + Name: stringPtr("my_extension"), + ExtensionType: stringPtr("bundle"), + Description: "my description", + Version: stringPtr("*"), + }), + ), + }, + want: nil, + wantRD: wantTC200withoutFilePath, + }, + { + name: "returns nil when it receives a 200 with file_path", + args: args{ + d: tc200withFilePath, + meta: api.NewMock( + mock.New200StructResponse(models.Extension{ // update request response + Name: stringPtr("my_extension"), + ExtensionType: stringPtr("bundle"), + Description: "my description", + Version: stringPtr("*"), + }), + mock.New200StructResponse(models.Extension{ // upload request response + Name: stringPtr("my_extension"), + ExtensionType: stringPtr("bundle"), + Description: "my description", + Version: stringPtr("*"), + }), + mock.New200StructResponse(models.Extension{ // read request response + Name: stringPtr("my_extension"), + ExtensionType: stringPtr("bundle"), + Description: "my description", + Version: stringPtr("*"), + }), + ), + }, + want: nil, + wantRD: wantTC200withFilePath, + }, { name: "returns an error when it receives a 500", args: args{ From a55feb8e8c14715f8d373dec8e23671939d6ac30 Mon Sep 17 00:00:00 2001 From: snowhork Date: Wed, 6 Jan 2021 06:53:40 +0900 Subject: [PATCH 10/25] ec_extension: fix extension_bundle example --- examples/extension/README.md | 12 ------------ examples/extension_bundle/README.md | 15 +++++++++++++++ .../{extension => extension_bundle}/extension.tf | 0 .../files/content.json | 0 .../files/content.json.zip | Bin .../{extension => extension_bundle}/outputs.tf | 0 6 files changed, 15 insertions(+), 12 deletions(-) delete mode 100644 examples/extension/README.md create mode 100644 examples/extension_bundle/README.md rename examples/{extension => extension_bundle}/extension.tf (100%) rename examples/{extension => extension_bundle}/files/content.json (100%) rename examples/{extension => extension_bundle}/files/content.json.zip (100%) rename examples/{extension => extension_bundle}/outputs.tf (100%) diff --git a/examples/extension/README.md b/examples/extension/README.md deleted file mode 100644 index 40b8ec27e..000000000 --- a/examples/extension/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Extension example - -This example shows how to create an Elastic Cloud extension using Terraform. -You can create the extension by `files/content.json` - -## Running the example - -To run the example, follow these steps: - -1. Build the provider by running `make install` from the main folder. -2. Run `terrafrom init` to initialize your Terraform CLI. -3. Run `terraform apply` to see how it works. diff --git a/examples/extension_bundle/README.md b/examples/extension_bundle/README.md new file mode 100644 index 000000000..b7f2d2738 --- /dev/null +++ b/examples/extension_bundle/README.md @@ -0,0 +1,15 @@ +# Extension Bundle example + +This example shows how to create an Elastic Cloud bundle extension using Terraform. + +The bundle extension is created with `files/content.json` uploaded. + +See https://www.elastic.co/guide/en/cloud/current/ec-custom-bundles.html#ec-add-your-plugin for details. + +## Running the example + +To run the example, follow these steps: + +1. Build the provider by running `make install` from the main folder. +2. Run `terrafrom init` to initialize your Terraform CLI. +3. Run `terraform apply` to see how it works. diff --git a/examples/extension/extension.tf b/examples/extension_bundle/extension.tf similarity index 100% rename from examples/extension/extension.tf rename to examples/extension_bundle/extension.tf diff --git a/examples/extension/files/content.json b/examples/extension_bundle/files/content.json similarity index 100% rename from examples/extension/files/content.json rename to examples/extension_bundle/files/content.json diff --git a/examples/extension/files/content.json.zip b/examples/extension_bundle/files/content.json.zip similarity index 100% rename from examples/extension/files/content.json.zip rename to examples/extension_bundle/files/content.json.zip diff --git a/examples/extension/outputs.tf b/examples/extension_bundle/outputs.tf similarity index 100% rename from examples/extension/outputs.tf rename to examples/extension_bundle/outputs.tf From 1aa8c44b97ab0c1a4968f7ab6f7dd22f3bad0140 Mon Sep 17 00:00:00 2001 From: snowhork Date: Fri, 8 Jan 2021 06:46:12 +0900 Subject: [PATCH 11/25] ec_extension: add download_url for request payload --- ec/ecresource/extensionresource/request.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ec/ecresource/extensionresource/request.go b/ec/ecresource/extensionresource/request.go index 98a6cd39a..167eb6bea 100644 --- a/ec/ecresource/extensionresource/request.go +++ b/ec/ecresource/extensionresource/request.go @@ -45,12 +45,14 @@ func createRequest(client *api.API, d *schema.ResourceData) (*models.Extension, version := d.Get("version").(string) extensionsType := d.Get("extension_type").(string) description := d.Get("description").(string) + downloadURL := d.Get("download_url").(string) body := &models.CreateExtensionRequest{ Name: &name, Version: &version, ExtensionType: &extensionsType, Description: description, + DownloadURL: downloadURL, } res, err := client.V1API.Extensions.CreateExtension( @@ -68,12 +70,14 @@ func updateRequest(client *api.API, d *schema.ResourceData) (*models.Extension, version := d.Get("version").(string) extensionsType := d.Get("extension_type").(string) description := d.Get("description").(string) + downloadURL := d.Get("download_url").(string) body := &models.UpdateExtensionRequest{ Name: &name, Version: &version, ExtensionType: &extensionsType, Description: description, + DownloadURL: downloadURL, } res, err := client.V1API.Extensions.UpdateExtension( From ecb300f1948af28b3522bcd5a9c098eef7747d41 Mon Sep 17 00:00:00 2001 From: snowhork Date: Fri, 8 Jan 2021 06:48:02 +0900 Subject: [PATCH 12/25] ec_extension: set empty id when delete succeed --- ec/ecresource/extensionresource/delete.go | 1 + ec/ecresource/extensionresource/delete_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/ec/ecresource/extensionresource/delete.go b/ec/ecresource/extensionresource/delete.go index 1c2d4aabb..c51b7a2c0 100644 --- a/ec/ecresource/extensionresource/delete.go +++ b/ec/ecresource/extensionresource/delete.go @@ -39,6 +39,7 @@ func deleteResource(_ context.Context, d *schema.ResourceData, meta interface{}) return diag.FromErr(err) } + d.SetId("") return nil } diff --git a/ec/ecresource/extensionresource/delete_test.go b/ec/ecresource/extensionresource/delete_test.go index 8e4f09a96..b8f2af718 100644 --- a/ec/ecresource/extensionresource/delete_test.go +++ b/ec/ecresource/extensionresource/delete_test.go @@ -41,6 +41,7 @@ func Test_deleteResource(t *testing.T) { State: newExtension(), Schema: newSchema(), }) + wantTC200.SetId("") tc500Err := util.NewResourceData(t, util.ResDataParams{ ID: "12345678", From cf32ed140fe4e15e9278db9f71f4a705f10e3f79 Mon Sep 17 00:00:00 2001 From: snowhork Date: Fri, 8 Jan 2021 06:59:07 +0900 Subject: [PATCH 13/25] ec_extension: set description for resource --- ec/ecresource/extensionresource/resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ec/ecresource/extensionresource/resource.go b/ec/ecresource/extensionresource/resource.go index 3dcdee81f..41fd94d04 100644 --- a/ec/ecresource/extensionresource/resource.go +++ b/ec/ecresource/extensionresource/resource.go @@ -26,7 +26,7 @@ import ( // Resource returns the ec_extension resource schema. func Resource() *schema.Resource { return &schema.Resource{ - Description: "Elastic Cloud deployment traffic filtering rules", + Description: "Elastic Cloud extension (plugin or bundle (scripts or dictionaries)) to enhance the core functionality of Elasticsearch. Before you install an extension, be sure to check out the supported and official Elasticsearch plugins already available", Schema: newSchema(), CreateContext: createResource, From dfbcd34ced7f6edb451b547bb76dd5dbab883bf6 Mon Sep 17 00:00:00 2001 From: snowhork Date: Fri, 8 Jan 2021 07:02:53 +0900 Subject: [PATCH 14/25] ec_extension: use string pointer util --- ec/ecresource/extensionresource/read_test.go | 10 +++--- .../extensionresource/update_test.go | 32 ++++++++++--------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/ec/ecresource/extensionresource/read_test.go b/ec/ecresource/extensionresource/read_test.go index 479ee82de..e6683034f 100644 --- a/ec/ecresource/extensionresource/read_test.go +++ b/ec/ecresource/extensionresource/read_test.go @@ -21,6 +21,8 @@ import ( "context" "testing" + "github.com/elastic/cloud-sdk-go/pkg/util/ec" + "github.com/elastic/cloud-sdk-go/pkg/api" "github.com/elastic/cloud-sdk-go/pkg/api/mock" "github.com/elastic/cloud-sdk-go/pkg/models" @@ -82,10 +84,10 @@ func Test_readResource(t *testing.T) { args: args{ d: tc200, meta: api.NewMock(mock.New200StructResponse(models.Extension{ - Name: stringPtr("my_extension"), - ExtensionType: stringPtr("bundle"), + Name: ec.String("my_extension"), + ExtensionType: ec.String("bundle"), Description: "my description", - Version: stringPtr("*"), + Version: ec.String("*"), })), }, want: nil, @@ -139,5 +141,3 @@ func Test_readResource(t *testing.T) { }) } } - -func stringPtr(s string) *string { return &s } diff --git a/ec/ecresource/extensionresource/update_test.go b/ec/ecresource/extensionresource/update_test.go index cadeb53b4..513b17512 100644 --- a/ec/ecresource/extensionresource/update_test.go +++ b/ec/ecresource/extensionresource/update_test.go @@ -21,6 +21,8 @@ import ( "context" "testing" + "github.com/elastic/cloud-sdk-go/pkg/util/ec" + "github.com/elastic/cloud-sdk-go/pkg/models" "github.com/elastic/cloud-sdk-go/pkg/api" @@ -83,16 +85,16 @@ func Test_updateResource(t *testing.T) { d: tc200withoutFilePath, meta: api.NewMock( mock.New200StructResponse(models.Extension{ // update request response - Name: stringPtr("my_extension"), - ExtensionType: stringPtr("bundle"), + Name: ec.String("my_extension"), + ExtensionType: ec.String("bundle"), Description: "my description", - Version: stringPtr("*"), + Version: ec.String("*"), }), mock.New200StructResponse(models.Extension{ // read request response - Name: stringPtr("my_extension"), - ExtensionType: stringPtr("bundle"), + Name: ec.String("my_extension"), + ExtensionType: ec.String("bundle"), Description: "my description", - Version: stringPtr("*"), + Version: ec.String("*"), }), ), }, @@ -105,22 +107,22 @@ func Test_updateResource(t *testing.T) { d: tc200withFilePath, meta: api.NewMock( mock.New200StructResponse(models.Extension{ // update request response - Name: stringPtr("my_extension"), - ExtensionType: stringPtr("bundle"), + Name: ec.String("my_extension"), + ExtensionType: ec.String("bundle"), Description: "my description", - Version: stringPtr("*"), + Version: ec.String("*"), }), mock.New200StructResponse(models.Extension{ // upload request response - Name: stringPtr("my_extension"), - ExtensionType: stringPtr("bundle"), + Name: ec.String("my_extension"), + ExtensionType: ec.String("bundle"), Description: "my description", - Version: stringPtr("*"), + Version: ec.String("*"), }), mock.New200StructResponse(models.Extension{ // read request response - Name: stringPtr("my_extension"), - ExtensionType: stringPtr("bundle"), + Name: ec.String("my_extension"), + ExtensionType: ec.String("bundle"), Description: "my description", - Version: stringPtr("*"), + Version: ec.String("*"), }), ), }, From c00675dacd8732f35c2ffc1874199fa32b05fbcd Mon Sep 17 00:00:00 2001 From: snowhork Date: Fri, 8 Jan 2021 07:44:45 +0900 Subject: [PATCH 15/25] ec_extension: add "url", "last_modified", "size" fields --- ec/ecresource/extensionresource/read.go | 16 +++++++ ec/ecresource/extensionresource/read_test.go | 8 ++++ ec/ecresource/extensionresource/schema.go | 16 +++++++ .../extensionresource/testutil_datastruct.go | 11 ++++- ec/ecresource/extensionresource/update.go | 2 +- .../extensionresource/update_test.go | 47 ++++++++++++++----- 6 files changed, 85 insertions(+), 15 deletions(-) diff --git a/ec/ecresource/extensionresource/read.go b/ec/ecresource/extensionresource/read.go index 920ac9baf..742af7709 100644 --- a/ec/ecresource/extensionresource/read.go +++ b/ec/ecresource/extensionresource/read.go @@ -74,5 +74,21 @@ func modelToState(d *schema.ResourceData, model *models.Extension) error { return err } + if err := d.Set("url", model.URL); err != nil { + return err + } + + if model.FileMetadata != nil { + filemeta := model.FileMetadata + + if err := d.Set("last_modified", filemeta.LastModifiedDate.String()); err != nil { + return err + } + + if err := d.Set("size", filemeta.Size); err != nil { + return err + } + } + return nil } diff --git a/ec/ecresource/extensionresource/read_test.go b/ec/ecresource/extensionresource/read_test.go index e6683034f..4257433e3 100644 --- a/ec/ecresource/extensionresource/read_test.go +++ b/ec/ecresource/extensionresource/read_test.go @@ -21,6 +21,8 @@ import ( "context" "testing" + "github.com/go-openapi/strfmt" + "github.com/elastic/cloud-sdk-go/pkg/util/ec" "github.com/elastic/cloud-sdk-go/pkg/api" @@ -68,6 +70,7 @@ func Test_readResource(t *testing.T) { }) wantTC404.SetId("") + lastModified, _ := strfmt.ParseDateTime("2021-01-07T22:13:42.999Z") type args struct { ctx context.Context d *schema.ResourceData @@ -88,6 +91,11 @@ func Test_readResource(t *testing.T) { ExtensionType: ec.String("bundle"), Description: "my description", Version: ec.String("*"), + URL: ec.String("repo://1234"), + FileMetadata: &models.ExtensionFileMetadata{ + LastModifiedDate: lastModified, + Size: 1000, + }, })), }, want: nil, diff --git a/ec/ecresource/extensionresource/schema.go b/ec/ecresource/extensionresource/schema.go index 3ba5fce41..c0979fb3a 100644 --- a/ec/ecresource/extensionresource/schema.go +++ b/ec/ecresource/extensionresource/schema.go @@ -61,5 +61,21 @@ func newSchema() map[string]*schema.Schema { Description: "file hash", Optional: true, }, + + "url": { + Type: schema.TypeString, + Description: "", + Computed: true, + }, + "last_modified": { + Type: schema.TypeString, + Description: "", + Computed: true, + }, + "size": { + Type: schema.TypeInt, + Description: "", + Computed: true, + }, } } diff --git a/ec/ecresource/extensionresource/testutil_datastruct.go b/ec/ecresource/extensionresource/testutil_datastruct.go index 642553c03..09c959d9a 100644 --- a/ec/ecresource/extensionresource/testutil_datastruct.go +++ b/ec/ecresource/extensionresource/testutil_datastruct.go @@ -23,6 +23,9 @@ func newExtension() map[string]interface{} { "extension_type": "bundle", "description": "my description", "version": "*", + "url": "repo://1234", + "last_modified": "2021-01-07T22:13:42.999Z", + "size": 1000, } } @@ -32,7 +35,11 @@ func newExtensionWithFilePath() map[string]interface{} { "extension_type": "bundle", "description": "my description", "version": "*", - "file_path": "test_extension_bundle.json", - "file_hash": "abcd", + "url": "repo://1234", + "last_modified": "2021-01-07T22:13:42.999Z", + "size": 1000, + + "file_path": "test_extension_bundle.json", + "file_hash": "abcd", } } diff --git a/ec/ecresource/extensionresource/update.go b/ec/ecresource/extensionresource/update.go index 4ec6ec9b3..cfeb63284 100644 --- a/ec/ecresource/extensionresource/update.go +++ b/ec/ecresource/extensionresource/update.go @@ -34,7 +34,7 @@ func updateResource(ctx context.Context, d *schema.ResourceData, meta interface{ return diag.FromErr(err) } - if _, ok := d.GetOk("file_path"); ok && d.HasChange("file_hash") { + if _, ok := d.GetOk("file_path"); ok && d.HasChanges("file_hash", "last_modified", "size") { if err := uploadRequest(client, d); err != nil { return diag.FromErr(multierror.NewPrefixed("failed to upload file", err)) } diff --git a/ec/ecresource/extensionresource/update_test.go b/ec/ecresource/extensionresource/update_test.go index 513b17512..ca729db15 100644 --- a/ec/ecresource/extensionresource/update_test.go +++ b/ec/ecresource/extensionresource/update_test.go @@ -21,6 +21,8 @@ import ( "context" "testing" + "github.com/go-openapi/strfmt" + "github.com/elastic/cloud-sdk-go/pkg/util/ec" "github.com/elastic/cloud-sdk-go/pkg/models" @@ -40,9 +42,12 @@ func Test_updateResource(t *testing.T) { State: newExtension(), Schema: newSchema(), }) + + wantTC200statewithoutFilePath := newExtension() + wantTC200statewithoutFilePath["name"] = "updated_extension" wantTC200withoutFilePath := util.NewResourceData(t, util.ResDataParams{ ID: "12345678", - State: newExtension(), + State: wantTC200statewithoutFilePath, Schema: newSchema(), }) @@ -51,9 +56,11 @@ func Test_updateResource(t *testing.T) { State: newExtensionWithFilePath(), Schema: newSchema(), }) + wantTC200statewithFilePath := newExtensionWithFilePath() + wantTC200statewithFilePath["name"] = "updated_extension" wantTC200withFilePath := util.NewResourceData(t, util.ResDataParams{ ID: "12345678", - State: newExtensionWithFilePath(), + State: wantTC200statewithFilePath, Schema: newSchema(), }) @@ -68,6 +75,7 @@ func Test_updateResource(t *testing.T) { Schema: newSchema(), }) + lastModified, _ := strfmt.ParseDateTime("2021-01-07T22:13:42.999Z") type args struct { ctx context.Context d *schema.ResourceData @@ -85,16 +93,26 @@ func Test_updateResource(t *testing.T) { d: tc200withoutFilePath, meta: api.NewMock( mock.New200StructResponse(models.Extension{ // update request response - Name: ec.String("my_extension"), + Name: ec.String("updated_extension"), ExtensionType: ec.String("bundle"), Description: "my description", Version: ec.String("*"), + URL: ec.String("repo://1234"), + FileMetadata: &models.ExtensionFileMetadata{ + LastModifiedDate: lastModified, + Size: 1000, + }, }), mock.New200StructResponse(models.Extension{ // read request response - Name: ec.String("my_extension"), + Name: ec.String("updated_extension"), ExtensionType: ec.String("bundle"), Description: "my description", Version: ec.String("*"), + URL: ec.String("repo://1234"), + FileMetadata: &models.ExtensionFileMetadata{ + LastModifiedDate: lastModified, + Size: 1000, + }, }), ), }, @@ -107,22 +125,27 @@ func Test_updateResource(t *testing.T) { d: tc200withFilePath, meta: api.NewMock( mock.New200StructResponse(models.Extension{ // update request response - Name: ec.String("my_extension"), - ExtensionType: ec.String("bundle"), - Description: "my description", - Version: ec.String("*"), - }), - mock.New200StructResponse(models.Extension{ // upload request response - Name: ec.String("my_extension"), + Name: ec.String("updated_extension"), ExtensionType: ec.String("bundle"), Description: "my description", Version: ec.String("*"), + URL: ec.String("repo://1234"), + FileMetadata: &models.ExtensionFileMetadata{ + LastModifiedDate: lastModified, + Size: 1000, + }, }), + mock.New200StructResponse(nil), // upload request response mock.New200StructResponse(models.Extension{ // read request response - Name: ec.String("my_extension"), + Name: ec.String("updated_extension"), ExtensionType: ec.String("bundle"), Description: "my description", Version: ec.String("*"), + URL: ec.String("repo://1234"), + FileMetadata: &models.ExtensionFileMetadata{ + LastModifiedDate: lastModified, + Size: 1000, + }, }), ), }, From b4246f9522309909d8b829a7eae1f22dc31343cc Mon Sep 17 00:00:00 2001 From: snowhork Date: Sat, 9 Jan 2021 10:24:27 +0900 Subject: [PATCH 16/25] ec_extension: acc test: extension_bundle_file --- ec/acc/extension_bundle_file_test.go | 177 +++++++++++++++++++++++ ec/acc/extension_destroy_test.go | 52 +++++++ ec/acc/testdata/extension_bundle_file.tf | 14 ++ 3 files changed, 243 insertions(+) create mode 100644 ec/acc/extension_bundle_file_test.go create mode 100644 ec/acc/extension_destroy_test.go create mode 100644 ec/acc/testdata/extension_bundle_file.tf diff --git a/ec/acc/extension_bundle_file_test.go b/ec/acc/extension_bundle_file_test.go new file mode 100644 index 000000000..f49e55149 --- /dev/null +++ b/ec/acc/extension_bundle_file_test.go @@ -0,0 +1,177 @@ +// 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 acc + +import ( + "archive/zip" + "bytes" + "fmt" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "testing" + + "github.com/elastic/cloud-sdk-go/pkg/client/extensions" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccExtension_bundle_file(t *testing.T) { + resName := "ec_extension.my_extension" + randomName := prefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + filePath := filepath.Join(os.TempDir(), "extension.zip") + defer os.Remove(filePath) + + cfg := fixtureAccExtensionBundleWithTF(t, "testdata/extension_bundle_file.tf", filePath, randomName, "desc") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactory, + CheckDestroy: testAccExtensionDestroy, + Steps: []resource.TestStep{ + { + PreConfig: func() { writeFile(t, filePath, "extension.txt", "foo") }, + Config: cfg, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resName, "name", randomName), + resource.TestCheckResourceAttr(resName, "version", "*"), + resource.TestCheckResourceAttr(resName, "description", "desc"), + resource.TestCheckResourceAttr(resName, "extension_type", "bundle"), + resource.TestCheckResourceAttr(resName, "file_path", filePath), + func(s *terraform.State) error { + return checkExtensionFile(t, s, "extension.txt", "foo") + }, + ), + }, + { + PreConfig: func() { writeFile(t, filePath, "extension.txt", "bar") }, + Config: cfg, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resName, "name", randomName), + resource.TestCheckResourceAttr(resName, "version", "*"), + resource.TestCheckResourceAttr(resName, "description", "desc"), + resource.TestCheckResourceAttr(resName, "extension_type", "bundle"), + resource.TestCheckResourceAttr(resName, "file_path", filePath), + func(s *terraform.State) error { + return checkExtensionFile(t, s, "extension.txt", "bar") + }, + ), + }, + }, + }) +} + +func fixtureAccExtensionBundleWithTF(t *testing.T, tfFileName, bundleFilePath, extensionName, description string) string { + t.Helper() + + b, err := ioutil.ReadFile(tfFileName) + if err != nil { + t.Fatal(err) + } + return fmt.Sprintf(string(b), + bundleFilePath, extensionName, description, + ) +} + +func writeFile(t *testing.T, filePath, fileName, content string) { + t.Helper() + + buf := new(bytes.Buffer) + writer := zip.NewWriter(buf) + + f, err := writer.Create(fileName) + if err != nil { + t.Fatal(err) + } + + if _, err := f.Write([]byte(content)); err != nil { + t.Fatal(err) + } + + if err := writer.Close(); err != nil { + t.Fatal(err) + } + + if err := ioutil.WriteFile(filePath, buf.Bytes(), 0644); err != nil { + t.Fatal(err) + } +} + +func checkExtensionFile(t *testing.T, s *terraform.State, filename string, expected string) error { + client, err := newAPI() + if err != nil { + t.Fatal(err) + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "ec_extension" { + continue + } + + res, err := client.V1API.Extensions.GetExtension( + extensions.NewGetExtensionParams().WithExtensionID(rs.Primary.ID), + client.AuthWriter) + if err != nil { + t.Fatal(err) + } + + content, err := downloadAndReadExtension(filename, res.Payload.FileMetadata.URL.String(), res.Payload.FileMetadata.Size) + if err != nil { + t.Fatal(err) + } + + if content == expected { + return nil // ok + } + return fmt.Errorf("extension content is expected: %s, but got: %s", expected, content) + } + + return fmt.Errorf("extension doesn't exists") +} + +func downloadAndReadExtension(filename string, url string, size int64) (string, error) { + resp, err := http.Get(url) + if err != nil { + return "", err + } + + b, _ := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + + r, err := zip.NewReader(bytes.NewReader(b), size) + if err != nil { + return "", err + } + + if len(r.File) == 0 { + return "", fmt.Errorf("the zip file has no content") + } + + for _, f := range r.File { + reader, _ := f.Open() + b, _ := ioutil.ReadAll(reader) + func() { defer reader.Close() }() + if filename == f.Name { + return string(b), nil + } + } + return "", fmt.Errorf("not found: %s", filename) +} diff --git a/ec/acc/extension_destroy_test.go b/ec/acc/extension_destroy_test.go new file mode 100644 index 000000000..25c07fe86 --- /dev/null +++ b/ec/acc/extension_destroy_test.go @@ -0,0 +1,52 @@ +// 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 acc + +import ( + "fmt" + + "github.com/elastic/cloud-sdk-go/pkg/api/apierror" + "github.com/elastic/cloud-sdk-go/pkg/client/extensions" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func testAccExtensionDestroy(s *terraform.State) error { + client, err := newAPI() + if err != nil { + return err + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "ec_extension" { + continue + } + + res, err := client.V1API.Extensions.GetExtension( + extensions.NewGetExtensionParams().WithExtensionID(rs.Primary.ID), + client.AuthWriter) + + // If not extension exists, api gets 403 error + if err != nil && apierror.IsRuntimeStatusCode(err, 403) { + continue + } + + return fmt.Errorf("extension (%s) still exists", *res.Payload.ID) + } + + return nil +} diff --git a/ec/acc/testdata/extension_bundle_file.tf b/ec/acc/testdata/extension_bundle_file.tf new file mode 100644 index 000000000..c4fd4010a --- /dev/null +++ b/ec/acc/testdata/extension_bundle_file.tf @@ -0,0 +1,14 @@ +locals { + file_path = "%s" +} + +# Create an Elastic Cloud Extension +resource "ec_extension" "my_extension" { + name = "%s" + description = "%s" + version = "*" + extension_type = "bundle" + + file_path = local.file_path + file_hash = filebase64sha256(local.file_path) +} From c3891ad598930999e460b175ba598dd4294badd0 Mon Sep 17 00:00:00 2001 From: snowhork Date: Mon, 11 Jan 2021 06:13:59 +0900 Subject: [PATCH 17/25] ec_extension: acc_test for updating description --- ec/acc/extension_basic_test.go | 73 ++++++++++++++++++++++++++++++ ec/acc/testdata/extension_basic.tf | 7 +++ 2 files changed, 80 insertions(+) create mode 100644 ec/acc/extension_basic_test.go create mode 100644 ec/acc/testdata/extension_basic.tf diff --git a/ec/acc/extension_basic_test.go b/ec/acc/extension_basic_test.go new file mode 100644 index 000000000..1380ed8a0 --- /dev/null +++ b/ec/acc/extension_basic_test.go @@ -0,0 +1,73 @@ +// 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 acc + +import ( + "fmt" + "io/ioutil" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccExtension_basic(t *testing.T) { + resName := "ec_extension.my_extension" + randomName := prefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + cfg := fixtureAccExtensionBasicWithTF(t, "testdata/extension_basic.tf", randomName, "desc") + cfg2 := fixtureAccExtensionBasicWithTF(t, "testdata/extension_basic.tf", randomName, "updated desc") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactory, + CheckDestroy: testAccExtensionDestroy, + Steps: []resource.TestStep{ + { + Config: cfg, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resName, "name", randomName), + resource.TestCheckResourceAttr(resName, "version", "*"), + resource.TestCheckResourceAttr(resName, "description", "desc"), + resource.TestCheckResourceAttr(resName, "extension_type", "bundle"), + ), + }, + { + Config: cfg2, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resName, "name", randomName), + resource.TestCheckResourceAttr(resName, "version", "*"), + resource.TestCheckResourceAttr(resName, "description", "updated desc"), + resource.TestCheckResourceAttr(resName, "extension_type", "bundle"), + ), + }, + }, + }) +} + +func fixtureAccExtensionBasicWithTF(t *testing.T, tfFileName, extensionName, description string) string { + t.Helper() + + b, err := ioutil.ReadFile(tfFileName) + if err != nil { + t.Fatal(err) + } + return fmt.Sprintf(string(b), + extensionName, description, + ) +} diff --git a/ec/acc/testdata/extension_basic.tf b/ec/acc/testdata/extension_basic.tf new file mode 100644 index 000000000..9c6ed801a --- /dev/null +++ b/ec/acc/testdata/extension_basic.tf @@ -0,0 +1,7 @@ +# Create an Elastic Cloud Extension +resource "ec_extension" "my_extension" { + name = "%s" + description = "%s" + version = "*" + extension_type = "bundle" +} From cd26327f3c8279a9b2b16c5ac5516f45de10aaa3 Mon Sep 17 00:00:00 2001 From: snowhork Date: Mon, 11 Jan 2021 06:48:03 +0900 Subject: [PATCH 18/25] ec_extension: add doc --- docs/resources/ec_extension.md | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 docs/resources/ec_extension.md diff --git a/docs/resources/ec_extension.md b/docs/resources/ec_extension.md new file mode 100644 index 000000000..a26c57acf --- /dev/null +++ b/docs/resources/ec_extension.md @@ -0,0 +1,58 @@ +--- +page_title: "Elastic Cloud: ec_extension" +description: |- + Provides an Elastic Cloud extension resource, which allows extension to be created, updated, and deleted. +--- + +# Resource: ec_extension +Provides an Elastic Cloud extension resource, which allows extension to be created, updated, and deleted. + +## Example Usage +### with extension file + +```hcl +locals { + file_path = "/path/to/plugin.zip" +} + +resource "ec_extension" "example_extension" { + name = "my_extension" + description = "my extension" + version = "*" + extension_type = "bundle" + + file_path = local.file_path + file_hash = filebase64sha256(local.file_path) +} +``` + +### with download URL +```hcl +resource "ec_extension" "example_extension" { + name = "my_extension" + description = "my extension" + version = "*" + extension_type = "bundle" + download_url = "https://example.net" +} +``` + +## Argument Reference +The following arguments are supported: + +* `name` - (Required) Name of the extension. +* `description` - (Required) Description of the extension. +* `extension_type` - (Required) `bundle` or `plugin` allowed. +* `version` - (Required) Elastic version. +* `download_url` - (Optional) The URL to download the extension archive. +* `file_path` - (Optional) File path of the extension uploaded. +* `file_hash` - (Optional) Hash value of the file. If it is changed, the file is reuploaded. + + +## Attributes Reference +In addition to all the arguments above, the following attributes are exported: + +* `id` - Extension identifier. +* `url` - The extension URL to be used in the plan. +* `last_modified` - The datetime the extension was last modified. +* `size` - The extension file size in bytes. From 57edd743e0454deba4343d997a463adfa57d831d Mon Sep 17 00:00:00 2001 From: snowhork Date: Mon, 11 Jan 2021 22:22:53 +0900 Subject: [PATCH 19/25] ec_extension: set description optional --- docs/resources/ec_extension.md | 2 +- ec/ecresource/extensionresource/schema.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/resources/ec_extension.md b/docs/resources/ec_extension.md index a26c57acf..cca6eb504 100644 --- a/docs/resources/ec_extension.md +++ b/docs/resources/ec_extension.md @@ -41,7 +41,7 @@ resource "ec_extension" "example_extension" { The following arguments are supported: * `name` - (Required) Name of the extension. -* `description` - (Required) Description of the extension. +* `description` - (Optional) Description of the extension. * `extension_type` - (Required) `bundle` or `plugin` allowed. * `version` - (Required) Elastic version. * `download_url` - (Optional) The URL to download the extension archive. diff --git a/ec/ecresource/extensionresource/schema.go b/ec/ecresource/extensionresource/schema.go index c0979fb3a..ee3ae22cb 100644 --- a/ec/ecresource/extensionresource/schema.go +++ b/ec/ecresource/extensionresource/schema.go @@ -31,7 +31,7 @@ func newSchema() map[string]*schema.Schema { "description": { Type: schema.TypeString, Description: "Description for extension", - Required: true, + Optional: true, }, "extension_type": { Type: schema.TypeString, From 4c5c13576e2a07e28a53e16ce68d6a59b3ee5bf4 Mon Sep 17 00:00:00 2001 From: snowhork Date: Mon, 11 Jan 2021 22:27:03 +0900 Subject: [PATCH 20/25] ec_extension: fmt --- ec/ecresource/extensionresource/read.go | 4 +--- ec/ecresource/extensionresource/update_test.go | 9 +++------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/ec/ecresource/extensionresource/read.go b/ec/ecresource/extensionresource/read.go index 742af7709..ef909791b 100644 --- a/ec/ecresource/extensionresource/read.go +++ b/ec/ecresource/extensionresource/read.go @@ -78,9 +78,7 @@ func modelToState(d *schema.ResourceData, model *models.Extension) error { return err } - if model.FileMetadata != nil { - filemeta := model.FileMetadata - + if filemeta := model.FileMetadata; filemeta != nil { if err := d.Set("last_modified", filemeta.LastModifiedDate.String()); err != nil { return err } diff --git a/ec/ecresource/extensionresource/update_test.go b/ec/ecresource/extensionresource/update_test.go index ca729db15..38bfaa498 100644 --- a/ec/ecresource/extensionresource/update_test.go +++ b/ec/ecresource/extensionresource/update_test.go @@ -21,14 +21,11 @@ import ( "context" "testing" - "github.com/go-openapi/strfmt" - - "github.com/elastic/cloud-sdk-go/pkg/util/ec" - - "github.com/elastic/cloud-sdk-go/pkg/models" - "github.com/elastic/cloud-sdk-go/pkg/api" "github.com/elastic/cloud-sdk-go/pkg/api/mock" + "github.com/elastic/cloud-sdk-go/pkg/models" + "github.com/elastic/cloud-sdk-go/pkg/util/ec" + "github.com/go-openapi/strfmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/stretchr/testify/assert" From 2a3e8a53dc5483f448b43a93491e6065e9cc5a56 Mon Sep 17 00:00:00 2001 From: snowhork Date: Mon, 11 Jan 2021 22:28:47 +0900 Subject: [PATCH 21/25] ec_extension: fix docs --- docs/resources/ec_extension.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/resources/ec_extension.md b/docs/resources/ec_extension.md index cca6eb504..90600e565 100644 --- a/docs/resources/ec_extension.md +++ b/docs/resources/ec_extension.md @@ -7,6 +7,8 @@ description: |- # Resource: ec_extension Provides an Elastic Cloud extension resource, which allows extension to be created, updated, and deleted. +Extensions allow users of Elastic Cloud to use custom plugins, scripts, or dictionaries to enhance the core functionality of Elasticsearch. Before you install an extension, be sure to check out the supported and official [Elasticsearch plugins](https://www.elastic.co/guide/en/elasticsearch/plugins/current/index.html) already available. + ## Example Usage ### with extension file @@ -42,8 +44,8 @@ The following arguments are supported: * `name` - (Required) Name of the extension. * `description` - (Optional) Description of the extension. -* `extension_type` - (Required) `bundle` or `plugin` allowed. -* `version` - (Required) Elastic version. +* `extension_type` - (Required) `bundle` or `plugin` allowed. A `bundle` will usually contain a dictionary or script, where a `plugin` is compiled from source. +* `version` - (Required) Elastic stack version, a numeric version for plugins, e.g. 2.3.0 should be set. Major version e.g. 2.*, or wildcards e.g. * for bundles. * `download_url` - (Optional) The URL to download the extension archive. * `file_path` - (Optional) File path of the extension uploaded. * `file_hash` - (Optional) Hash value of the file. If it is changed, the file is reuploaded. From f1211384d2b845bee96736b7a9bf3f3305f132f7 Mon Sep 17 00:00:00 2001 From: snowhork Date: Mon, 11 Jan 2021 22:40:06 +0900 Subject: [PATCH 22/25] ec_extension: add bundle_download test --- ec/acc/extension_bundle_download_test.go | 62 ++++++++++++++++++++ ec/acc/extension_bundle_file_test.go | 2 +- ec/acc/testdata/extension_basic.tf | 1 - ec/acc/testdata/extension_bundle_download.tf | 6 ++ ec/acc/testdata/extension_bundle_file.tf | 1 - 5 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 ec/acc/extension_bundle_download_test.go create mode 100644 ec/acc/testdata/extension_bundle_download.tf diff --git a/ec/acc/extension_bundle_download_test.go b/ec/acc/extension_bundle_download_test.go new file mode 100644 index 000000000..eb0e23664 --- /dev/null +++ b/ec/acc/extension_bundle_download_test.go @@ -0,0 +1,62 @@ +// 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 acc + +import ( + "fmt" + "io/ioutil" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccExtension_bundleDownload(t *testing.T) { + resName := "ec_extension.my_extension" + randomName := prefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + downloadURL := "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-7.10.1.zip" + + cfg := fixtureAccExtensionBundleDownloadWithTF(t, "testdata/extension_bundle_download.tf", randomName, downloadURL) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactory, + CheckDestroy: testAccExtensionDestroy, + Steps: []resource.TestStep{ + { + Config: cfg, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resName, "name", randomName), + resource.TestCheckResourceAttr(resName, "version", "*"), + resource.TestCheckResourceAttr(resName, "download_url", downloadURL), + resource.TestCheckResourceAttr(resName, "extension_type", "bundle"), + ), + }, + }, + }) +} + +func fixtureAccExtensionBundleDownloadWithTF(t *testing.T, tfFileName, extensionName, downloadURL string) string { + t.Helper() + + b, err := ioutil.ReadFile(tfFileName) + if err != nil { + t.Fatal(err) + } + return fmt.Sprintf(string(b), extensionName, downloadURL) +} diff --git a/ec/acc/extension_bundle_file_test.go b/ec/acc/extension_bundle_file_test.go index f49e55149..330323de8 100644 --- a/ec/acc/extension_bundle_file_test.go +++ b/ec/acc/extension_bundle_file_test.go @@ -33,7 +33,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -func TestAccExtension_bundle_file(t *testing.T) { +func TestAccExtension_bundleFile(t *testing.T) { resName := "ec_extension.my_extension" randomName := prefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) diff --git a/ec/acc/testdata/extension_basic.tf b/ec/acc/testdata/extension_basic.tf index 9c6ed801a..21c7fc456 100644 --- a/ec/acc/testdata/extension_basic.tf +++ b/ec/acc/testdata/extension_basic.tf @@ -1,4 +1,3 @@ -# Create an Elastic Cloud Extension resource "ec_extension" "my_extension" { name = "%s" description = "%s" diff --git a/ec/acc/testdata/extension_bundle_download.tf b/ec/acc/testdata/extension_bundle_download.tf new file mode 100644 index 000000000..8b9b0eade --- /dev/null +++ b/ec/acc/testdata/extension_bundle_download.tf @@ -0,0 +1,6 @@ +resource "ec_extension" "my_extension" { + name = "%s" + version = "*" + extension_type = "bundle" + download_url = "%s" +} diff --git a/ec/acc/testdata/extension_bundle_file.tf b/ec/acc/testdata/extension_bundle_file.tf index c4fd4010a..e731e65f4 100644 --- a/ec/acc/testdata/extension_bundle_file.tf +++ b/ec/acc/testdata/extension_bundle_file.tf @@ -2,7 +2,6 @@ locals { file_path = "%s" } -# Create an Elastic Cloud Extension resource "ec_extension" "my_extension" { name = "%s" description = "%s" From d340fa390b98bad664421b516c88dae4fcc96ec5 Mon Sep 17 00:00:00 2001 From: snowhork Date: Mon, 11 Jan 2021 23:22:29 +0900 Subject: [PATCH 23/25] ec_extension: change extension type bundle to plugin --- ...download_test.go => extension_plugin_download_test.go} | 8 ++++---- ...on_bundle_download.tf => extension_plugin_download.tf} | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) rename ec/acc/{extension_bundle_download_test.go => extension_plugin_download_test.go} (91%) rename ec/acc/testdata/{extension_bundle_download.tf => extension_plugin_download.tf} (61%) diff --git a/ec/acc/extension_bundle_download_test.go b/ec/acc/extension_plugin_download_test.go similarity index 91% rename from ec/acc/extension_bundle_download_test.go rename to ec/acc/extension_plugin_download_test.go index eb0e23664..bd88565ac 100644 --- a/ec/acc/extension_bundle_download_test.go +++ b/ec/acc/extension_plugin_download_test.go @@ -26,12 +26,12 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -func TestAccExtension_bundleDownload(t *testing.T) { +func TestAccExtension_pluginDownload(t *testing.T) { resName := "ec_extension.my_extension" randomName := prefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) downloadURL := "https://artifacts.elastic.co/downloads/elasticsearch-plugins/analysis-icu/analysis-icu-7.10.1.zip" - cfg := fixtureAccExtensionBundleDownloadWithTF(t, "testdata/extension_bundle_download.tf", randomName, downloadURL) + cfg := fixtureAccExtensionBundleDownloadWithTF(t, "testdata/extension_plugin_download.tf", randomName, downloadURL) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -42,9 +42,9 @@ func TestAccExtension_bundleDownload(t *testing.T) { Config: cfg, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resName, "name", randomName), - resource.TestCheckResourceAttr(resName, "version", "*"), + resource.TestCheckResourceAttr(resName, "version", "7.10.1"), resource.TestCheckResourceAttr(resName, "download_url", downloadURL), - resource.TestCheckResourceAttr(resName, "extension_type", "bundle"), + resource.TestCheckResourceAttr(resName, "extension_type", "plugin"), ), }, }, diff --git a/ec/acc/testdata/extension_bundle_download.tf b/ec/acc/testdata/extension_plugin_download.tf similarity index 61% rename from ec/acc/testdata/extension_bundle_download.tf rename to ec/acc/testdata/extension_plugin_download.tf index 8b9b0eade..6122557d3 100644 --- a/ec/acc/testdata/extension_bundle_download.tf +++ b/ec/acc/testdata/extension_plugin_download.tf @@ -1,6 +1,6 @@ resource "ec_extension" "my_extension" { name = "%s" - version = "*" - extension_type = "bundle" + version = "7.10.1" + extension_type = "plugin" download_url = "%s" } From afb3299f4d07fa849f95594afeef868c46039aff Mon Sep 17 00:00:00 2001 From: "kyoh.maruo" <18437866+snowhork@users.noreply.github.com> Date: Tue, 12 Jan 2021 10:36:53 +0900 Subject: [PATCH 24/25] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ec_extension: fix some comments Co-authored-by: Karen Cárcamo --- docs/resources/ec_extension.md | 10 +++++----- ec/ecresource/extensionresource/create.go | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/resources/ec_extension.md b/docs/resources/ec_extension.md index 90600e565..5acdf43d3 100644 --- a/docs/resources/ec_extension.md +++ b/docs/resources/ec_extension.md @@ -1,16 +1,16 @@ --- page_title: "Elastic Cloud: ec_extension" description: |- - Provides an Elastic Cloud extension resource, which allows extension to be created, updated, and deleted. + Provides an Elastic Cloud extension resource, which allows extensions to be created, updated, and deleted. --- # Resource: ec_extension -Provides an Elastic Cloud extension resource, which allows extension to be created, updated, and deleted. +Provides an Elastic Cloud extension resource, which allows extensions to be created, updated, and deleted. Extensions allow users of Elastic Cloud to use custom plugins, scripts, or dictionaries to enhance the core functionality of Elasticsearch. Before you install an extension, be sure to check out the supported and official [Elasticsearch plugins](https://www.elastic.co/guide/en/elasticsearch/plugins/current/index.html) already available. ## Example Usage -### with extension file +### With extension file ```hcl locals { @@ -28,7 +28,7 @@ resource "ec_extension" "example_extension" { } ``` -### with download URL +### With download URL ```hcl resource "ec_extension" "example_extension" { name = "my_extension" @@ -44,7 +44,7 @@ The following arguments are supported: * `name` - (Required) Name of the extension. * `description` - (Optional) Description of the extension. -* `extension_type` - (Required) `bundle` or `plugin` allowed. A `bundle` will usually contain a dictionary or script, where a `plugin` is compiled from source. +* `extension_type` - (Required) `bundle` or `plugin` allowed. A `bundle` will usually contain a dictionary or script, where a `plugin` is compiled from source. * `version` - (Required) Elastic stack version, a numeric version for plugins, e.g. 2.3.0 should be set. Major version e.g. 2.*, or wildcards e.g. * for bundles. * `download_url` - (Optional) The URL to download the extension archive. * `file_path` - (Optional) File path of the extension uploaded. diff --git a/ec/ecresource/extensionresource/create.go b/ec/ecresource/extensionresource/create.go index 6c8c5506d..70394e102 100644 --- a/ec/ecresource/extensionresource/create.go +++ b/ec/ecresource/extensionresource/create.go @@ -26,7 +26,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -// Create will createResource a new deployment traffic filter ruleset +// createResource will create a new deployment extension func createResource(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*api.API) From adbac570f6338ce93c13063d27b5ac1e33d685bf Mon Sep 17 00:00:00 2001 From: snowhork Date: Tue, 12 Jan 2021 10:39:26 +0900 Subject: [PATCH 25/25] ec_extension: set download_url on read --- ec/ecresource/extensionresource/read.go | 4 ++++ ec/ecresource/extensionresource/read_test.go | 1 + ec/ecresource/extensionresource/testutil_datastruct.go | 2 ++ ec/ecresource/extensionresource/update_test.go | 4 ++++ 4 files changed, 11 insertions(+) diff --git a/ec/ecresource/extensionresource/read.go b/ec/ecresource/extensionresource/read.go index ef909791b..9529d33f2 100644 --- a/ec/ecresource/extensionresource/read.go +++ b/ec/ecresource/extensionresource/read.go @@ -78,6 +78,10 @@ func modelToState(d *schema.ResourceData, model *models.Extension) error { return err } + if err := d.Set("download_url", model.DownloadURL); err != nil { + return err + } + if filemeta := model.FileMetadata; filemeta != nil { if err := d.Set("last_modified", filemeta.LastModifiedDate.String()); err != nil { return err diff --git a/ec/ecresource/extensionresource/read_test.go b/ec/ecresource/extensionresource/read_test.go index 4257433e3..a70d4ee49 100644 --- a/ec/ecresource/extensionresource/read_test.go +++ b/ec/ecresource/extensionresource/read_test.go @@ -91,6 +91,7 @@ func Test_readResource(t *testing.T) { ExtensionType: ec.String("bundle"), Description: "my description", Version: ec.String("*"), + DownloadURL: "example.com", URL: ec.String("repo://1234"), FileMetadata: &models.ExtensionFileMetadata{ LastModifiedDate: lastModified, diff --git a/ec/ecresource/extensionresource/testutil_datastruct.go b/ec/ecresource/extensionresource/testutil_datastruct.go index 09c959d9a..6d168864e 100644 --- a/ec/ecresource/extensionresource/testutil_datastruct.go +++ b/ec/ecresource/extensionresource/testutil_datastruct.go @@ -23,6 +23,7 @@ func newExtension() map[string]interface{} { "extension_type": "bundle", "description": "my description", "version": "*", + "download_url": "example.com", "url": "repo://1234", "last_modified": "2021-01-07T22:13:42.999Z", "size": 1000, @@ -35,6 +36,7 @@ func newExtensionWithFilePath() map[string]interface{} { "extension_type": "bundle", "description": "my description", "version": "*", + "download_url": "example.com", "url": "repo://1234", "last_modified": "2021-01-07T22:13:42.999Z", "size": 1000, diff --git a/ec/ecresource/extensionresource/update_test.go b/ec/ecresource/extensionresource/update_test.go index 38bfaa498..297bae5f1 100644 --- a/ec/ecresource/extensionresource/update_test.go +++ b/ec/ecresource/extensionresource/update_test.go @@ -94,6 +94,7 @@ func Test_updateResource(t *testing.T) { ExtensionType: ec.String("bundle"), Description: "my description", Version: ec.String("*"), + DownloadURL: "example.com", URL: ec.String("repo://1234"), FileMetadata: &models.ExtensionFileMetadata{ LastModifiedDate: lastModified, @@ -105,6 +106,7 @@ func Test_updateResource(t *testing.T) { ExtensionType: ec.String("bundle"), Description: "my description", Version: ec.String("*"), + DownloadURL: "example.com", URL: ec.String("repo://1234"), FileMetadata: &models.ExtensionFileMetadata{ LastModifiedDate: lastModified, @@ -126,6 +128,7 @@ func Test_updateResource(t *testing.T) { ExtensionType: ec.String("bundle"), Description: "my description", Version: ec.String("*"), + DownloadURL: "example.com", URL: ec.String("repo://1234"), FileMetadata: &models.ExtensionFileMetadata{ LastModifiedDate: lastModified, @@ -138,6 +141,7 @@ func Test_updateResource(t *testing.T) { ExtensionType: ec.String("bundle"), Description: "my description", Version: ec.String("*"), + DownloadURL: "example.com", URL: ec.String("repo://1234"), FileMetadata: &models.ExtensionFileMetadata{ LastModifiedDate: lastModified,