Skip to content

Commit

Permalink
added security_policy_id to resource definition
Browse files Browse the repository at this point in the history
  • Loading branch information
vmanilo committed Nov 10, 2023
1 parent 6001536 commit 489c394
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 17 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
pull_request:
branches:
- main
- feature/add-security_policy-to-resource
paths-ignore:
- 'README.md'

Expand All @@ -15,6 +16,7 @@ on:
- 'README.md'
branches:
- main
- feature/add-security_policy-to-resource

# Ensures only 1 action runs per PR and previous is canceled on new trigger
concurrency:
Expand Down Expand Up @@ -118,7 +120,7 @@ jobs:
name: Matrix Acceptance Tests
needs: build
runs-on: ubuntu-latest
if: "!github.event.pull_request.head.repo.fork"
# if: "!github.event.pull_request.head.repo.fork"
timeout-minutes: 15
strategy:
fail-fast: false
Expand Down Expand Up @@ -169,7 +171,7 @@ jobs:

cleanup:
name: Cleanup
if: "!github.event.pull_request.head.repo.fork"
# if: "!github.event.pull_request.head.repo.fork"
needs: tests-acceptance
runs-on: ubuntu-latest
timeout-minutes: 15
Expand Down
1 change: 1 addition & 0 deletions docs/resources/resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ resource "twingate_resource" "resource" {
- `is_browser_shortcut_enabled` (Boolean) Controls whether an "Open in Browser" shortcut will be shown for this Resource in the Twingate Client.
- `is_visible` (Boolean) Controls whether this Resource will be visible in the main Resource list in the Twingate Client.
- `protocols` (Block List, Max: 1) Restrict access to certain protocols and ports. By default or when this argument is not defined, there is no restriction, and all protocols and ports are allowed. (see [below for nested schema](#nestedblock--protocols))
- `security_policy_id` (String) The ID of a `twingate_security_policy` to set as this Resource's Security Policy.

### Read-Only

Expand Down
2 changes: 1 addition & 1 deletion twingate/internal/client/query/resource-create.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package query

type CreateResource struct {
ResourceEntityResponse `graphql:"resourceCreate(name: $name, address: $address, remoteNetworkId: $remoteNetworkId, groupIds: $groupIds, protocols: $protocols, isVisible: $isVisible, isBrowserShortcutEnabled: $isBrowserShortcutEnabled, alias: $alias)"`
ResourceEntityResponse `graphql:"resourceCreate(name: $name, address: $address, remoteNetworkId: $remoteNetworkId, groupIds: $groupIds, protocols: $protocols, isVisible: $isVisible, isBrowserShortcutEnabled: $isBrowserShortcutEnabled, alias: $alias, securityPolicyId: $securityPolicyId)"`
}

func (q CreateResource) IsEmpty() bool {
Expand Down
7 changes: 7 additions & 0 deletions twingate/internal/client/query/resource-read.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type ResourceNode struct {
IsVisible bool
IsBrowserShortcutEnabled bool
Alias string
SecurityPolicy *gqlSecurityPolicy
}

type Protocols struct {
Expand Down Expand Up @@ -90,6 +91,11 @@ func (r gqlResource) ToModel() *model.Resource {
}

func (r ResourceNode) ToModel() *model.Resource {
var securityPolicy string
if r.SecurityPolicy != nil {
securityPolicy = string(r.SecurityPolicy.ID)
}

return &model.Resource{
ID: string(r.ID),
Name: r.Name,
Expand All @@ -100,6 +106,7 @@ func (r ResourceNode) ToModel() *model.Resource {
IsVisible: &r.IsVisible,
IsBrowserShortcutEnabled: &r.IsBrowserShortcutEnabled,
Alias: optionalString(r.Alias),
SecurityPolicyID: optionalString(securityPolicy),
}
}

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)"`
ResourceEntityResponse `graphql:"resourceUpdate(id: $id, name: $name, address: $address, remoteNetworkId: $remoteNetworkId, protocols: $protocols, isVisible: $isVisible, isBrowserShortcutEnabled: $isBrowserShortcutEnabled, alias: $alias, securityPolicyId: $securityPolicyId)"`
}

func (q UpdateResource) IsEmpty() bool {
Expand Down
10 changes: 10 additions & 0 deletions twingate/internal/client/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func (client *Client) CreateResource(ctx context.Context, input *model.Resource)
gqlNullable(input.IsVisible, "isVisible"),
gqlNullable(input.IsBrowserShortcutEnabled, "isBrowserShortcutEnabled"),
gqlNullable(input.Alias, "alias"),
gqlNullableID(input.SecurityPolicyID, "securityPolicyId"),
cursor(query.CursorAccess),
pageLimit(client.pageLimit),
)
Expand All @@ -92,6 +93,10 @@ func (client *Client) CreateResource(ctx context.Context, input *model.Resource)
resource.IsBrowserShortcutEnabled = nil
}

if input.SecurityPolicyID == nil {
resource.SecurityPolicyID = nil
}

return resource, nil
}

Expand Down Expand Up @@ -180,6 +185,7 @@ func (client *Client) UpdateResource(ctx context.Context, input *model.Resource)
gqlNullable(input.IsVisible, "isVisible"),
gqlNullable(input.IsBrowserShortcutEnabled, "isBrowserShortcutEnabled"),
gqlNullable(input.Alias, "alias"),
gqlNullableID(input.SecurityPolicyID, "securityPolicyId"),
cursor(query.CursorAccess),
pageLimit(client.pageLimit),
)
Expand All @@ -204,6 +210,10 @@ func (client *Client) UpdateResource(ctx context.Context, input *model.Resource)
resource.IsBrowserShortcutEnabled = nil
}

if input.SecurityPolicyID == nil {
resource.SecurityPolicyID = nil
}

return resource, nil
}

Expand Down
5 changes: 5 additions & 0 deletions twingate/internal/client/variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,18 @@ func getValue(val any) any {
}
}

//nolint:unparam
func gqlNullableID(val interface{}, name string) gqlVarOption {
return func(values map[string]interface{}) map[string]interface{} {
var (
gqlValue interface{}
defaultID *graphql.ID
)

if value, ok := val.(*string); ok && value != nil {
val = *value
}

if isZeroValue(val) {
gqlValue = defaultID
} else {
Expand Down
1 change: 1 addition & 0 deletions twingate/internal/model/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Resource struct {
IsVisible *bool
IsBrowserShortcutEnabled *bool
Alias *string
SecurityPolicyID *string
}

func (r Resource) AccessToTerraform() []interface{} {
Expand Down
46 changes: 33 additions & 13 deletions twingate/internal/provider/resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ func Resource() *schema.Resource { //nolint:funlen
Description: "Restrict access to certain groups or service accounts",
Elem: accessSchema,
},
attr.SecurityPolicyID: {
Type: schema.TypeString,
Optional: true,
Description: "The ID of a `twingate_security_policy` to set as this Resource's Security Policy.",
},
// computed
attr.IsVisible: {
Type: schema.TypeBool,
Expand Down Expand Up @@ -222,6 +227,7 @@ func resourceUpdate(ctx context.Context, resourceData *schema.ResourceData, meta
attr.IsVisible,
attr.IsBrowserShortcutEnabled,
attr.Alias,
attr.SecurityPolicyID,
) {
resource, err = client.UpdateResource(ctx, resource)
} else {
Expand All @@ -239,9 +245,15 @@ func resourceUpdate(ctx context.Context, resourceData *schema.ResourceData, meta
func resourceRead(ctx context.Context, resourceData *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*client.Client)

securityPolicyID := resourceData.Get(attr.SecurityPolicyID)

resource, err := client.ReadResource(ctx, resourceData.Id())
if resource != nil {
resource.IsAuthoritative = convertAuthoritativeFlagLegacy(resourceData)

if securityPolicyID == "" {
resource.SecurityPolicyID = nil
}
}

return resourceResourceReadHelper(ctx, client, resourceData, resource, err)
Expand Down Expand Up @@ -348,13 +360,12 @@ func readDiagnostics(resourceData *schema.ResourceData, resource *model.Resource
}
}

var alias interface{}
if resource.Alias != nil {
alias = *resource.Alias
if err := resourceData.Set(attr.Alias, resource.Alias); err != nil {
return ErrAttributeSet(err, attr.Alias)
}

if err := resourceData.Set(attr.Alias, alias); err != nil {
return ErrAttributeSet(err, attr.Alias)
if err := resourceData.Set(attr.SecurityPolicyID, resource.SecurityPolicyID); err != nil {
return ErrAttributeSet(err, attr.SecurityPolicyID)
}

return nil
Expand Down Expand Up @@ -483,14 +494,15 @@ func convertResource(data *schema.ResourceData) (*model.Resource, error) {

groups, serviceAccounts := convertAccess(data)
res := &model.Resource{
Name: data.Get(attr.Name).(string),
RemoteNetworkID: data.Get(attr.RemoteNetworkID).(string),
Address: data.Get(attr.Address).(string),
Protocols: protocols,
Groups: groups,
ServiceAccounts: serviceAccounts,
IsAuthoritative: convertAuthoritativeFlagLegacy(data),
Alias: getOptionalString(data, attr.Alias),
Name: data.Get(attr.Name).(string),
RemoteNetworkID: data.Get(attr.RemoteNetworkID).(string),
Address: data.Get(attr.Address).(string),
Protocols: protocols,
Groups: groups,
ServiceAccounts: serviceAccounts,
IsAuthoritative: convertAuthoritativeFlagLegacy(data),
Alias: getOptionalString(data, attr.Alias),
SecurityPolicyID: getOptionalString(data, attr.SecurityPolicyID),
}

isVisible, ok := data.GetOkExists(attr.IsVisible) //nolint
Expand Down Expand Up @@ -524,9 +536,17 @@ func isAttrKnown(data *schema.ResourceData, attr string) bool {
}

func getOptionalString(data *schema.ResourceData, attr string) *string {
if data == nil {
return nil
}

var result *string

cfg := data.GetRawConfig()
if cfg.IsNull() {
return nil
}

val := cfg.GetAttr(attr)

if !val.IsNull() {
Expand Down
76 changes: 76 additions & 0 deletions twingate/internal/test/acctests/resource/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2542,3 +2542,79 @@ func createResourceWithBrowserOption(name, networkName, resourceName, address st
}
`, name, networkName, resourceName, address, browserOption)
}

func TestAccTwingateResourceUpdateSecurityPolicy(t *testing.T) {
resourceName := test.RandomResourceName()
theResource := acctests.TerraformResource(resourceName)
remoteNetworkName := test.RandomName()

policies, err := acctests.ListSecurityPolicies()
if err != nil {
t.Skipf("failed to retrieve security policies: %v", err)
}

if len(policies) < 2 {
t.Skip("requires at least 2 security policy for the test")
}

sdk.Test(t, sdk.TestCase{
ProtoV6ProviderFactories: acctests.ProviderFactories,
PreCheck: func() { acctests.PreCheck(t) },
CheckDestroy: acctests.CheckTwingateResourceDestroy,
Steps: []sdk.TestStep{
{
Config: createResourceWithSecurityPolicy(remoteNetworkName, resourceName, policies[0].ID),
Check: acctests.ComposeTestCheckFunc(
acctests.CheckTwingateResourceExists(theResource),
sdk.TestCheckResourceAttr(theResource, attr.SecurityPolicyID, policies[0].ID),
),
},
{
Config: createResourceWithSecurityPolicy(remoteNetworkName, resourceName, policies[1].ID),
Check: acctests.ComposeTestCheckFunc(
acctests.CheckTwingateResourceExists(theResource),
sdk.TestCheckResourceAttr(theResource, attr.SecurityPolicyID, policies[1].ID),
),
},
{
Config: createResourceWithoutSecurityPolicy(remoteNetworkName, resourceName),
Check: acctests.ComposeTestCheckFunc(
acctests.CheckTwingateResourceExists(theResource),
sdk.TestCheckResourceAttr(theResource, attr.SecurityPolicyID, ""),
),
},
{
Config: createResourceWithSecurityPolicy(remoteNetworkName, resourceName, ""),
// no changes
PlanOnly: true,
},
},
})
}

func createResourceWithSecurityPolicy(remoteNetwork, resource, policyID 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-address.com"
remote_network_id = twingate_remote_network.%[1]s.id
security_policy_id = "%[3]s"
}
`, remoteNetwork, resource, policyID)
}

func createResourceWithoutSecurityPolicy(remoteNetwork, resource 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-address.com"
remote_network_id = twingate_remote_network.%[1]s.id
}
`, remoteNetwork, resource)
}

0 comments on commit 489c394

Please sign in to comment.