Skip to content

Commit

Permalink
Add Traffic Filter data source (#619)
Browse files Browse the repository at this point in the history
* Adding a basic traffic filter data source that uses the ID of the filter to fetch the name

* Pivoted to using the list api and filtering on the client for the name of the filter we're looking for

* adding the ability to filter on region and id as well and also returning more of the properties of the ruleset, though not yet complete

* got the rules showing up as well 🎉

* fixing some lint

* struggling with tests

* wow a test actually works, quick commit before I screw it up more

* refactored the tests and added a few more to cover the scenarios

* and one more for matching region

* adding a basic acceptance test though I expect it to fail because it's a different id

* refactoring the acceptance test to use region instead of id

* more acc test refactoring. Creating a traffic filter resource and then using that to query in the data source

* removing the testing code from the example deployment and adding docs

* update docs link
  • Loading branch information
andrew-moldovan authored Apr 27, 2023
1 parent a8bb2c1 commit cd25ecf
Show file tree
Hide file tree
Showing 7 changed files with 606 additions and 1 deletion.
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 the [API guide](https://www.elastic.co/guide/en/cloud/current/definitions.html#TrafficFilterRulesets) for the available fields.
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": {
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": {
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

0 comments on commit cd25ecf

Please sign in to comment.