Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into feature/convert-res…
Browse files Browse the repository at this point in the history
…ource-object
  • Loading branch information
vmanilo committed Dec 13, 2023
2 parents 6dd03bf + 3fcbb68 commit 7288a24
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 15 deletions.
3 changes: 3 additions & 0 deletions docs/resources/resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ resource "twingate_resource" "resource" {
group_ids = [twingate_group.aws.id]
service_account_ids = [twingate_service_account.github_actions_prod.id]
}
is_active = true
}
```

Expand All @@ -72,6 +74,7 @@ resource "twingate_resource" "resource" {

- `access` (Block List) Restrict access to certain groups or service accounts (see [below for nested schema](#nestedblock--access))
- `alias` (String) Set a DNS alias address for the Resource. Must be a DNS-valid name string.
- `is_active` (Boolean) Set the resource as active or inactive. Default is `true`.
- `is_authoritative` (Boolean) Determines whether assignments in the access block will override any existing assignments. Default is `true`. If set to `false`, assignments made outside of Terraform will be ignored.
- `is_browser_shortcut_enabled` (Boolean) Controls whether an "Open in Browser" shortcut will be shown for this Resource in the Twingate Client. Default is `false`.
- `is_visible` (Boolean) Controls whether this Resource will be visible in the main Resource list in the Twingate Client. Default is `true`.
Expand Down
2 changes: 2 additions & 0 deletions examples/resources/twingate_resource/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,7 @@ resource "twingate_resource" "resource" {
group_ids = [twingate_group.aws.id]
service_account_ids = [twingate_service_account.github_actions_prod.id]
}

is_active = true
}

1 change: 1 addition & 0 deletions golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ linters-settings:
- github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier.Set
- github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier.Bool
- github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier.String
- github.com/hashicorp/terraform-plugin-testing/plancheck.PlanCheck
errcheck:
check-type-assertions: false
check-blank: false
Expand Down
2 changes: 1 addition & 1 deletion twingate/internal/client/query/resource-update.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package query

type UpdateResource struct {
ResourceEntityResponse `graphql:"resourceUpdate(id: $id, name: $name, address: $address, remoteNetworkId: $remoteNetworkId, protocols: $protocols, isVisible: $isVisible, isBrowserShortcutEnabled: $isBrowserShortcutEnabled, alias: $alias, securityPolicyId: $securityPolicyId)"`
ResourceEntityResponse `graphql:"resourceUpdate(id: $id, name: $name, address: $address, remoteNetworkId: $remoteNetworkId, protocols: $protocols, isVisible: $isVisible, isBrowserShortcutEnabled: $isBrowserShortcutEnabled, alias: $alias, securityPolicyId: $securityPolicyId, isActive: $isActive)"`
}

func (q UpdateResource) IsEmpty() bool {
Expand Down
1 change: 1 addition & 0 deletions twingate/internal/client/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ func (client *Client) UpdateResource(ctx context.Context, input *model.Resource)
gqlVar(input.Name, "name"),
gqlVar(input.Address, "address"),
gqlVar(newProtocolsInput(input.Protocols), "protocols"),
gqlVar(input.IsActive, "isActive"),
gqlNullable(input.IsVisible, "isVisible"),
gqlNullable(input.IsBrowserShortcutEnabled, "isBrowserShortcutEnabled"),
gqlNullable(input.Alias, "alias"),
Expand Down
44 changes: 30 additions & 14 deletions twingate/internal/provider/resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type resourceModel struct {
IsAuthoritative types.Bool `tfsdk:"is_authoritative"`
Protocols types.Object `tfsdk:"protocols"`
Access types.List `tfsdk:"access"`
IsActive types.Bool `tfsdk:"is_active"`
IsVisible types.Bool `tfsdk:"is_visible"`
IsBrowserShortcutEnabled types.Bool `tfsdk:"is_browser_shortcut_enabled"`
Alias types.String `tfsdk:"alias"`
Expand All @@ -78,6 +79,7 @@ type resourceModelV0 struct {
IsAuthoritative types.Bool `tfsdk:"is_authoritative"`
Protocols types.List `tfsdk:"protocols"`
Access types.List `tfsdk:"access"`
IsActive types.Bool `tfsdk:"is_active"`
IsVisible types.Bool `tfsdk:"is_visible"`
IsBrowserShortcutEnabled types.Bool `tfsdk:"is_browser_shortcut_enabled"`
Alias types.String `tfsdk:"alias"`
Expand Down Expand Up @@ -130,6 +132,7 @@ func (r *twingateResource) ImportState(ctx context.Context, req resource.ImportS
}
}

//nolint:funlen
func (r *twingateResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Version: 1,
Expand All @@ -148,6 +151,12 @@ func (r *twingateResource) Schema(_ context.Context, _ resource.SchemaRequest, r
Description: "Remote Network ID where the Resource lives",
},
// optional
attr.IsActive: schema.BoolAttribute{
Optional: true,
Computed: true,
Description: "Set the resource as active or inactive. Default is `true`.",
Default: booldefault.StaticBool(true),
},
attr.IsAuthoritative: schema.BoolAttribute{
Optional: true,
Computed: true,
Expand Down Expand Up @@ -210,6 +219,10 @@ func (r *twingateResource) UpgradeState(ctx context.Context) map[int64]resource.
attr.RemoteNetworkID: schema.StringAttribute{
Required: true,
},
attr.IsActive: schema.BoolAttribute{
Optional: true,
Computed: true,
},
attr.IsAuthoritative: schema.BoolAttribute{
Optional: true,
Computed: true,
Expand Down Expand Up @@ -340,6 +353,7 @@ func (r *twingateResource) UpgradeState(ctx context.Context) map[int64]resource.
RemoteNetworkID: priorState.RemoteNetworkID,
Protocols: protocolsState,
Access: priorState.Access,
IsActive: priorState.IsActive,
}

if !priorState.IsAuthoritative.IsNull() {
Expand Down Expand Up @@ -561,6 +575,19 @@ func (r *twingateResource) Create(ctx context.Context, req resource.CreateReques
return
}

if !input.IsActive {
if err := r.client.UpdateResourceActiveState(ctx, &model.Resource{
ID: resource.ID,
IsActive: false,
}); err != nil {
addErr(&resp.Diagnostics, err, operationCreate, TwingateResource)

return
}

resource.IsActive = false
}

r.helper(ctx, resource, &plan, &plan, &resp.State, &resp.Diagnostics, err, operationCreate)
}

Expand Down Expand Up @@ -608,6 +635,7 @@ func convertResource(plan *resourceModel) (*model.Resource, error) {
Protocols: protocols,
Groups: groupIDs,
ServiceAccounts: serviceAccountIDs,
IsActive: plan.IsActive.ValueBool(),
IsAuthoritative: convertAuthoritativeFlag(plan.IsAuthoritative),
Alias: getOptionalString(plan.Alias),
IsVisible: getOptionalBool(plan.IsVisible),
Expand Down Expand Up @@ -982,6 +1010,7 @@ func isResourceChanged(plan, state *resourceModel) bool {
!plan.Name.Equal(state.Name) ||
!plan.Address.Equal(state.Address) ||
!equalProtocolsState(&plan.Protocols, &state.Protocols) ||
!plan.IsActive.Equal(state.IsActive) ||
!plan.IsVisible.Equal(state.IsVisible) ||
!plan.IsBrowserShortcutEnabled.Equal(state.IsBrowserShortcutEnabled) ||
!plan.Alias.Equal(state.Alias) ||
Expand Down Expand Up @@ -1069,20 +1098,6 @@ func (r *twingateResource) helper(ctx context.Context, resource *model.Resource,
resource.Protocols = model.DefaultProtocols()
}

if !resource.IsActive {
// fix set active state for the resource on `terraform apply`
err = r.client.UpdateResourceActiveState(ctx, &model.Resource{
ID: resource.ID,
IsActive: true,
})

if err != nil {
addErr(diagnostics, err, operationUpdate, TwingateResource)

return
}
}

if !resource.IsAuthoritative {
resource.Groups = setIntersection(getAccessAttribute(reference.Access, attr.GroupIDs), resource.Groups)
resource.ServiceAccounts = setIntersection(getAccessAttribute(reference.Access, attr.ServiceAccountIDs), resource.ServiceAccounts)
Expand All @@ -1103,6 +1118,7 @@ func setState(ctx context.Context, state, reference *resourceModel, resource *mo
state.Name = types.StringValue(resource.Name)
state.RemoteNetworkID = types.StringValue(resource.RemoteNetworkID)
state.Address = types.StringValue(resource.Address)
state.IsActive = types.BoolValue(resource.IsActive)
state.IsAuthoritative = types.BoolValue(resource.IsAuthoritative)
state.SecurityPolicyID = types.StringPointerValue(resource.SecurityPolicyID)

Expand Down
62 changes: 62 additions & 0 deletions twingate/internal/test/acctests/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import (
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
sdk "github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/plancheck"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
)

var (
Expand Down Expand Up @@ -323,6 +325,66 @@ func CheckTwingateResourceActiveState(resourceName string, expectedActiveState b
}
}

type checkResourceActiveState struct {
resourceAddress string
expectedActiveState bool
}

// CheckPlan implements the plan check logic.
func (e checkResourceActiveState) CheckPlan(ctx context.Context, req plancheck.CheckPlanRequest, resp *plancheck.CheckPlanResponse) {
var resourceID string

for _, rc := range req.Plan.ResourceChanges {
if e.resourceAddress != rc.Address {
continue
}

result, err := tfjsonpath.Traverse(rc.Change.Before, tfjsonpath.New(attr.ID))
if err != nil {
resp.Error = err

return
}

resultID, ok := result.(string)
if !ok {
resp.Error = fmt.Errorf("invalid path: the path value cannot be asserted as string") //nolint:goerr113

return
}

resourceID = resultID

break
}

if resourceID == "" {
resp.Error = fmt.Errorf("%s - Resource not found in plan ResourceChanges", e.resourceAddress) //nolint:goerr113

return
}

res, err := providerClient.ReadResource(ctx, resourceID)
if err != nil {
resp.Error = fmt.Errorf("failed to read resource: %w", err)

return
}

if res.IsActive != e.expectedActiveState {
resp.Error = fmt.Errorf("expected active state %v, got %v", e.expectedActiveState, res.IsActive) //nolint:goerr113

return
}
}

func CheckResourceActiveState(resourceAddress string, activeState bool) plancheck.PlanCheck {
return checkResourceActiveState{
resourceAddress: resourceAddress,
expectedActiveState: activeState,
}
}

func CheckImportState(attributes map[string]string) func(data []*terraform.InstanceState) error {
return func(data []*terraform.InstanceState) error {
if len(data) != 1 {
Expand Down
117 changes: 117 additions & 0 deletions twingate/internal/test/acctests/resource/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/Twingate/terraform-provider-twingate/twingate/internal/test"
"github.com/Twingate/terraform-provider-twingate/twingate/internal/test/acctests"
sdk "github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/plancheck"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -714,6 +715,8 @@ func TestAccTwingateResourceSetActiveStateOnUpdate(t *testing.T) {
acctests.WaitTestFunc(),
acctests.CheckTwingateResourceActiveState(theResource, false),
),
// provider noticed drift and tried to change it to true
ExpectNonEmptyPlan: true,
},
{
Config: createResourceOnlyWithNetwork(terraformResourceName, remoteNetworkName, resourceName),
Expand Down Expand Up @@ -2907,3 +2910,117 @@ func TestAccTwingateResourceSecurityPolicy(t *testing.T) {
},
})
}

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

resourceName := test.RandomResourceName()
theResource := acctests.TerraformResource(resourceName)
remoteNetworkName := test.RandomName()

sdk.Test(t, sdk.TestCase{
ProtoV6ProviderFactories: acctests.ProviderFactories,
PreCheck: func() { acctests.PreCheck(t) },
CheckDestroy: acctests.CheckTwingateResourceDestroy,
Steps: []sdk.TestStep{
{
Config: createResourceWithIsActiveFlag(remoteNetworkName, resourceName, false),
Check: acctests.ComposeTestCheckFunc(
sdk.TestCheckResourceAttr(theResource, attr.IsActive, "false"),
acctests.CheckTwingateResourceActiveState(theResource, false),
),
},
},
})
}

func createResourceWithIsActiveFlag(networkName, resourceName string, isActive bool) string {
return fmt.Sprintf(`
resource "twingate_remote_network" "%[1]s" {
name = "%[1]s"
}
resource "twingate_resource" "%[2]s" {
name = "%[2]s"
address = "acc-test.com"
remote_network_id = twingate_remote_network.%[1]s.id
is_active = %[3]v
}
`, networkName, resourceName, isActive)
}

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

resourceName := test.RandomResourceName()
theResource := acctests.TerraformResource(resourceName)
remoteNetworkName := test.RandomName()

sdk.Test(t, sdk.TestCase{
ProtoV6ProviderFactories: acctests.ProviderFactories,
PreCheck: func() { acctests.PreCheck(t) },
CheckDestroy: acctests.CheckTwingateResourceDestroy,
Steps: []sdk.TestStep{
{
Config: createResourceWithIsActiveFlag(remoteNetworkName, resourceName, true),
Check: acctests.ComposeTestCheckFunc(
sdk.TestCheckResourceAttr(theResource, attr.IsActive, "true"),
),
},
{
Config: createResourceWithIsActiveFlag(remoteNetworkName, resourceName, false),
Check: acctests.ComposeTestCheckFunc(
sdk.TestCheckResourceAttr(theResource, attr.IsActive, "false"),
acctests.CheckTwingateResourceActiveState(theResource, false),
),
},
},
})
}

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

resourceName := test.RandomResourceName()
theResource := acctests.TerraformResource(resourceName)
remoteNetworkName := test.RandomName()

sdk.Test(t, sdk.TestCase{
ProtoV6ProviderFactories: acctests.ProviderFactories,
PreCheck: func() { acctests.PreCheck(t) },
CheckDestroy: acctests.CheckTwingateResourceDestroy,
Steps: []sdk.TestStep{
{
Config: createResource(remoteNetworkName, resourceName),
},
{
RefreshState: true,
Check: acctests.ComposeTestCheckFunc(
acctests.DeactivateTwingateResource(theResource),
),
},
{
Config: createResource(remoteNetworkName, resourceName),
ConfigPlanChecks: sdk.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectNonEmptyPlan(),
plancheck.ExpectResourceAction(theResource, plancheck.ResourceActionUpdate),
acctests.CheckResourceActiveState(theResource, false),
},
},
},
},
})
}

func createResource(networkName, resourceName string) string {
return fmt.Sprintf(`
resource "twingate_remote_network" "%[1]s" {
name = "%[1]s"
}
resource "twingate_resource" "%[2]s" {
name = "%[2]s"
address = "acc-test.com"
remote_network_id = twingate_remote_network.%[1]s.id
}
`, networkName, resourceName)
}

0 comments on commit 7288a24

Please sign in to comment.