Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ec_extension: implement extension resource #216

Merged
merged 25 commits into from
Jan 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b9a0301
ec_extension: implement extension resource
snowhork Dec 25, 2020
a6bcc37
ec_extension: fix variable code style
snowhork Jan 4, 2021
0238de7
ec_extension: fix typo
snowhork Jan 4, 2021
5e66825
ec_extension: file_hash requires_with file_path
snowhork Jan 4, 2021
87dee54
ec_extension: use GetOk for key check
snowhork Jan 4, 2021
112f437
ec_extension: check hash changed on update for avoiding reupload
snowhork Jan 4, 2021
31a44fb
ec_extension: fix request wrapper to return model
snowhork Jan 4, 2021
73dfb89
ec_extension: fix upload request wrapper to use filePath variable
snowhork Jan 4, 2021
89f1147
ec_extension: fix request error handling and add success test case
snowhork Jan 5, 2021
a55feb8
ec_extension: fix extension_bundle example
snowhork Jan 5, 2021
1aa8c44
ec_extension: add download_url for request payload
snowhork Jan 7, 2021
ecb300f
ec_extension: set empty id when delete succeed
snowhork Jan 7, 2021
cf32ed1
ec_extension: set description for resource
snowhork Jan 7, 2021
dfbcd34
ec_extension: use string pointer util
snowhork Jan 7, 2021
c00675d
ec_extension: add "url", "last_modified", "size" fields
snowhork Jan 7, 2021
b4246f9
ec_extension: acc test: extension_bundle_file
snowhork Jan 9, 2021
c3891ad
ec_extension: acc_test for updating description
snowhork Jan 10, 2021
cd26327
ec_extension: add doc
snowhork Jan 10, 2021
57edd74
ec_extension: set description optional
snowhork Jan 11, 2021
4c5c135
ec_extension: fmt
snowhork Jan 11, 2021
2a3e8a5
ec_extension: fix docs
snowhork Jan 11, 2021
f121138
ec_extension: add bundle_download test
snowhork Jan 11, 2021
d340fa3
ec_extension: change extension type bundle to plugin
snowhork Jan 11, 2021
afb3299
Apply suggestions from code review
snowhork Jan 12, 2021
adbac57
ec_extension: set download_url on read
snowhork Jan 12, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions docs/resources/ec_extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
page_title: "Elastic Cloud: ec_extension"
description: |-
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 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

```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` - (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.
* `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.


## 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.
73 changes: 73 additions & 0 deletions ec/acc/extension_basic_test.go
Original file line number Diff line number Diff line change
@@ -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,
)
}
177 changes: 177 additions & 0 deletions ec/acc/extension_bundle_file_test.go
Original file line number Diff line number Diff line change
@@ -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_bundleFile(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)
}
52 changes: 52 additions & 0 deletions ec/acc/extension_destroy_test.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading