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

Add Traffic Filter data source #619

Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
aa4baba
Adding a basic traffic filter data source that uses the ID of the fil…
andrew-moldovan Apr 13, 2023
dc25ccf
Pivoted to using the list api and filtering on the client for the nam…
andrew-moldovan Apr 17, 2023
2a7a8bb
adding the ability to filter on region and id as well and also return…
andrew-moldovan Apr 18, 2023
c2e9db6
got the rules showing up as well :tada:
andrew-moldovan Apr 18, 2023
d2d17a9
fixing some lint
andrew-moldovan Apr 18, 2023
2b5ab94
struggling with tests
andrew-moldovan Apr 19, 2023
0569bb8
wow a test actually works, quick commit before I screw it up more
andrew-moldovan Apr 19, 2023
5c9337c
refactored the tests and added a few more to cover the scenarios
andrew-moldovan Apr 19, 2023
fa561c7
and one more for matching region
andrew-moldovan Apr 19, 2023
7e1e552
adding a basic acceptance test though I expect it to fail because it'…
andrew-moldovan Apr 20, 2023
95ec473
refactoring the acceptance test to use region instead of id
andrew-moldovan Apr 20, 2023
29ffabd
Merge branch 'master' into #405-add-traffic-filter-datasource
andrew-moldovan Apr 20, 2023
7e54a92
more acc test refactoring. Creating a traffic filter resource and the…
andrew-moldovan Apr 20, 2023
43cde9f
Merge branch '#405-add-traffic-filter-datasource' of https://github.c…
andrew-moldovan Apr 20, 2023
943a12a
removing the testing code from the example deployment and adding docs
andrew-moldovan Apr 26, 2023
30efe54
update docs link
andrew-moldovan Apr 27, 2023
bcf4d79
Merge branch 'master' into #405-add-traffic-filter-datasource
andrew-moldovan Apr 27, 2023
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
34 changes: 34 additions & 0 deletions docs/data-sources/ec_trafficfilter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
page_title: "Elastic Cloud: ec_trafficfilter"
description: |-
Filters for available traffic filters that match the given criteria
---

# Data Source: ec_trafficfilter

Use this data source to filter for an existing traffic filter that has been created via one of the provided filters.

## Example Usage

```hcl
data "ec_trafficfilter" "name" {
name = "example-filter"
}

data "ec_trafficfilter" "id" {
id = "41d275439f884ce89359039e53eac516"
}

data "ec_trafficfilter" "region" {
region = "us-east-1"
}
```

## Argument Reference

* `name` (Optional) - The name of the traffic filter. Has to match exactly.
* `id` (Optional) - The id of the traffic filter. Has to match exactly.
* `region` (Optional) - Region where the traffic filter is. For Elastic Cloud Enterprise (ECE) installations, use `"ece-region`.

## Attributes Reference
See https://www.elastic.co/guide/en/cloud/current/definitions.html#TrafficFilterRulesets for the API guide
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
See https://www.elastic.co/guide/en/cloud/current/definitions.html#TrafficFilterRulesets for the API guide
See the [API guide](https://www.elastic.co/guide/en/cloud/current/definitions.html#TrafficFilterRulesets) for the available fields.

Although it would be better to document the actual structure here instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I deliberately didn't document the actual structure here because I felt that wasn't the right choice. Why am I copy/pasting the structure from the link and writing up my own descriptions (or copying them) when that structure could change or new things added or descriptions changed? Then we'd have to remember to update them in these docs as well instead of just having people visit the one source of truth

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just re-read your comment about autogenerating these docs and I totally agree with that!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when that structure could change or new things added or descriptions changed

Because API structure changes aren't automatically reflected here. We'd have to add those fields to the provider as well.

62 changes: 62 additions & 0 deletions ec/acc/datasource_traffic_filter_test.go
Original file line number Diff line number Diff line change
@@ -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"
"os"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

// This test creates a resource of type traffic filter with the randomName
// then it creates a data source that queries for this traffic filter by the id
func TestAccDatasource_trafficfilter(t *testing.T) {
datasourceName := "data.ec_trafficfilter.name"
depCfg := "testdata/datasource_trafficfilter.tf"
randomName := prefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
cfg := fixtureAccTrafficFilterDataSource(t, depCfg, randomName, getRegion())

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProviderFactory,
Steps: []resource.TestStep{
{
Config: cfg,
PreventDiskCleanup: true,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(datasourceName, "rulesets.#", "1"),
resource.TestCheckResourceAttr(datasourceName, "rulesets.0.name", randomName),
resource.TestCheckResourceAttr(datasourceName, "rulesets.0.region", getRegion()),
),
},
},
})
}

func fixtureAccTrafficFilterDataSource(t *testing.T, fileName string, name string, region string) string {
t.Helper()

b, err := os.ReadFile(fileName)
if err != nil {
t.Fatal(err)
}
return fmt.Sprintf(string(b), name, region)
}
13 changes: 13 additions & 0 deletions ec/acc/testdata/datasource_trafficfilter.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
resource "ec_deployment_traffic_filter" "basic" {
name = "%s"
region = "%s"
type = "ip"

rule {
source = "0.0.0.0/0"
}
}

data "ec_trafficfilter" "name" {
id = ec_deployment_traffic_filter.basic.id
}
253 changes: 253 additions & 0 deletions ec/ecdatasource/trafficfilterdatasource/datasource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
// 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 trafficfilterdatasource

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"

"github.com/elastic/cloud-sdk-go/pkg/api"
"github.com/elastic/cloud-sdk-go/pkg/api/deploymentapi/trafficfilterapi"
"github.com/elastic/cloud-sdk-go/pkg/models"
"github.com/elastic/terraform-provider-ec/ec/internal"
)

type DataSource struct {
client *api.API
}

var _ datasource.DataSource = &DataSource{}
var _ datasource.DataSourceWithConfigure = &DataSource{}

func (d *DataSource) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
return tfsdk.Schema{
Attributes: map[string]tfsdk.Attribute{
"name": {
andrew-moldovan marked this conversation as resolved.
Show resolved Hide resolved
Type: types.StringType,
Description: "The name we are filtering on.",
Optional: true,
},
"id": {
Type: types.StringType,
Description: "The id we are filtering on.",
Optional: true,
},
"region": {
Type: types.StringType,
Description: "The region we are filtering on.",
Optional: true,
},

// computed fields
"rulesets": rulesetSchema(),
},
}, nil
}

func rulesetSchema() tfsdk.Attribute {
return tfsdk.Attribute{
Description: "An individual ruleset",
Computed: true,
Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{
"id": {
andrew-moldovan marked this conversation as resolved.
Show resolved Hide resolved
Type: types.StringType,
Description: "The ID of the ruleset",
Computed: true,
},
"name": {
Type: types.StringType,
Description: "The name of the ruleset.",
Computed: true,
},
"description": {
Type: types.StringType,
Description: "The description of the ruleset.",
Computed: true,
},
"region": {
Type: types.StringType,
Description: "The ruleset can be attached only to deployments in the specific region.",
Computed: true,
},
"include_by_default": {
Type: types.BoolType,
Description: "Should the ruleset be automatically included in the new deployments.",
Computed: true,
},
"rules": ruleSchema(),
}),
}
}

func ruleSchema() tfsdk.Attribute {
return tfsdk.Attribute{
Description: "An individual rule",
Computed: true,
Attributes: tfsdk.ListNestedAttributes(map[string]tfsdk.Attribute{
"id": {
Type: types.StringType,
Description: "The ID of the rule",
Computed: true,
},
"source": {
Type: types.StringType,
Description: "Allowed traffic filter source: IP address, CIDR mask, or VPC endpoint ID.",
Computed: true,
},
"description": {
Type: types.StringType,
Description: "The description of the rule.",
Computed: true,
},
}),
}
}

func (d DataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) {
// Prevent panic if the provider has not been configured.
if d.client == nil {
response.Diagnostics.AddError(
"Unconfigured API Client",
"Expected configured API client. Please report this issue to the provider developers.",
)

return
}

var newState modelV0
response.Diagnostics.Append(request.Config.Get(ctx, &newState)...)
if response.Diagnostics.HasError() {
return
}

res, err := trafficfilterapi.List(trafficfilterapi.ListParams{
API: d.client,
})

if err != nil {
response.Diagnostics.AddError(
"Failed retrieving deployment information",
fmt.Sprintf("Failed retrieving deployment information: %s", err),
)
return
}

response.Diagnostics.Append(modelToState(ctx, res, &newState)...)
if response.Diagnostics.HasError() {
return
}

// Finally, set the state
response.Diagnostics.Append(response.State.Set(ctx, newState)...)
}

func (d *DataSource) Metadata(ctx context.Context, request datasource.MetadataRequest, response *datasource.MetadataResponse) {
response.TypeName = request.ProviderTypeName + "_trafficfilter"
}

func (d *DataSource) Configure(ctx context.Context, request datasource.ConfigureRequest, response *datasource.ConfigureResponse) {
client, diags := internal.ConvertProviderData(request.ProviderData)
response.Diagnostics.Append(diags...)
d.client = client
}

type modelV0 struct {
Name types.String `tfsdk:"name"`
Id types.String `tfsdk:"id"`
Region types.String `tfsdk:"region"`
Rulesets types.List `tfsdk:"rulesets"` //< rulesetModelV0
}

type rulesetModelV0 struct {
Id types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
Region types.String `tfsdk:"region"`
IncludeByDefault types.Bool `tfsdk:"include_by_default"`
Rules []ruleModelV0 `tfsdk:"rules"` //< ruleModelV0
}

type ruleModelV0 struct {
Id types.String `tfsdk:"id"`
Source types.String `tfsdk:"source"`
Description types.String `tfsdk:"description"`
}

func modelToState(ctx context.Context, res *models.TrafficFilterRulesets, state *modelV0) diag.Diagnostics {
var diags diag.Diagnostics
var result = make([]rulesetModelV0, 0, len(res.Rulesets))

for _, ruleset := range res.Rulesets {
if *ruleset.Name != state.Name.Value && *ruleset.ID != state.Id.Value && *ruleset.Region != state.Region.Value {
continue
}

m := rulesetModelV0{
Name: types.String{Value: *ruleset.Name},
Id: types.String{Value: *ruleset.ID},
Description: types.String{Value: ruleset.Description},
Region: types.String{Value: *ruleset.Region},
IncludeByDefault: types.Bool{Value: *ruleset.IncludeByDefault},
}

var ruleArray = make([]ruleModelV0, 0, len(ruleset.Rules))
for _, rule := range ruleset.Rules {
t := ruleModelV0{
Id: types.String{Value: rule.ID},
Source: types.String{Value: rule.Source},
Description: types.String{Value: rule.Description},
}
ruleArray = append(ruleArray, t)
}
if len(ruleArray) > 0 {
m.Rules = ruleArray
}

result = append(result, m)
}

diags.Append(tfsdk.ValueFrom(ctx, result, types.ListType{
ElemType: types.ObjectType{
AttrTypes: rulesetAttrTypes(),
},
}, &state.Rulesets)...)

return diags
}

func rulesetAttrTypes() map[string]attr.Type {
return rulesetSchema().Attributes.Type().(types.ListType).ElemType.(types.ObjectType).AttrTypes
}

func rulesetElemType() attr.Type {
return rulesetSchema().Attributes.Type().(types.ListType).ElemType
}

func ruleAttrTypes() map[string]attr.Type {
return ruleSchema().Attributes.Type().(types.ListType).ElemType.(types.ObjectType).AttrTypes
}

func ruleElemType() attr.Type {
return ruleSchema().Attributes.Type().(types.ListType).ElemType
}
Loading