Skip to content

Commit

Permalink
[After Twingate#443] Feature: add filtering for twingate remote netwo…
Browse files Browse the repository at this point in the history
…rks datasource (Twingate#458)

* added new attributes for resources datasource

* added optional name attributes for resources datasource

* added feature branch

* fix test

* remove feature branch

* enable tests

* remove feature branch

* refactore

* updated docs

* update resources datasource: allow to list all resources

* wip

* fix docs

* added optional filters for twingate_remote_networks datasource

* enable test

* enable test

* revert sweeper changes

* run only datasource tests

* fix users basic test

* revert test.sh

* fix users test

* revert ci.yml

* added example for remote_networks datasource

---------

Co-authored-by: bertekintw <[email protected]>
  • Loading branch information
vmanilo and bertekintw authored Feb 20, 2024
1 parent 6f16df1 commit 9e96e7c
Show file tree
Hide file tree
Showing 11 changed files with 271 additions and 16 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ on:
branches:
- main


# Ensures only 1 action runs per PR and previous is canceled on new trigger
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand Down
23 changes: 21 additions & 2 deletions docs/data-sources/remote_networks.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,28 @@ A Remote Network represents a single private network in Twingate that can have o
## Example Usage

```terraform
data "twingate_remote_networks" "all" {}
data "twingate_remote_networks" "all" {
name = "<your network's name>"
# name_regexp = "<regular expression of network name>"
# name_contains = "<a string in the network name>"
# name_exclude = "<your network's name to exclude>"
# name_prefix = "<prefix of network name>"
# name_suffix = "<suffix of network name>"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Optional

- `name` (String) Returns only remote networks that exactly match this name. If no options are passed it will return all remote networks. Only one option can be used at a time.
- `name_contains` (String) Match when the value exist in the name of the remote network.
- `name_exclude` (String) Match when the value does not exist in the name of the remote network.
- `name_prefix` (String) The name of the remote network must start with the value.
- `name_regexp` (String) The regular expression match of the name of the remote network.
- `name_suffix` (String) The name of the remote network must end with the value.

### Read-Only

- `id` (String) The ID of this resource.
Expand All @@ -27,8 +43,11 @@ data "twingate_remote_networks" "all" {}
<a id="nestedatt--remote_networks"></a>
### Nested Schema for `remote_networks`

Optional:

- `name` (String) The name of the Remote Network.

Read-Only:

- `id` (String) The ID of the Remote Network.
- `location` (String) The location of the Remote Network. Must be one of the following: AWS, AZURE, GOOGLE_CLOUD, ON_PREMISE, OTHER.
- `name` (String) The name of the Remote Network
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
data "twingate_remote_networks" "all" {}
data "twingate_remote_networks" "all" {
name = "<your network's name>"
# name_regexp = "<regular expression of network name>"
# name_contains = "<a string in the network name>"
# name_exclude = "<your network's name to exclude>"
# name_prefix = "<prefix of network name>"
# name_suffix = "<suffix of network name>"
}
12 changes: 11 additions & 1 deletion twingate/internal/client/query/remote-networks-read.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
const CursorRemoteNetworks = "remoteNetworksEndCursor"

type ReadRemoteNetworks struct {
RemoteNetworks `graphql:"remoteNetworks(after: $remoteNetworksEndCursor, first: $pageLimit)"`
RemoteNetworks `graphql:"remoteNetworks(filter: $filter, after: $remoteNetworksEndCursor, first: $pageLimit)"`
}

func (q ReadRemoteNetworks) IsEmpty() bool {
Expand All @@ -28,3 +28,13 @@ func (r RemoteNetworks) ToModel() []*model.RemoteNetwork {
return edge.Node.ToModel()
})
}

type RemoteNetworkFilterInput struct {
Name *StringFilterOperationInput `json:"name"`
}

func NewRemoteNetworkFilterInput(name, filter string) *RemoteNetworkFilterInput {
return &RemoteNetworkFilterInput{
Name: NewStringFilterOperationInput(name, filter),
}
}
3 changes: 2 additions & 1 deletion twingate/internal/client/remote-network.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ func (client *Client) CreateRemoteNetwork(ctx context.Context, req *model.Remote
return response.ToModel(), nil
}

func (client *Client) ReadRemoteNetworks(ctx context.Context) ([]*model.RemoteNetwork, error) {
func (client *Client) ReadRemoteNetworks(ctx context.Context, name, filter string) ([]*model.RemoteNetwork, error) {
opr := resourceRemoteNetwork.read()

variables := newVars(
gqlNullable(query.NewRemoteNetworkFilterInput(name, filter), "filter"),
cursor(query.CursorRemoteNetworks),
pageLimit(client.pageLimit),
)
Expand Down
77 changes: 74 additions & 3 deletions twingate/internal/provider/datasource/remote-networks.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
)

var ErrRemoteNetworksDatasourceShouldSetOneOptionalNameAttribute = errors.New("Only one of name, name_regex, name_contains, name_exclude, name_prefix or name_suffix must be set.")

// Ensure the implementation satisfies the desired interfaces.
var _ datasource.DataSource = &remoteNetworks{}

Expand All @@ -27,6 +29,12 @@ type remoteNetworks struct {

type remoteNetworksModel struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
NameRegexp types.String `tfsdk:"name_regexp"`
NameContains types.String `tfsdk:"name_contains"`
NameExclude types.String `tfsdk:"name_exclude"`
NamePrefix types.String `tfsdk:"name_prefix"`
NameSuffix types.String `tfsdk:"name_suffix"`
RemoteNetworks []remoteNetworkModel `tfsdk:"remote_networks"`
}

Expand Down Expand Up @@ -61,6 +69,31 @@ func (d *remoteNetworks) Schema(ctx context.Context, req datasource.SchemaReques
Description: computedDatasourceIDDescription,
},

attr.Name: schema.StringAttribute{
Optional: true,
Description: "Returns only remote networks that exactly match this name. If no options are passed it will return all remote networks. Only one option can be used at a time.",
},
attr.Name + attr.FilterByRegexp: schema.StringAttribute{
Optional: true,
Description: "The regular expression match of the name of the remote network.",
},
attr.Name + attr.FilterByContains: schema.StringAttribute{
Optional: true,
Description: "Match when the value exist in the name of the remote network.",
},
attr.Name + attr.FilterByExclude: schema.StringAttribute{
Optional: true,
Description: "Match when the value does not exist in the name of the remote network.",
},
attr.Name + attr.FilterByPrefix: schema.StringAttribute{
Optional: true,
Description: "The name of the remote network must start with the value.",
},
attr.Name + attr.FilterBySuffix: schema.StringAttribute{
Optional: true,
Description: "The name of the remote network must end with the value.",
},

attr.RemoteNetworks: schema.ListNestedAttribute{
Computed: true,
Description: "List of Remote Networks",
Expand All @@ -71,8 +104,8 @@ func (d *remoteNetworks) Schema(ctx context.Context, req datasource.SchemaReques
Description: "The ID of the Remote Network.",
},
attr.Name: schema.StringAttribute{
Computed: true,
Description: "The name of the Remote Network",
Optional: true,
Description: "The name of the Remote Network.",
},
attr.Location: schema.StringAttribute{
Computed: true,
Expand All @@ -85,6 +118,7 @@ func (d *remoteNetworks) Schema(ctx context.Context, req datasource.SchemaReques
}
}

//nolint:cyclop
func (d *remoteNetworks) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data remoteNetworksModel

Expand All @@ -95,7 +129,44 @@ func (d *remoteNetworks) Read(ctx context.Context, req datasource.ReadRequest, r
return
}

networks, err := d.client.ReadRemoteNetworks(ctx)
var name, filter string

if data.Name.ValueString() != "" {
name = data.Name.ValueString()
}

if data.NameRegexp.ValueString() != "" {
name = data.NameRegexp.ValueString()
filter = attr.FilterByRegexp
}

if data.NameContains.ValueString() != "" {
name = data.NameContains.ValueString()
filter = attr.FilterByContains
}

if data.NameExclude.ValueString() != "" {
name = data.NameExclude.ValueString()
filter = attr.FilterByExclude
}

if data.NamePrefix.ValueString() != "" {
name = data.NamePrefix.ValueString()
filter = attr.FilterByPrefix
}

if data.NameSuffix.ValueString() != "" {
name = data.NameSuffix.ValueString()
filter = attr.FilterBySuffix
}

if countOptionalAttributes(data.Name, data.NameRegexp, data.NameContains, data.NameExclude, data.NamePrefix, data.NameSuffix) > 1 {
addErr(&resp.Diagnostics, ErrRemoteNetworksDatasourceShouldSetOneOptionalNameAttribute, TwingateRemoteNetworks)

return
}

networks, err := d.client.ReadRemoteNetworks(ctx, name, filter)
if err != nil && !errors.Is(err, client.ErrGraphqlResultIsEmpty) {
addErr(&resp.Diagnostics, err, TwingateRemoteNetworks)

Expand Down
141 changes: 140 additions & 1 deletion twingate/internal/test/acctests/datasource/remote-networks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package datasource

import (
"fmt"
"github.com/Twingate/terraform-provider-twingate/twingate/internal/attr"
"testing"

"github.com/Twingate/terraform-provider-twingate/twingate/internal/test"
Expand All @@ -10,6 +11,11 @@ import (
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

var (
remoteNetworksLen = attr.Len(attr.RemoteNetworks)
remoteNetworkNamePath = attr.Path(attr.RemoteNetworks, attr.Name)
)

func TestAccDatasourceTwingateRemoteNetworks_read(t *testing.T) {
t.Run("Test Twingate Datasource : Acc Remote Networks Read", func(t *testing.T) {
acctests.SetPageLimit(1)
Expand Down Expand Up @@ -49,7 +55,140 @@ func testDatasourceTwingateRemoteNetworks2(networkName1, networkName2, prefix st
}
output "test_networks" {
value = [for n in [for net in data.twingate_remote_networks.all : net if can(net.*.name)][0] : n if length(regexall("%s.*", n.name)) > 0]
value = [for n in [for net in data.twingate_remote_networks.all : net if can(net.*.name)][6] : n if length(regexall("%s.*", n.name)) > 0]
}
`, networkName1, networkName2, prefix)
}

func TestAccDatasourceTwingateRemoteNetworksFilterByName(t *testing.T) {
t.Parallel()

resourceName := test.RandomResourceName()
networkName := test.RandomName()
theDatasource := "data.twingate_remote_networks." + resourceName

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: acctests.ProviderFactories,
PreCheck: func() { acctests.PreCheck(t) },
CheckDestroy: acctests.CheckTwingateResourceDestroy,
Steps: []resource.TestStep{
{
Config: testDatasourceTwingateRemoteNetworksFilter(resourceName, networkName, networkName, ""),
Check: acctests.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(theDatasource, remoteNetworksLen, "1"),
resource.TestCheckResourceAttr(theDatasource, remoteNetworkNamePath, networkName),
),
},
},
})
}

func testDatasourceTwingateRemoteNetworksFilter(resourceName, networkName, name, filter string) string {
return fmt.Sprintf(`
resource "twingate_remote_network" "%[1]s" {
name = "%[2]s"
}
data "twingate_remote_networks" "%[1]s" {
name%[3]s = "%[4]s"
depends_on = [twingate_remote_network.%[1]s]
}
`, resourceName, networkName, filter, name)
}

func TestAccDatasourceTwingateRemoteNetworksFilterByPrefix(t *testing.T) {
t.Parallel()

prefix := acctest.RandString(5)
resourceName := test.RandomResourceName()
networkName := prefix + "_" + test.RandomName()
theDatasource := "data.twingate_remote_networks." + resourceName

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: acctests.ProviderFactories,
PreCheck: func() { acctests.PreCheck(t) },
CheckDestroy: acctests.CheckTwingateResourceDestroy,
Steps: []resource.TestStep{
{
Config: testDatasourceTwingateRemoteNetworksFilter(resourceName, networkName, prefix, attr.FilterByPrefix),
Check: acctests.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(theDatasource, remoteNetworksLen, "1"),
resource.TestCheckResourceAttr(theDatasource, remoteNetworkNamePath, networkName),
),
},
},
})
}

func TestAccDatasourceTwingateRemoteNetworksFilterBySuffix(t *testing.T) {
t.Parallel()

suffix := acctest.RandString(5)
resourceName := test.RandomResourceName()
networkName := test.RandomName() + "_" + suffix
theDatasource := "data.twingate_remote_networks." + resourceName

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: acctests.ProviderFactories,
PreCheck: func() { acctests.PreCheck(t) },
CheckDestroy: acctests.CheckTwingateResourceDestroy,
Steps: []resource.TestStep{
{
Config: testDatasourceTwingateRemoteNetworksFilter(resourceName, networkName, suffix, attr.FilterBySuffix),
Check: acctests.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(theDatasource, remoteNetworksLen, "1"),
resource.TestCheckResourceAttr(theDatasource, remoteNetworkNamePath, networkName),
),
},
},
})
}

func TestAccDatasourceTwingateRemoteNetworksFilterByContains(t *testing.T) {
t.Parallel()

randString := acctest.RandString(5)
resourceName := test.RandomResourceName()
networkName := test.RandomName() + "_" + randString + "_" + acctest.RandString(5)
theDatasource := "data.twingate_remote_networks." + resourceName

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: acctests.ProviderFactories,
PreCheck: func() { acctests.PreCheck(t) },
CheckDestroy: acctests.CheckTwingateResourceDestroy,
Steps: []resource.TestStep{
{
Config: testDatasourceTwingateRemoteNetworksFilter(resourceName, networkName, randString, attr.FilterByContains),
Check: acctests.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(theDatasource, remoteNetworksLen, "1"),
resource.TestCheckResourceAttr(theDatasource, remoteNetworkNamePath, networkName),
),
},
},
})
}

func TestAccDatasourceTwingateRemoteNetworksFilterByRegexp(t *testing.T) {
t.Parallel()

randString := acctest.RandString(5)
resourceName := test.RandomResourceName()
networkName := test.RandomName() + "_" + randString + "_" + acctest.RandString(5)
theDatasource := "data.twingate_remote_networks." + resourceName

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: acctests.ProviderFactories,
PreCheck: func() { acctests.PreCheck(t) },
CheckDestroy: acctests.CheckTwingateResourceDestroy,
Steps: []resource.TestStep{
{
Config: testDatasourceTwingateRemoteNetworksFilter(resourceName, networkName, ".*_"+randString+"_.*", attr.FilterByRegexp),
Check: acctests.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(theDatasource, remoteNetworksLen, "1"),
resource.TestCheckResourceAttr(theDatasource, remoteNetworkNamePath, networkName),
),
},
},
})
}
Loading

0 comments on commit 9e96e7c

Please sign in to comment.