• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Twingate / terraform-provider-twingate / 12287568180

12 Dec 2024 12:41AM UTC coverage: 57.177% (-4.9%) from 62.107%
12287568180

Pull #619

github

web-flow
Bump golang.org/x/crypto from 0.29.0 to 0.31.0

Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.29.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.29.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #619: Bump golang.org/x/crypto from 0.29.0 to 0.31.0

5079 of 8883 relevant lines covered (57.18%)

0.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

31.64
/twingate/internal/provider/resource/resource.go
1
package resource
2

3
import (
4
        "context"
5
        "encoding/base64"
6
        "errors"
7
        "fmt"
8
        "reflect"
9
        "regexp"
10
        "strings"
11

12
        "github.com/Twingate/terraform-provider-twingate/v3/twingate/internal/attr"
13
        "github.com/Twingate/terraform-provider-twingate/v3/twingate/internal/client"
14
        "github.com/Twingate/terraform-provider-twingate/v3/twingate/internal/model"
15
        "github.com/Twingate/terraform-provider-twingate/v3/twingate/internal/utils"
16
        "github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
17
        "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
18
        "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
19
        tfattr "github.com/hashicorp/terraform-plugin-framework/attr"
20
        "github.com/hashicorp/terraform-plugin-framework/diag"
21
        "github.com/hashicorp/terraform-plugin-framework/path"
22
        "github.com/hashicorp/terraform-plugin-framework/resource"
23
        "github.com/hashicorp/terraform-plugin-framework/resource/schema"
24
        "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
25
        "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
26
        "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault"
27
        "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
28
        "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault"
29
        "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
30
        "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
31
        "github.com/hashicorp/terraform-plugin-framework/schema/validator"
32
        "github.com/hashicorp/terraform-plugin-framework/tfsdk"
33
        "github.com/hashicorp/terraform-plugin-framework/types"
34
        "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
35
)
36

37
const (
38
        DefaultSecurityPolicyName       = "Default Policy"
39
        schemaVersion             int64 = 2
40
)
41

42
var (
43
        DefaultSecurityPolicyID               string //nolint:gochecknoglobals
44
        ErrPortsWithPolicyAllowAll            = errors.New(model.PolicyAllowAll + " policy does not allow specifying ports.")
45
        ErrPortsWithPolicyDenyAll             = errors.New(model.PolicyDenyAll + " policy does not allow specifying ports.")
46
        ErrPolicyRestrictedWithoutPorts       = errors.New(model.PolicyRestricted + " policy requires specifying ports.")
47
        ErrInvalidAttributeCombination        = errors.New("invalid attribute combination")
48
        ErrWildcardAddressWithEnabledShortcut = errors.New("Resources with a CIDR range or wildcard can't have the browser shortcut enabled.")
49
        ErrDefaultPolicyNotSet                = errors.New("default policy not set")
50
        ErrWrongGlobalID                      = errors.New("Unable to parse global ID")
51
)
52

53
// Ensure the implementation satisfies the desired interfaces.
54
var _ resource.Resource = &twingateResource{}
55

56
func NewResourceResource() resource.Resource {
2✔
57
        return &twingateResource{}
2✔
58
}
2✔
59

60
type twingateResource struct {
61
        client *client.Client
62
}
63

64
type resourceModel struct {
65
        ID                       types.String `tfsdk:"id"`
66
        Name                     types.String `tfsdk:"name"`
67
        Address                  types.String `tfsdk:"address"`
68
        RemoteNetworkID          types.String `tfsdk:"remote_network_id"`
69
        IsAuthoritative          types.Bool   `tfsdk:"is_authoritative"`
70
        Protocols                types.Object `tfsdk:"protocols"`
71
        GroupAccess              types.Set    `tfsdk:"access_group"`
72
        ServiceAccess            types.Set    `tfsdk:"access_service"`
73
        IsActive                 types.Bool   `tfsdk:"is_active"`
74
        IsVisible                types.Bool   `tfsdk:"is_visible"`
75
        IsBrowserShortcutEnabled types.Bool   `tfsdk:"is_browser_shortcut_enabled"`
76
        Alias                    types.String `tfsdk:"alias"`
77
        SecurityPolicyID         types.String `tfsdk:"security_policy_id"`
78
}
79

80
func (r *twingateResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
2✔
81
        resp.TypeName = TwingateResource
2✔
82
}
2✔
83

84
func (r *twingateResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) {
×
85
        if req.ProviderData == nil {
×
86
                return
×
87
        }
×
88

89
        r.client = req.ProviderData.(*client.Client)
×
90
}
91

92
func (r *twingateResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
×
93
        resource.ImportStatePassthroughID(ctx, path.Root(attr.ID), req, resp)
×
94

×
95
        res, err := r.client.ReadResource(ctx, req.ID)
×
96
        if err != nil {
×
97
                resp.Diagnostics.AddError("failed to import state", err.Error())
×
98

×
99
                return
×
100
        }
×
101

102
        if res.Protocols != nil {
×
103
                protocols, diags := convertProtocolsToTerraform(res.Protocols, nil)
×
104
                resp.Diagnostics.Append(diags...)
×
105

×
106
                if resp.Diagnostics.HasError() {
×
107
                        return
×
108
                }
×
109

110
                resp.State.SetAttribute(ctx, path.Root(attr.Protocols), protocols)
×
111
        }
112

113
        if len(res.GroupsAccess) > 0 {
×
114
                accessGroup, diags := convertGroupsAccessToTerraform(ctx, res.GroupsAccess)
×
115
                resp.Diagnostics.Append(diags...)
×
116

×
117
                if resp.Diagnostics.HasError() {
×
118
                        return
×
119
                }
×
120

121
                resp.State.SetAttribute(ctx, path.Root(attr.AccessGroup), accessGroup)
×
122
        }
123

124
        if len(res.ServiceAccounts) > 0 {
×
125
                accessServiceAccount, diags := convertAccessServiceAccountsToTerraform(ctx, res.ServiceAccounts)
×
126
                resp.Diagnostics.Append(diags...)
×
127

×
128
                if resp.Diagnostics.HasError() {
×
129
                        return
×
130
                }
×
131

132
                resp.State.SetAttribute(ctx, path.Root(attr.AccessService), accessServiceAccount)
×
133
        }
134
}
135

136
//nolint:funlen
137
func (r *twingateResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
2✔
138
        resp.Schema = schema.Schema{
2✔
139
                Version:     schemaVersion,
2✔
140
                Description: "Resources in Twingate represent servers on the private network that clients can connect to. Resources can be defined by IP, CIDR range, FQDN, or DNS zone. For more information, see the Twingate [documentation](https://docs.twingate.com/docs/resources-and-access-nodes).",
2✔
141
                Attributes: map[string]schema.Attribute{
2✔
142
                        attr.Name: schema.StringAttribute{
2✔
143
                                Required:    true,
2✔
144
                                Description: "The name of the Resource",
2✔
145
                        },
2✔
146
                        attr.Address: schema.StringAttribute{
2✔
147
                                Required:    true,
2✔
148
                                Description: "The Resource's IP/CIDR or FQDN/DNS zone",
2✔
149
                        },
2✔
150
                        attr.RemoteNetworkID: schema.StringAttribute{
2✔
151
                                Required:    true,
2✔
152
                                Description: "Remote Network ID where the Resource lives",
2✔
153
                        },
2✔
154
                        // optional
2✔
155
                        attr.IsActive: schema.BoolAttribute{
2✔
156
                                Optional:    true,
2✔
157
                                Computed:    true,
2✔
158
                                Description: "Set the resource as active or inactive. Default is `true`.",
2✔
159
                                Default:     booldefault.StaticBool(true),
2✔
160
                        },
2✔
161
                        attr.IsAuthoritative: schema.BoolAttribute{
2✔
162
                                Optional:      true,
2✔
163
                                Computed:      true,
2✔
164
                                Description:   "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.",
2✔
165
                                PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()},
2✔
166
                        },
2✔
167
                        attr.Alias: schema.StringAttribute{
2✔
168
                                Optional:      true,
2✔
169
                                Description:   "Set a DNS alias address for the Resource. Must be a DNS-valid name string.",
2✔
170
                                PlanModifiers: []planmodifier.String{CaseInsensitiveDiff()},
2✔
171
                        },
2✔
172
                        attr.Protocols: protocols(),
2✔
173
                        // computed
2✔
174
                        attr.SecurityPolicyID: schema.StringAttribute{
2✔
175
                                Optional:      true,
2✔
176
                                Computed:      true,
2✔
177
                                Description:   "The ID of a `twingate_security_policy` to set as this Resource's Security Policy. Default is `Default Policy`.",
2✔
178
                                Default:       stringdefault.StaticString(DefaultSecurityPolicyID),
2✔
179
                                PlanModifiers: []planmodifier.String{UseDefaultPolicyForUnknownModifier()},
2✔
180
                        },
2✔
181
                        attr.IsVisible: schema.BoolAttribute{
2✔
182
                                Optional:      true,
2✔
183
                                Computed:      true,
2✔
184
                                Description:   "Controls whether this Resource will be visible in the main Resource list in the Twingate Client. Default is `true`.",
2✔
185
                                Default:       booldefault.StaticBool(true),
2✔
186
                                PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()},
2✔
187
                        },
2✔
188
                        attr.IsBrowserShortcutEnabled: schema.BoolAttribute{
2✔
189
                                Optional:    true,
2✔
190
                                Computed:    true,
2✔
191
                                Description: "Controls whether an \"Open in Browser\" shortcut will be shown for this Resource in the Twingate Client. Default is `false`.",
2✔
192
                                Default:     booldefault.StaticBool(false),
2✔
193
                        },
2✔
194
                        attr.ID: schema.StringAttribute{
2✔
195
                                Computed:      true,
2✔
196
                                Description:   "Autogenerated ID of the Resource, encoded in base64",
2✔
197
                                PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
2✔
198
                        },
2✔
199
                },
2✔
200

2✔
201
                Blocks: map[string]schema.Block{
2✔
202
                        attr.AccessGroup:   groupAccessBlock(),
2✔
203
                        attr.AccessService: serviceAccessBlock(),
2✔
204
                },
2✔
205
        }
2✔
206
}
2✔
207

208
func (r *twingateResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader {
×
209
        return map[int64]resource.StateUpgrader{
×
210
                // State upgrade implementation from 0 (prior state version) to 1 (Schema.Version)
×
211
                0: upgradeResourceStateV0(),
×
212
                // State upgrade implementation from schema version 1 to 2
×
213
                1: upgradeResourceStateV1(),
×
214
        }
×
215
}
×
216

217
func protocols() schema.SingleNestedAttribute {
2✔
218
        return schema.SingleNestedAttribute{
2✔
219
                Optional: true,
2✔
220
                Computed: true,
2✔
221
                Default:  objectdefault.StaticValue(defaultProtocolsObject()),
2✔
222
                Attributes: map[string]schema.Attribute{
2✔
223
                        attr.AllowIcmp: schema.BoolAttribute{
2✔
224
                                Optional:    true,
2✔
225
                                Computed:    true,
2✔
226
                                Default:     booldefault.StaticBool(true),
2✔
227
                                Description: "Whether to allow ICMP (ping) traffic",
2✔
228
                        },
2✔
229

2✔
230
                        attr.UDP: protocol(),
2✔
231
                        attr.TCP: protocol(),
2✔
232
                },
2✔
233
                Description: "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.",
2✔
234
        }
2✔
235
}
2✔
236

237
func protocol() schema.SingleNestedAttribute {
2✔
238
        return schema.SingleNestedAttribute{
2✔
239
                Optional: true,
2✔
240
                Computed: true,
2✔
241
                Default:  objectdefault.StaticValue(defaultProtocolObject()),
2✔
242
                Attributes: map[string]schema.Attribute{
2✔
243
                        attr.Policy: schema.StringAttribute{
2✔
244
                                Optional: true,
2✔
245
                                Computed: true,
2✔
246
                                Validators: []validator.String{
2✔
247
                                        stringvalidator.OneOf(model.Policies...),
2✔
248
                                },
2✔
249
                                Default:     stringdefault.StaticString(model.PolicyAllowAll),
2✔
250
                                Description: fmt.Sprintf("Whether to allow or deny all ports, or restrict protocol access within certain port ranges: Can be `%s` (only listed ports are allowed), `%s`, or `%s`", model.PolicyRestricted, model.PolicyAllowAll, model.PolicyDenyAll),
2✔
251
                        },
2✔
252
                        attr.Ports: schema.SetAttribute{
2✔
253
                                Optional:    true,
2✔
254
                                Computed:    true,
2✔
255
                                ElementType: types.StringType,
2✔
256
                                Description: "List of port ranges between 1 and 65535 inclusive, in the format `100-200` for a range, or `8080` for a single port",
2✔
257
                                PlanModifiers: []planmodifier.Set{
2✔
258
                                        PortsDiff(),
2✔
259
                                },
2✔
260
                                Default: setdefault.StaticValue(defaultEmptyPorts()),
2✔
261
                        },
2✔
262
                },
2✔
263
        }
2✔
264
}
2✔
265

266
func groupAccessBlock() schema.SetNestedBlock {
2✔
267
        return schema.SetNestedBlock{
2✔
268
                Validators: []validator.Set{
2✔
269
                        setvalidator.SizeAtLeast(1),
2✔
270
                },
2✔
271
                Description: "Restrict access to certain group",
2✔
272
                NestedObject: schema.NestedBlockObject{
2✔
273
                        Attributes: map[string]schema.Attribute{
2✔
274
                                attr.GroupID: schema.StringAttribute{
2✔
275
                                        Optional:    true,
2✔
276
                                        Computed:    true,
2✔
277
                                        Description: "Group ID that will have permission to access the Resource.",
2✔
278
                                        Validators: []validator.String{
2✔
279
                                                stringvalidator.RegexMatches(regexp.MustCompile(`\w+`), "Group ID can't be empty"),
2✔
280
                                        },
2✔
281
                                },
2✔
282
                                attr.SecurityPolicyID: schema.StringAttribute{
2✔
283
                                        Optional:    true,
2✔
284
                                        Computed:    true,
2✔
285
                                        Description: "The ID of a `twingate_security_policy` to use as the access policy for the group IDs in the access block.",
2✔
286
                                        Validators: []validator.String{
2✔
287
                                                stringvalidator.AlsoRequires(path.MatchRelative().AtParent().AtName(attr.GroupID)),
2✔
288
                                        },
2✔
289
                                        PlanModifiers: []planmodifier.String{
2✔
290
                                                UseNullPolicyForGroupAccessWhenValueOmitted(),
2✔
291
                                        },
2✔
292
                                },
2✔
293
                                attr.UsageBasedAutolockDurationDays: schema.Int64Attribute{
2✔
294
                                        Optional:    true,
2✔
295
                                        Computed:    true,
2✔
296
                                        Description: "The usage-based auto-lock duration configured on the edge (in days).",
2✔
297
                                        Validators: []validator.Int64{
2✔
298
                                                int64validator.AlsoRequires(path.MatchRelative().AtParent().AtName(attr.GroupID)),
2✔
299
                                        },
2✔
300
                                        PlanModifiers: []planmodifier.Int64{
2✔
301
                                                UseNullIntWhenValueOmitted(),
2✔
302
                                        },
2✔
303
                                },
2✔
304
                        },
2✔
305
                },
2✔
306
        }
2✔
307
}
2✔
308

309
func serviceAccessBlock() schema.SetNestedBlock {
2✔
310
        return schema.SetNestedBlock{
2✔
311
                Validators: []validator.Set{
2✔
312
                        setvalidator.SizeAtLeast(1),
2✔
313
                },
2✔
314
                Description: "Restrict access to certain service account",
2✔
315
                NestedObject: schema.NestedBlockObject{
2✔
316
                        Attributes: map[string]schema.Attribute{
2✔
317
                                attr.ServiceAccountID: schema.StringAttribute{
2✔
318
                                        Optional:    true,
2✔
319
                                        Computed:    true,
2✔
320
                                        Description: "The ID of the service account that should have access to this Resource.",
2✔
321
                                        Validators: []validator.String{
2✔
322
                                                stringvalidator.RegexMatches(regexp.MustCompile(`\w+`), "ServiceAccount ID can't be empty"),
2✔
323
                                        },
2✔
324
                                },
2✔
325
                        },
2✔
326
                },
2✔
327
        }
2✔
328
}
2✔
329

330
func PortsDiff() planmodifier.Set {
2✔
331
        return portsDiff{}
2✔
332
}
2✔
333

334
type portsDiff struct{}
335

336
// Description returns a human-readable description of the plan modifier.
337
func (m portsDiff) Description(_ context.Context) string {
×
338
        return "Handles ports difference."
×
339
}
×
340

341
// MarkdownDescription returns a markdown description of the plan modifier.
342
func (m portsDiff) MarkdownDescription(_ context.Context) string {
×
343
        return "Handles ports difference."
×
344
}
×
345

346
// PlanModifySet implements the plan modification logic.
347
func (m portsDiff) PlanModifySet(_ context.Context, req planmodifier.SetRequest, resp *planmodifier.SetResponse) {
×
348
        if req.StateValue.IsNull() {
×
349
                return
×
350
        }
×
351

352
        if equalPorts(req.StateValue, req.PlanValue) {
×
353
                resp.PlanValue = req.StateValue
×
354
        }
×
355
}
356

357
func equalPorts(one, another types.Set) bool {
1✔
358
        oldPortsRange, err := convertPorts(one)
1✔
359
        if err != nil {
2✔
360
                return false
1✔
361
        }
1✔
362

363
        newPortsRange, err := convertPorts(another)
1✔
364
        if err != nil {
2✔
365
                return false
1✔
366
        }
1✔
367

368
        return portRangeEqual(oldPortsRange, newPortsRange)
1✔
369
}
370

371
func portRangeEqual(one, another []*model.PortRange) bool {
1✔
372
        oneMap := convertPortsRangeToMap(one)
1✔
373
        anotherMap := convertPortsRangeToMap(another)
1✔
374

1✔
375
        return reflect.DeepEqual(oneMap, anotherMap)
1✔
376
}
1✔
377

378
func convertPorts(list types.Set) ([]*model.PortRange, error) {
1✔
379
        items := list.Elements()
1✔
380

1✔
381
        var ports = make([]*model.PortRange, 0, len(items))
1✔
382

1✔
383
        for _, port := range items {
2✔
384
                portRange, err := model.NewPortRange(port.(types.String).ValueString())
1✔
385
                if err != nil {
2✔
386
                        return nil, err //nolint:wrapcheck
1✔
387
                }
1✔
388

389
                ports = append(ports, portRange)
1✔
390
        }
391

392
        return ports, nil
1✔
393
}
394

395
func convertPortsRangeToMap(portsRange []*model.PortRange) map[int]struct{} {
1✔
396
        out := make(map[int]struct{})
1✔
397

1✔
398
        for _, port := range portsRange {
2✔
399
                if port.Start == port.End {
2✔
400
                        out[port.Start] = struct{}{}
1✔
401

1✔
402
                        continue
1✔
403
                }
404

405
                for i := port.Start; i <= port.End; i++ {
2✔
406
                        out[i] = struct{}{}
1✔
407
                }
1✔
408
        }
409

410
        return out
1✔
411
}
412

413
func (r *twingateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
×
414
        var plan resourceModel
×
415

×
416
        resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
×
417

×
418
        if resp.Diagnostics.HasError() {
×
419
                return
×
420
        }
×
421

422
        input, err := convertResource(&plan)
×
423
        if err != nil {
×
424
                addErr(&resp.Diagnostics, err, operationCreate, TwingateResource)
×
425

×
426
                return
×
427
        }
×
428

429
        resource, err := r.client.CreateResource(ctx, input)
×
430
        if err != nil {
×
431
                addErr(&resp.Diagnostics, err, operationCreate, TwingateResource)
×
432

×
433
                return
×
434
        }
×
435

436
        if err = r.client.AddResourceAccess(ctx, resource.ID, convertResourceAccess(resource.ServiceAccounts, resource.GroupsAccess)); err != nil {
×
437
                addErr(&resp.Diagnostics, err, operationCreate, TwingateResource)
×
438

×
439
                return
×
440
        }
×
441

442
        if !input.IsActive {
×
443
                if err := r.client.UpdateResourceActiveState(ctx, &model.Resource{
×
444
                        ID:       resource.ID,
×
445
                        IsActive: false,
×
446
                }); err != nil {
×
447
                        addErr(&resp.Diagnostics, err, operationCreate, TwingateResource)
×
448

×
449
                        return
×
450
                }
×
451

452
                resource.IsActive = false
×
453
        }
454

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

458
func convertResourceAccess(serviceAccounts []string, groupsAccess []model.AccessGroup) []client.AccessInput {
×
459
        access := make([]client.AccessInput, 0, len(serviceAccounts)+len(groupsAccess))
×
460
        for _, account := range serviceAccounts {
×
461
                access = append(access, client.AccessInput{PrincipalID: account})
×
462
        }
×
463

464
        for _, group := range groupsAccess {
×
465
                access = append(access, client.AccessInput{
×
466
                        PrincipalID:                    group.GroupID,
×
467
                        SecurityPolicyID:               group.SecurityPolicyID,
×
468
                        UsageBasedAutolockDurationDays: group.UsageBasedDuration,
×
469
                })
×
470
        }
×
471

472
        return access
×
473
}
474

475
func getAccessAttribute(list types.List, attribute string) []string {
×
476
        if list.IsNull() || list.IsUnknown() || len(list.Elements()) == 0 {
×
477
                return nil
×
478
        }
×
479

480
        obj := list.Elements()[0].(types.Object)
×
481
        if obj.IsNull() || obj.IsUnknown() {
×
482
                return nil
×
483
        }
×
484

485
        val := obj.Attributes()[attribute]
×
486
        if val == nil || val.IsNull() || val.IsUnknown() {
×
487
                return nil
×
488
        }
×
489

490
        return convertIDs(val.(types.Set))
×
491
}
492

493
//nolint:cyclop
494
func getGroupAccessAttribute(list types.Set) []model.AccessGroup {
×
495
        if list.IsNull() || list.IsUnknown() || len(list.Elements()) == 0 {
×
496
                return nil
×
497
        }
×
498

499
        access := make([]model.AccessGroup, 0, len(list.Elements()))
×
500

×
501
        for _, item := range list.Elements() {
×
502
                obj := item.(types.Object)
×
503
                if obj.IsNull() || obj.IsUnknown() {
×
504
                        continue
×
505
                }
506

507
                groupVal := obj.Attributes()[attr.GroupID]
×
508
                accessGroup := model.AccessGroup{
×
509
                        GroupID: groupVal.(types.String).ValueString(),
×
510
                }
×
511

×
512
                securityPolicyVal := obj.Attributes()[attr.SecurityPolicyID]
×
513
                if securityPolicyVal != nil && !securityPolicyVal.IsNull() && !securityPolicyVal.IsUnknown() {
×
514
                        accessGroup.SecurityPolicyID = securityPolicyVal.(types.String).ValueStringPointer()
×
515
                }
×
516

517
                usageBasedDuration := obj.Attributes()[attr.UsageBasedAutolockDurationDays]
×
518
                if usageBasedDuration != nil && !usageBasedDuration.IsNull() && !usageBasedDuration.IsUnknown() {
×
519
                        accessGroup.UsageBasedDuration = usageBasedDuration.(types.Int64).ValueInt64Pointer()
×
520
                }
×
521

522
                access = append(access, accessGroup)
×
523
        }
524

525
        return access
×
526
}
527

528
func getServiceAccountAccessAttribute(list types.Set) []string {
×
529
        if list.IsNull() || list.IsUnknown() || len(list.Elements()) == 0 {
×
530
                return nil
×
531
        }
×
532

533
        serviceAccountIDs := make([]string, 0, len(list.Elements()))
×
534

×
535
        for _, item := range list.Elements() {
×
536
                obj := item.(types.Object)
×
537
                if obj.IsNull() || obj.IsUnknown() {
×
538
                        continue
×
539
                }
540

541
                val := obj.Attributes()[attr.ServiceAccountID]
×
542
                if val == nil || val.IsNull() || val.IsUnknown() {
×
543
                        continue
×
544
                }
545

546
                serviceAccountIDs = append(serviceAccountIDs, val.(types.String).ValueString())
×
547
        }
548

549
        return serviceAccountIDs
×
550
}
551

552
//nolint:cyclop
553
func convertResource(plan *resourceModel) (*model.Resource, error) {
×
554
        protocols, err := convertProtocols(&plan.Protocols)
×
555
        if err != nil {
×
556
                return nil, err
×
557
        }
×
558

559
        accessGroups := getGroupAccessAttribute(plan.GroupAccess)
×
560
        serviceAccountIDs := getServiceAccountAccessAttribute(plan.ServiceAccess)
×
561

×
562
        for _, access := range accessGroups {
×
563
                if access.SecurityPolicyID == nil && access.UsageBasedDuration == nil && len(strings.TrimSpace(access.GroupID)) == 0 {
×
564
                        return nil, ErrInvalidAttributeCombination
×
565
                }
×
566

567
                if err := checkGlobalID(access.GroupID); err != nil {
×
568
                        return nil, err
×
569
                }
×
570
        }
571

572
        for _, id := range serviceAccountIDs {
×
573
                if err := checkGlobalID(id); err != nil {
×
574
                        return nil, err
×
575
                }
×
576
        }
577

578
        isBrowserShortcutEnabled := getOptionalBool(plan.IsBrowserShortcutEnabled)
×
579

×
580
        if isBrowserShortcutEnabled != nil && *isBrowserShortcutEnabled && isWildcardAddress(plan.Address.ValueString()) {
×
581
                return nil, ErrWildcardAddressWithEnabledShortcut
×
582
        }
×
583

584
        return &model.Resource{
×
585
                Name:                     plan.Name.ValueString(),
×
586
                RemoteNetworkID:          plan.RemoteNetworkID.ValueString(),
×
587
                Address:                  plan.Address.ValueString(),
×
588
                Protocols:                protocols,
×
589
                GroupsAccess:             accessGroups,
×
590
                ServiceAccounts:          serviceAccountIDs,
×
591
                IsActive:                 plan.IsActive.ValueBool(),
×
592
                IsAuthoritative:          convertAuthoritativeFlag(plan.IsAuthoritative),
×
593
                Alias:                    getOptionalString(plan.Alias),
×
594
                IsVisible:                getOptionalBool(plan.IsVisible),
×
595
                IsBrowserShortcutEnabled: isBrowserShortcutEnabled,
×
596
                SecurityPolicyID:         plan.SecurityPolicyID.ValueStringPointer(),
×
597
        }, nil
×
598
}
599

600
func checkGlobalID(val string) error {
×
601
        const expectedTokens = 2
×
602

×
603
        data, err := base64.StdEncoding.DecodeString(val)
×
604
        if err != nil {
×
605
                return ErrWrongGlobalID
×
606
        }
×
607

608
        tokens := strings.Split(string(data), ":")
×
609
        if len(tokens) != expectedTokens {
×
610
                return ErrWrongGlobalID
×
611
        }
×
612

613
        name := tokens[0]
×
614

×
615
        if name != "Group" && name != "ServiceAccount" {
×
616
                return ErrWrongGlobalID
×
617
        }
×
618

619
        return nil
×
620
}
621

622
func getOptionalBool(val types.Bool) *bool {
×
623
        if !val.IsUnknown() {
×
624
                return val.ValueBoolPointer()
×
625
        }
×
626

627
        return nil
×
628
}
629

630
func getOptionalString(val types.String) *string {
×
631
        if !val.IsUnknown() && !val.IsNull() {
×
632
                return val.ValueStringPointer()
×
633
        }
×
634

635
        return nil
×
636
}
637

638
func convertIDs(list types.Set) []string {
×
639
        return utils.Map(list.Elements(), func(item tfattr.Value) string {
×
640
                return item.(types.String).ValueString()
×
641
        })
×
642
}
643

644
func equalProtocolsState(objA, objB *types.Object) bool {
×
645
        if objA.IsNull() != objB.IsNull() || objA.IsUnknown() != objB.IsUnknown() {
×
646
                return false
×
647
        }
×
648

649
        protocolsA, err := convertProtocols(objA)
×
650
        if err != nil {
×
651
                return false
×
652
        }
×
653

654
        protocolsB, err := convertProtocols(objB)
×
655
        if err != nil {
×
656
                return false
×
657
        }
×
658

659
        return equalProtocols(protocolsA, protocolsB)
×
660
}
661

662
func equalProtocols(one, another *model.Protocols) bool {
×
663
        return one.AllowIcmp == another.AllowIcmp && equalProtocol(one.TCP, another.TCP) && equalProtocol(one.UDP, another.UDP)
×
664
}
×
665

666
func equalProtocol(one, another *model.Protocol) bool {
×
667
        return one.Policy == another.Policy && portRangeEqual(one.Ports, another.Ports)
×
668
}
×
669

670
func convertProtocols(protocols *types.Object) (*model.Protocols, error) {
×
671
        if protocols == nil || protocols.IsNull() || protocols.IsUnknown() {
×
672
                return model.DefaultProtocols(), nil
×
673
        }
×
674

675
        udp, err := convertProtocol(protocols.Attributes()[attr.UDP])
×
676
        if err != nil {
×
677
                return nil, err
×
678
        }
×
679

680
        tcp, err := convertProtocol(protocols.Attributes()[attr.TCP])
×
681
        if err != nil {
×
682
                return nil, err
×
683
        }
×
684

685
        return &model.Protocols{
×
686
                AllowIcmp: protocols.Attributes()[attr.AllowIcmp].(types.Bool).ValueBool(),
×
687
                UDP:       udp,
×
688
                TCP:       tcp,
×
689
        }, nil
×
690
}
691

692
func convertProtocol(protocol tfattr.Value) (*model.Protocol, error) {
1✔
693
        obj := convertProtocolObj(protocol)
1✔
694
        if obj.IsNull() {
2✔
695
                return nil, nil //nolint:nilnil
1✔
696
        }
1✔
697

698
        ports, err := decodePorts(obj)
1✔
699
        if err != nil {
2✔
700
                return nil, err
1✔
701
        }
1✔
702

703
        policy := obj.Attributes()[attr.Policy].(types.String).ValueString()
1✔
704
        if err := isValidPolicy(policy, ports); err != nil {
1✔
705
                return nil, err
×
706
        }
×
707

708
        if policy == model.PolicyDenyAll {
1✔
709
                policy = model.PolicyRestricted
×
710
        }
×
711

712
        return model.NewProtocol(policy, ports), nil
1✔
713
}
714

715
func convertProtocolObj(protocol tfattr.Value) types.Object {
1✔
716
        if protocol == nil || protocol.IsNull() {
2✔
717
                return types.ObjectNull(nil)
1✔
718
        }
1✔
719

720
        obj, ok := protocol.(types.Object)
1✔
721
        if !ok || obj.IsNull() {
1✔
722
                return types.ObjectNull(nil)
×
723
        }
×
724

725
        return obj
1✔
726
}
727

728
func decodePorts(obj types.Object) ([]*model.PortRange, error) {
1✔
729
        portsVal := obj.Attributes()[attr.Ports]
1✔
730
        if portsVal == nil || portsVal.IsNull() {
1✔
731
                return nil, nil
×
732
        }
×
733

734
        portsList, ok := portsVal.(types.Set)
1✔
735
        if !ok {
1✔
736
                return nil, nil
×
737
        }
×
738

739
        return convertPorts(portsList)
1✔
740
}
741

742
func isValidPolicy(policy string, ports []*model.PortRange) error {
1✔
743
        switch policy {
1✔
744
        case model.PolicyAllowAll:
×
745
                if len(ports) > 0 {
×
746
                        return ErrPortsWithPolicyAllowAll
×
747
                }
×
748

749
        case model.PolicyDenyAll:
×
750
                if len(ports) > 0 {
×
751
                        return ErrPortsWithPolicyDenyAll
×
752
                }
×
753

754
        case model.PolicyRestricted:
1✔
755
                if len(ports) == 0 {
1✔
756
                        return ErrPolicyRestrictedWithoutPorts
×
757
                }
×
758
        }
759

760
        return nil
1✔
761
}
762

763
func (r *twingateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
×
764
        var state resourceModel
×
765

×
766
        resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
×
767

×
768
        if resp.Diagnostics.HasError() {
×
769
                return
×
770
        }
×
771

772
        resource, err := r.client.ReadResource(ctx, state.ID.ValueString())
×
773
        if resource != nil {
×
774
                resource.IsAuthoritative = convertAuthoritativeFlag(state.IsAuthoritative)
×
775

×
776
                if state.SecurityPolicyID.IsNull() {
×
777
                        resource.SecurityPolicyID = nil
×
778
                }
×
779
        }
780

781
        r.helper(ctx, resource, &state, &state, &resp.State, &resp.Diagnostics, err, operationRead)
×
782
}
783

784
//nolint:cyclop
785
func (r *twingateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
×
786
        var plan, state resourceModel
×
787

×
788
        resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
×
789
        resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
×
790

×
791
        if resp.Diagnostics.HasError() {
×
792
                return
×
793
        }
×
794

795
        input, err := convertResource(&plan)
×
796
        if err != nil {
×
797
                addErr(&resp.Diagnostics, err, operationUpdate, TwingateResource)
×
798

×
799
                return
×
800
        }
×
801

802
        planSecurityPolicy := input.SecurityPolicyID
×
803
        input.ID = state.ID.ValueString()
×
804

×
805
        if !plan.GroupAccess.Equal(state.GroupAccess) || !plan.ServiceAccess.Equal(state.ServiceAccess) {
×
806
                if err := r.updateResourceAccess(ctx, &plan, &state, input); err != nil {
×
807
                        addErr(&resp.Diagnostics, err, operationUpdate, TwingateResource)
×
808

×
809
                        return
×
810
                }
×
811
        }
812

813
        var resource *model.Resource
×
814

×
815
        if isResourceChanged(&plan, &state) {
×
816
                if err := r.setDefaultSecurityPolicy(ctx, input); err != nil {
×
817
                        addErr(&resp.Diagnostics, err, operationUpdate, TwingateResource)
×
818

×
819
                        return
×
820
                }
×
821

822
                resource, err = r.client.UpdateResource(ctx, input)
×
823
        } else {
×
824
                resource, err = r.client.ReadResource(ctx, input.ID)
×
825
        }
×
826

827
        if resource != nil {
×
828
                resource.IsAuthoritative = input.IsAuthoritative
×
829

×
830
                if planSecurityPolicy != nil && *planSecurityPolicy == "" {
×
831
                        resource.SecurityPolicyID = planSecurityPolicy
×
832
                }
×
833
        }
834

835
        r.helper(ctx, resource, &state, &plan, &resp.State, &resp.Diagnostics, err, operationUpdate)
×
836
}
837

838
func (r *twingateResource) setDefaultSecurityPolicy(ctx context.Context, resource *model.Resource) error {
×
839
        if DefaultSecurityPolicyID == "" {
×
840
                policy, _ := r.client.ReadSecurityPolicy(ctx, "", DefaultSecurityPolicyName)
×
841
                if policy != nil {
×
842
                        DefaultSecurityPolicyID = policy.ID
×
843
                }
×
844
        }
845

846
        if DefaultSecurityPolicyID == "" {
×
847
                return ErrDefaultPolicyNotSet
×
848
        }
×
849

850
        remoteResource, err := r.client.ReadResource(ctx, resource.ID)
×
851
        if err != nil {
×
852
                return err //nolint:wrapcheck
×
853
        }
×
854

855
        if remoteResource.SecurityPolicyID != nil && (resource.SecurityPolicyID == nil || *resource.SecurityPolicyID == "") &&
×
856
                *remoteResource.SecurityPolicyID != DefaultSecurityPolicyID {
×
857
                resource.SecurityPolicyID = &DefaultSecurityPolicyID
×
858
        }
×
859

860
        return nil
×
861
}
862

863
func isResourceChanged(plan, state *resourceModel) bool {
×
864
        return !plan.RemoteNetworkID.Equal(state.RemoteNetworkID) ||
×
865
                !plan.Name.Equal(state.Name) ||
×
866
                !plan.Address.Equal(state.Address) ||
×
867
                !equalProtocolsState(&plan.Protocols, &state.Protocols) ||
×
868
                !plan.IsActive.Equal(state.IsActive) ||
×
869
                !plan.IsVisible.Equal(state.IsVisible) ||
×
870
                !plan.IsBrowserShortcutEnabled.Equal(state.IsBrowserShortcutEnabled) ||
×
871
                !plan.Alias.Equal(state.Alias) ||
×
872
                !plan.SecurityPolicyID.Equal(state.SecurityPolicyID)
×
873
}
×
874

875
func (r *twingateResource) updateResourceAccess(ctx context.Context, plan, state *resourceModel, input *model.Resource) error {
×
876
        idsToDelete, serviceAccountsToAdd, groupsToAdd, err := r.getChangedAccessIDs(ctx, plan, state, input)
×
877
        if err != nil {
×
878
                return fmt.Errorf("failed to update resource access: %w", err)
×
879
        }
×
880

881
        if err := r.client.RemoveResourceAccess(ctx, input.ID, idsToDelete); err != nil {
×
882
                return fmt.Errorf("failed to update resource access: %w", err)
×
883
        }
×
884

885
        if err := r.client.AddResourceAccess(ctx, input.ID, convertResourceAccess(serviceAccountsToAdd, groupsToAdd)); err != nil {
×
886
                return fmt.Errorf("failed to update resource access: %w", err)
×
887
        }
×
888

889
        return nil
×
890
}
891

892
func (r *twingateResource) getChangedAccessIDs(ctx context.Context, plan, state *resourceModel, resource *model.Resource) ([]string, []string, []model.AccessGroup, error) {
×
893
        remote, err := r.client.ReadResource(ctx, resource.ID)
×
894
        if err != nil {
×
895
                return nil, nil, nil, fmt.Errorf("failed to get changedIDs: %w", err)
×
896
        }
×
897

898
        var (
×
899
                oldServiceAccounts []string
×
900
                oldGroups          []model.AccessGroup
×
901
        )
×
902

×
903
        if resource.IsAuthoritative {
×
904
                oldGroups, oldServiceAccounts = remote.GroupsAccess, remote.ServiceAccounts
×
905
        } else {
×
906
                oldGroups = getOldIDsNonAuthoritativeGroupAccess(plan, state)
×
907
                oldServiceAccounts = getOldIDsNonAuthoritativeServiceAccountAccess(plan, state)
×
908
        }
×
909

910
        // ids to delete
911
        groupsToDelete := setDifferenceGroups(oldGroups, resource.GroupsAccess)
×
912
        serviceAccountsToDelete := setDifference(oldServiceAccounts, resource.ServiceAccounts)
×
913

×
914
        // ids to add
×
915
        groupsToAdd := setDifferenceGroupAccess(resource.GroupsAccess, remote.GroupsAccess)
×
916
        serviceAccountsToAdd := setDifference(resource.ServiceAccounts, remote.ServiceAccounts)
×
917

×
918
        return append(groupsToDelete, serviceAccountsToDelete...), serviceAccountsToAdd, groupsToAdd, nil
×
919
}
920

921
func getOldIDsNonAuthoritativeServiceAccountAccess(plan, state *resourceModel) []string {
×
922
        if !plan.ServiceAccess.Equal(state.ServiceAccess) {
×
923
                return getServiceAccountAccessAttribute(state.ServiceAccess)
×
924
        }
×
925

926
        return nil
×
927
}
928

929
func getOldIDsNonAuthoritativeGroupAccess(plan, state *resourceModel) []model.AccessGroup {
×
930
        if !plan.GroupAccess.Equal(state.GroupAccess) {
×
931
                return getGroupAccessAttribute(state.GroupAccess)
×
932
        }
×
933

934
        return nil
×
935
}
936

937
func (r *twingateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
×
938
        var state resourceModel
×
939

×
940
        resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
×
941

×
942
        if resp.Diagnostics.HasError() {
×
943
                return
×
944
        }
×
945

946
        err := r.client.DeleteResource(ctx, state.ID.ValueString())
×
947
        addErr(&resp.Diagnostics, err, operationDelete, TwingateResource)
×
948
}
949

950
func (r *twingateResource) helper(ctx context.Context, resource *model.Resource, state, reference *resourceModel, respState *tfsdk.State, diagnostics *diag.Diagnostics, err error, operation string) {
×
951
        if err != nil {
×
952
                if errors.Is(err, client.ErrGraphqlResultIsEmpty) {
×
953
                        // clear state
×
954
                        respState.RemoveResource(ctx)
×
955

×
956
                        return
×
957
                }
×
958

959
                addErr(diagnostics, err, operation, TwingateResource)
×
960

×
961
                return
×
962
        }
963

964
        if resource.Protocols == nil {
×
965
                resource.Protocols = model.DefaultProtocols()
×
966
        }
×
967

968
        if !resource.IsAuthoritative {
×
969
                resource.GroupsAccess = setIntersectionGroupAccess(getGroupAccessAttribute(reference.GroupAccess), resource.GroupsAccess)
×
970
                resource.ServiceAccounts = setIntersection(getServiceAccountAccessAttribute(reference.ServiceAccess), resource.ServiceAccounts)
×
971

×
972
                serviceAccessIDs := utils.MakeLookupMap(getServiceAccountAccessAttribute(reference.ServiceAccess))
×
973

×
974
                var filteredServiceAccess []string
×
975

×
976
                for _, serviceAccessID := range resource.ServiceAccounts {
×
977
                        if serviceAccessIDs[serviceAccessID] {
×
978
                                filteredServiceAccess = append(filteredServiceAccess, serviceAccessID)
×
979
                        }
×
980
                }
981

982
                resource.ServiceAccounts = filteredServiceAccess
×
983
        }
984

985
        setState(ctx, state, reference, resource, diagnostics)
×
986

×
987
        if diagnostics.HasError() {
×
988
                return
×
989
        }
×
990

991
        // Set refreshed state
992
        diagnostics.Append(respState.Set(ctx, state)...)
×
993
}
994

995
func setState(ctx context.Context, state, reference *resourceModel, resource *model.Resource, diagnostics *diag.Diagnostics) { //nolint:cyclop
×
996
        state.ID = types.StringValue(resource.ID)
×
997
        state.Name = types.StringValue(resource.Name)
×
998
        state.RemoteNetworkID = types.StringValue(resource.RemoteNetworkID)
×
999
        state.Address = types.StringValue(resource.Address)
×
1000
        state.IsActive = types.BoolValue(resource.IsActive)
×
1001
        state.IsAuthoritative = types.BoolValue(resource.IsAuthoritative)
×
1002
        state.SecurityPolicyID = types.StringPointerValue(resource.SecurityPolicyID)
×
1003

×
1004
        if !state.IsVisible.IsNull() || !reference.IsVisible.IsUnknown() {
×
1005
                state.IsVisible = types.BoolPointerValue(resource.IsVisible)
×
1006
        }
×
1007

1008
        if !state.IsBrowserShortcutEnabled.IsNull() || !reference.IsBrowserShortcutEnabled.IsUnknown() {
×
1009
                state.IsBrowserShortcutEnabled = types.BoolPointerValue(resource.IsBrowserShortcutEnabled)
×
1010
        }
×
1011

1012
        if !state.Alias.IsNull() || !reference.Alias.IsUnknown() {
×
1013
                state.Alias = reference.Alias
×
1014
        }
×
1015

1016
        if !state.Protocols.IsNull() || !reference.Protocols.IsUnknown() {
×
1017
                protocols, diags := convertProtocolsToTerraform(resource.Protocols, &reference.Protocols)
×
1018
                diagnostics.Append(diags...)
×
1019

×
1020
                if diagnostics.HasError() {
×
1021
                        return
×
1022
                }
×
1023

1024
                if !equalProtocolsState(&state.Protocols, &protocols) {
×
1025
                        state.Protocols = protocols
×
1026
                }
×
1027
        }
1028

1029
        groupAccess, diags := convertGroupsAccessToTerraform(ctx, resource.GroupsAccess)
×
1030
        diagnostics.Append(diags...)
×
1031
        serviceAccess, diags := convertServiceAccessToTerraform(ctx, resource.ServiceAccounts)
×
1032
        diagnostics.Append(diags...)
×
1033

×
1034
        if diagnostics.HasError() {
×
1035
                return
×
1036
        }
×
1037

1038
        state.GroupAccess = groupAccess
×
1039
        state.ServiceAccess = serviceAccess
×
1040
}
1041

1042
func convertProtocolsToTerraform(protocols *model.Protocols, reference *types.Object) (types.Object, diag.Diagnostics) {
×
1043
        var diagnostics diag.Diagnostics
×
1044

×
1045
        if protocols == nil || reference != nil && (reference.IsUnknown() || reference.IsNull()) {
×
1046
                return defaultProtocolsModelToTerraform()
×
1047
        }
×
1048

1049
        var referenceTCP, referenceUDP tfattr.Value
×
1050
        if reference != nil {
×
1051
                referenceTCP = reference.Attributes()[attr.TCP]
×
1052
                referenceUDP = reference.Attributes()[attr.UDP]
×
1053
        }
×
1054

1055
        tcp, diags := convertProtocolModelToTerraform(protocols.TCP, referenceTCP)
×
1056
        diagnostics.Append(diags...)
×
1057

×
1058
        udp, diags := convertProtocolModelToTerraform(protocols.UDP, referenceUDP)
×
1059
        diagnostics.Append(diags...)
×
1060

×
1061
        if diagnostics.HasError() {
×
1062
                return types.ObjectNull(protocolsAttributeTypes()), diagnostics
×
1063
        }
×
1064

1065
        attributes := map[string]tfattr.Value{
×
1066
                attr.AllowIcmp: types.BoolValue(protocols.AllowIcmp),
×
1067
                attr.TCP:       tcp,
×
1068
                attr.UDP:       udp,
×
1069
        }
×
1070

×
1071
        obj := types.ObjectValueMust(protocolsAttributeTypes(), attributes)
×
1072

×
1073
        return obj, diagnostics
×
1074
}
1075

1076
func convertPortsToTerraform(ports []*model.PortRange) types.Set {
×
1077
        if len(ports) == 0 {
×
1078
                return defaultEmptyPorts()
×
1079
        }
×
1080

1081
        elements := make([]tfattr.Value, 0, len(ports))
×
1082
        for _, port := range ports {
×
1083
                elements = append(elements, types.StringValue(port.String()))
×
1084
        }
×
1085

1086
        return types.SetValueMust(types.StringType, elements)
×
1087
}
1088

1089
func convertProtocolModelToTerraform(protocol *model.Protocol, _ tfattr.Value) (types.Object, diag.Diagnostics) {
×
1090
        if protocol == nil {
×
1091
                return types.ObjectNull(protocolAttributeTypes()), nil
×
1092
        }
×
1093

1094
        ports := convertPortsToTerraform(protocol.Ports)
×
1095

×
1096
        policy := protocol.Policy
×
1097
        if policy == model.PolicyRestricted && len(ports.Elements()) == 0 {
×
1098
                policy = model.PolicyDenyAll
×
1099
        }
×
1100

1101
        attributes := map[string]tfattr.Value{
×
1102
                attr.Policy: types.StringValue(policy),
×
1103
                attr.Ports:  ports,
×
1104
        }
×
1105

×
1106
        return types.ObjectValue(protocolAttributeTypes(), attributes)
×
1107
}
1108

1109
func defaultProtocolsModelToTerraform() (types.Object, diag.Diagnostics) {
×
1110
        attributeTypes := protocolsAttributeTypes()
×
1111

×
1112
        var diagnostics diag.Diagnostics
×
1113

×
1114
        defaultPorts, diags := defaultProtocolModelToTerraform()
×
1115
        diagnostics.Append(diags...)
×
1116

×
1117
        if diagnostics.HasError() {
×
1118
                return makeNullObject(attributeTypes), diagnostics
×
1119
        }
×
1120

1121
        attributes := map[string]tfattr.Value{
×
1122
                attr.AllowIcmp: types.BoolValue(true),
×
1123
                attr.TCP:       defaultPorts,
×
1124
                attr.UDP:       defaultPorts,
×
1125
        }
×
1126

×
1127
        obj, diags := types.ObjectValue(attributeTypes, attributes)
×
1128
        diagnostics.Append(diags...)
×
1129

×
1130
        if diagnostics.HasError() {
×
1131
                return makeNullObject(attributeTypes), diagnostics
×
1132
        }
×
1133

1134
        return obj, diagnostics
×
1135
}
1136

1137
func defaultProtocolsObject() types.Object {
2✔
1138
        attributeTypes := protocolsAttributeTypes()
2✔
1139

2✔
1140
        var diagnostics diag.Diagnostics
2✔
1141

2✔
1142
        defaultPorts, diags := defaultProtocolModelToTerraform()
2✔
1143
        diagnostics.Append(diags...)
2✔
1144

2✔
1145
        if diagnostics.HasError() {
2✔
1146
                return makeNullObject(attributeTypes)
×
1147
        }
×
1148

1149
        attributes := map[string]tfattr.Value{
2✔
1150
                attr.AllowIcmp: types.BoolValue(true),
2✔
1151
                attr.TCP:       defaultPorts,
2✔
1152
                attr.UDP:       defaultPorts,
2✔
1153
        }
2✔
1154

2✔
1155
        obj, diags := types.ObjectValue(attributeTypes, attributes)
2✔
1156
        diagnostics.Append(diags...)
2✔
1157

2✔
1158
        if diagnostics.HasError() {
2✔
1159
                return makeNullObject(attributeTypes)
×
1160
        }
×
1161

1162
        return obj
2✔
1163
}
1164

1165
func defaultEmptyPorts() types.Set {
2✔
1166
        return types.SetValueMust(types.StringType, []tfattr.Value{})
2✔
1167
}
2✔
1168

1169
func defaultProtocolModelToTerraform() (basetypes.ObjectValue, diag.Diagnostics) {
2✔
1170
        attributes := map[string]tfattr.Value{
2✔
1171
                attr.Policy: types.StringValue(model.PolicyAllowAll),
2✔
1172
                attr.Ports:  defaultEmptyPorts(),
2✔
1173
        }
2✔
1174

2✔
1175
        return types.ObjectValue(protocolAttributeTypes(), attributes)
2✔
1176
}
2✔
1177

1178
func defaultProtocolObject() basetypes.ObjectValue {
2✔
1179
        obj, _ := defaultProtocolModelToTerraform()
2✔
1180

2✔
1181
        return obj
2✔
1182
}
2✔
1183

1184
func protocolsAttributeTypes() map[string]tfattr.Type {
2✔
1185
        return map[string]tfattr.Type{
2✔
1186
                attr.AllowIcmp: types.BoolType,
2✔
1187
                attr.TCP: types.ObjectType{
2✔
1188
                        AttrTypes: protocolAttributeTypes(),
2✔
1189
                },
2✔
1190
                attr.UDP: types.ObjectType{
2✔
1191
                        AttrTypes: protocolAttributeTypes(),
2✔
1192
                },
2✔
1193
        }
2✔
1194
}
2✔
1195

1196
func protocolAttributeTypes() map[string]tfattr.Type {
3✔
1197
        return map[string]tfattr.Type{
3✔
1198
                attr.Policy: types.StringType,
3✔
1199
                attr.Ports: types.SetType{
3✔
1200
                        ElemType: types.StringType,
3✔
1201
                },
3✔
1202
        }
3✔
1203
}
3✔
1204

1205
func convertServiceAccessToTerraform(ctx context.Context, serviceAccounts []string) (types.Set, diag.Diagnostics) {
×
1206
        var diagnostics diag.Diagnostics
×
1207

×
1208
        if len(serviceAccounts) == 0 {
×
1209
                return makeObjectsSetNull(ctx, accessServiceAccountAttributeTypes()), diagnostics
×
1210
        }
×
1211

1212
        objects := make([]types.Object, 0, len(serviceAccounts))
×
1213

×
1214
        for _, account := range serviceAccounts {
×
1215
                attributes := map[string]tfattr.Value{
×
1216
                        attr.ServiceAccountID: types.StringValue(account),
×
1217
                }
×
1218

×
1219
                obj, diags := types.ObjectValue(accessServiceAccountAttributeTypes(), attributes)
×
1220
                diagnostics.Append(diags...)
×
1221

×
1222
                objects = append(objects, obj)
×
1223
        }
×
1224

1225
        if diagnostics.HasError() {
×
1226
                return makeObjectsSetNull(ctx, accessServiceAccountAttributeTypes()), diagnostics
×
1227
        }
×
1228

1229
        return makeObjectsSet(ctx, objects...)
×
1230
}
1231

1232
func convertGroupsAccessToTerraform(ctx context.Context, groupAccess []model.AccessGroup) (types.Set, diag.Diagnostics) {
×
1233
        var diagnostics diag.Diagnostics
×
1234

×
1235
        if len(groupAccess) == 0 {
×
1236
                return makeObjectsSetNull(ctx, accessGroupAttributeTypes()), diagnostics
×
1237
        }
×
1238

1239
        objects := make([]types.Object, 0, len(groupAccess))
×
1240

×
1241
        for _, access := range groupAccess {
×
1242
                attributes := map[string]tfattr.Value{
×
1243
                        attr.GroupID:                        types.StringValue(access.GroupID),
×
1244
                        attr.SecurityPolicyID:               types.StringPointerValue(access.SecurityPolicyID),
×
1245
                        attr.UsageBasedAutolockDurationDays: types.Int64PointerValue(access.UsageBasedDuration),
×
1246
                }
×
1247

×
1248
                obj, diags := types.ObjectValue(accessGroupAttributeTypes(), attributes)
×
1249
                diagnostics.Append(diags...)
×
1250

×
1251
                objects = append(objects, obj)
×
1252
        }
×
1253

1254
        if diagnostics.HasError() {
×
1255
                return makeObjectsSetNull(ctx, accessGroupAttributeTypes()), diagnostics
×
1256
        }
×
1257

1258
        return makeObjectsSet(ctx, objects...)
×
1259
}
1260

1261
func CaseInsensitiveDiff() planmodifier.String {
2✔
1262
        return caseInsensitiveDiffModifier{
2✔
1263
                description: "Handles case insensitive strings",
2✔
1264
        }
2✔
1265
}
2✔
1266

1267
type caseInsensitiveDiffModifier struct {
1268
        description string
1269
}
1270

1271
func (m caseInsensitiveDiffModifier) Description(_ context.Context) string {
×
1272
        return m.description
×
1273
}
×
1274

1275
func (m caseInsensitiveDiffModifier) MarkdownDescription(_ context.Context) string {
×
1276
        return m.description
×
1277
}
×
1278

1279
func (m caseInsensitiveDiffModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
×
1280
        // Do not replace on resource creation.
×
1281
        if req.State.Raw.IsNull() {
×
1282
                return
×
1283
        }
×
1284

1285
        // Do not replace on resource destroy.
1286
        if req.Plan.Raw.IsNull() {
×
1287
                return
×
1288
        }
×
1289

1290
        if !req.PlanValue.IsUnknown() && req.StateValue.IsNull() {
×
1291
                return
×
1292
        }
×
1293

1294
        if strings.EqualFold(strings.ToLower(req.PlanValue.ValueString()), strings.ToLower(req.StateValue.ValueString())) {
×
1295
                resp.PlanValue = req.StateValue
×
1296
        }
×
1297
}
1298

1299
var cidrRgxp = regexp.MustCompile(`(\d{1,3}\.){3}\d{1,3}(/\d+)`)
1300

1301
func isWildcardAddress(address string) bool {
1✔
1302
        return strings.ContainsAny(address, "*?") || cidrRgxp.MatchString(address)
1✔
1303
}
1✔
1304

1305
func UseDefaultPolicyForUnknownModifier() planmodifier.String {
2✔
1306
        return useDefaultPolicyForUnknownModifier{}
2✔
1307
}
2✔
1308

1309
// useDefaultPolicyForUnknownModifier implements the plan modifier.
1310
type useDefaultPolicyForUnknownModifier struct{}
1311

1312
// Description returns a human-readable description of the plan modifier.
1313
func (m useDefaultPolicyForUnknownModifier) Description(_ context.Context) string {
×
1314
        return "Once set, the value of this attribute will fallback to Default Policy on unset."
×
1315
}
×
1316

1317
// MarkdownDescription returns a markdown description of the plan modifier.
1318
func (m useDefaultPolicyForUnknownModifier) MarkdownDescription(_ context.Context) string {
×
1319
        return "Once set, the value of this attribute will fallback to Default Policy on unset."
×
1320
}
×
1321

1322
// PlanModifyString implements the plan modification logic.
1323
func (m useDefaultPolicyForUnknownModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
×
1324
        if req.StateValue.IsNull() && req.ConfigValue.IsNull() {
×
1325
                resp.PlanValue = types.StringNull()
×
1326

×
1327
                return
×
1328
        }
×
1329
}
1330

1331
func accessGroupAttributeTypes() map[string]tfattr.Type {
×
1332
        return map[string]tfattr.Type{
×
1333
                attr.GroupID:                        types.StringType,
×
1334
                attr.SecurityPolicyID:               types.StringType,
×
1335
                attr.UsageBasedAutolockDurationDays: types.Int64Type,
×
1336
        }
×
1337
}
×
1338

1339
func accessServiceAccountAttributeTypes() map[string]tfattr.Type {
×
1340
        return map[string]tfattr.Type{
×
1341
                attr.ServiceAccountID: types.StringType,
×
1342
        }
×
1343
}
×
1344

1345
func UseNullPolicyForGroupAccessWhenValueOmitted() planmodifier.String {
2✔
1346
        return useNullPolicyForGroupAccessWhenValueOmitted{}
2✔
1347
}
2✔
1348

1349
type useNullPolicyForGroupAccessWhenValueOmitted struct{}
1350

1351
func (m useNullPolicyForGroupAccessWhenValueOmitted) Description(_ context.Context) string {
×
1352
        return ""
×
1353
}
×
1354

1355
func (m useNullPolicyForGroupAccessWhenValueOmitted) MarkdownDescription(_ context.Context) string {
×
1356
        return ""
×
1357
}
×
1358

1359
func (m useNullPolicyForGroupAccessWhenValueOmitted) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
×
1360
        if req.StateValue.IsNull() && req.ConfigValue.IsNull() {
×
1361
                resp.PlanValue = types.StringNull()
×
1362

×
1363
                return
×
1364
        }
×
1365

1366
        // Do nothing if there is no state value.
1367
        if req.StateValue.IsNull() {
×
1368
                return
×
1369
        }
×
1370

1371
        // Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
1372
        if req.ConfigValue.IsUnknown() {
×
1373
                return
×
1374
        }
×
1375

1376
        if req.ConfigValue.IsNull() && !req.PlanValue.IsNull() {
×
1377
                resp.PlanValue = types.StringNull()
×
1378
        }
×
1379
}
1380

1381
func UseNullIntWhenValueOmitted() planmodifier.Int64 {
2✔
1382
        return useNullIntWhenValueOmitted{}
2✔
1383
}
2✔
1384

1385
type useNullIntWhenValueOmitted struct{}
1386

1387
func (m useNullIntWhenValueOmitted) Description(_ context.Context) string {
×
1388
        return ""
×
1389
}
×
1390

1391
func (m useNullIntWhenValueOmitted) MarkdownDescription(_ context.Context) string {
×
1392
        return ""
×
1393
}
×
1394

1395
func (m useNullIntWhenValueOmitted) PlanModifyInt64(ctx context.Context, req planmodifier.Int64Request, resp *planmodifier.Int64Response) {
×
1396
        if req.StateValue.IsNull() && req.ConfigValue.IsNull() {
×
1397
                resp.PlanValue = types.Int64Null()
×
1398

×
1399
                return
×
1400
        }
×
1401

1402
        // Do nothing if there is no state value.
1403
        if req.StateValue.IsNull() {
×
1404
                return
×
1405
        }
×
1406

1407
        // Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
1408
        if req.ConfigValue.IsUnknown() {
×
1409
                return
×
1410
        }
×
1411

1412
        if req.ConfigValue.IsNull() && !req.PlanValue.IsNull() {
×
1413
                resp.PlanValue = types.Int64Null()
×
1414
        }
×
1415
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc