Skip to content

Commit

Permalink
[After Twingate#443] Feature: add support filtering twingate_connecto…
Browse files Browse the repository at this point in the history
…rs datasource (Twingate#463)

* 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

* fix docs

* added optional filters for connectors

* enable tests

* fix test

* revert ci.yml

* fix docs

* fix linter

---------

Co-authored-by: bertekintw <[email protected]>
  • Loading branch information
vmanilo and bertekintw authored Feb 20, 2024
1 parent 9e96e7c commit 4e98b1f
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 57 deletions.
17 changes: 15 additions & 2 deletions docs/data-sources/connectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,31 @@ Connectors provide connectivity to Remote Networks. For more information, see Tw
## Example Usage

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

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

### Optional

- `connectors` (Attributes List) List of Connectors (see [below for nested schema](#nestedatt--connectors))
- `name` (String) Returns only connectors that exactly match this name. If no options are passed it will return all connectors. Only one option can be used at a time.
- `name_contains` (String) Match when the value exist in the name of the connector.
- `name_exclude` (String) Match when the value does not exist in the name of the connector.
- `name_prefix` (String) The name of the connector must start with the value.
- `name_regexp` (String) The regular expression match of the name of the connector.
- `name_suffix` (String) The name of the connector must end with the value.

### Read-Only

- `connectors` (Attributes List) List of Connectors (see [below for nested schema](#nestedatt--connectors))
- `id` (String) The ID of this resource.

<a id="nestedatt--connectors"></a>
Expand Down
9 changes: 8 additions & 1 deletion examples/data-sources/twingate_connectors/data-source.tf
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
data "twingate_connectors" "all" {}
data "twingate_connectors" "all" {
name = "<your connector's name>"
# name_regexp = "<regular expression of connector name>"
# name_contains = "<a string in the connector name>"
# name_exclude = "<your connector's name to exclude>"
# name_prefix = "<prefix of connector name>"
# name_suffix = "<suffix of connector name>"
}
3 changes: 2 additions & 1 deletion twingate/internal/client/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,11 @@ func (client *Client) ReadConnector(ctx context.Context, connectorID string) (*m
return response.ToModel(), nil
}

func (client *Client) ReadConnectors(ctx context.Context) ([]*model.Connector, error) {
func (client *Client) ReadConnectors(ctx context.Context, name, filter string) ([]*model.Connector, error) {
opr := resourceConnector.read()

variables := newVars(
gqlNullable(query.NewConnectorFilterInput(name, filter), "filter"),
cursor(query.CursorConnectors),
pageLimit(client.pageLimit),
)
Expand Down
12 changes: 11 additions & 1 deletion twingate/internal/client/query/connectors-read.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
const CursorConnectors = "connectorsEndCursor"

type ReadConnectors struct {
Connectors `graphql:"connectors(after: $connectorsEndCursor, first: $pageLimit)"`
Connectors `graphql:"connectors(filter: $filter, after: $connectorsEndCursor, first: $pageLimit)"`
}

type Connectors struct {
Expand Down Expand Up @@ -36,3 +36,13 @@ func (c Connectors) ToModel() []*model.Connector {
return edge.Node.ToModel()
})
}

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

func NewConnectorFilterInput(name, filter string) *ConnectorFilterInput {
return &ConnectorFilterInput{
Name: NewStringFilterOperationInput(name, filter),
}
}
93 changes: 85 additions & 8 deletions twingate/internal/provider/datasource/connectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
)

var ErrConnectorsDatasourceShouldSetOneOptionalNameAttribute = 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 = &connectors{}

Expand All @@ -24,8 +26,14 @@ type connectors struct {
}

type connectorsModel struct {
ID types.String `tfsdk:"id"`
Connectors []connectorModel `tfsdk:"connectors"`
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"`
Connectors []connectorModel `tfsdk:"connectors"`
}

func (d *connectors) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
Expand Down Expand Up @@ -59,10 +67,34 @@ func (d *connectors) Schema(ctx context.Context, req datasource.SchemaRequest, r
Description: computedDatasourceIDDescription,
},

attr.Name: schema.StringAttribute{
Optional: true,
Description: "Returns only connectors that exactly match this name. If no options are passed it will return all connectors. 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 connector.",
},
attr.Name + attr.FilterByContains: schema.StringAttribute{
Optional: true,
Description: "Match when the value exist in the name of the connector.",
},
attr.Name + attr.FilterByExclude: schema.StringAttribute{
Optional: true,
Description: "Match when the value does not exist in the name of the connector.",
},
attr.Name + attr.FilterByPrefix: schema.StringAttribute{
Optional: true,
Description: "The name of the connector must start with the value.",
},
attr.Name + attr.FilterBySuffix: schema.StringAttribute{
Optional: true,
Description: "The name of the connector must end with the value.",
},

// computed
attr.Connectors: schema.ListNestedAttribute{
Computed: true,
Optional: true,
Description: "List of Connectors",
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
Expand All @@ -89,18 +121,63 @@ func (d *connectors) Schema(ctx context.Context, req datasource.SchemaRequest, r
}
}

//nolint:cyclop
func (d *connectors) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
connectors, err := d.client.ReadConnectors(ctx)
var data connectorsModel

// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

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, ErrConnectorsDatasourceShouldSetOneOptionalNameAttribute, TwingateResources)

return
}

connectors, err := d.client.ReadConnectors(ctx, name, filter)
if err != nil && !errors.Is(err, client.ErrGraphqlResultIsEmpty) {
addErr(&resp.Diagnostics, err, TwingateConnectors)

return
}

data := connectorsModel{
ID: types.StringValue("all-connectors"),
Connectors: convertConnectorsToTerraform(connectors),
}
data.ID = types.StringValue("all-connectors")
data.Connectors = convertConnectorsToTerraform(connectors)

// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
Expand Down
Loading

0 comments on commit 4e98b1f

Please sign in to comment.