Skip to content

Commit 5c44c13

Browse files
Shadow649Copilot
andauthored
feat(zones): support v2 expression syntax for sysdig_secure_zone (#712)
<!-- Thank you for your contribution! For a cleaner PR make sure you follow these recommendations: - Add the **scope** of the affected area in the PR name, following [Conventional Commit](https://www.conventionalcommits. org/en/v1.0.0/) format ex.: feat(secure-policy): Add runbook to policy resources - If not there yet, add yourself and/or your team as the **Codeowners** of the affected area - Make sure to modify the respective **tests** - Make sure to modify the respective **documentation** --> This pull request significantly enhances the `sysdig_secure_zone` data source to support v2-compatible scope expressions, improves test coverage, and updates dependency versions. The main focus is on allowing richer scope filtering via expressions, ensuring backward compatibility, and providing robust validation with new unit and acceptance tests. Enhancements to scope expression support: * Added support for v2-compatible scope expressions in the `sysdig_secure_zone` data source by introducing new schema fields (`expression`, `field`, `operator`, `value`, `values`) and merging legacy rules with v2 expressions based on scope ID. This enables richer and more flexible filtering in resource definitions. [[1]](diffhunk://#diff-61c5dc355aa136849ccbeda35f10a0f35ecdd1a1964951638e4f674891f21f93R55-R60) [[2]](diffhunk://#diff-a0229110cc4b1cae2cbc580b78c4dc26bad0bb035b9d7dcd578e40227fa2ac3dR53-R80) [[3]](diffhunk://#diff-a0229110cc4b1cae2cbc580b78c4dc26bad0bb035b9d7dcd578e40227fa2ac3dL81-R107) [[4]](diffhunk://#diff-a0229110cc4b1cae2cbc580b78c4dc26bad0bb035b9d7dcd578e40227fa2ac3dR118-R121) [[5]](diffhunk://#diff-a0229110cc4b1cae2cbc580b78c4dc26bad0bb035b9d7dcd578e40227fa2ac3dR137-R140) [[6]](diffhunk://#diff-a0229110cc4b1cae2cbc580b78c4dc26bad0bb035b9d7dcd578e40227fa2ac3dL120-R193) Testing improvements: * Added new acceptance tests (`TestAccDataSourceSysdigSecureZone_ByName`, `TestAccDataSourceSysdigSecureZone_ByID`) to verify expression support and correct retrieval by name and ID, as well as helper methods for test configurations. * Introduced unit tests for the scope merging logic to ensure correct handling of partial matches, missing IDs, and graceful degradation when v2 data is unavailable. Dependency and API updates: * Updated multiple dependencies in `go.mod` to newer versions, including `terraform-plugin-sdk`, `terraform-plugin-log`, and several indirect dependencies for improved compatibility and security. [[1]](diffhunk://#diff-33ef32bf6c23acb95f5902d7097b7a1d5128ca061167ec0716715b0b9eeaa5f6L10-R18) [[2]](diffhunk://#diff-33ef32bf6c23acb95f5902d7097b7a1d5128ca061167ec0716715b0b9eeaa5f6L26-R27) [[3]](diffhunk://#diff-33ef32bf6c23acb95f5902d7097b7a1d5128ca061167ec0716715b0b9eeaa5f6L40-R50) [[4]](diffhunk://#diff-33ef32bf6c23acb95f5902d7097b7a1d5128ca061167ec0716715b0b9eeaa5f6L65-R82) * Added a new `ZoneV2Interface` to the client and implemented a robust `APIError` type with improved error extraction from API responses. [[1]](diffhunk://#diff-3e543bee017248841b3c762241b5460f71986a4dac8ffcb87af8324adf237626R65) [[2]](diffhunk://#diff-3e543bee017248841b3c762241b5460f71986a4dac8ffcb87af8324adf237626R78-R93) [[3]](diffhunk://#diff-3e543bee017248841b3c762241b5460f71986a4dac8ffcb87af8324adf237626R118-R146) * Introduced new model types for v2 zones in the client (`ZonesV2Wrapper`, `ZoneV2`). These changes collectively modernize the provider's handling of secure zones, improve error reporting, and ensure comprehensive validation through testing. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent a37211b commit 5c44c13

19 files changed

+2706
-169
lines changed

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ require (
1919
)
2020

2121
require (
22+
dario.cat/mergo v1.0.1 // indirect
2223
github.com/ProtonMail/go-crypto v1.4.0 // indirect
2324
github.com/agext/levenshtein v1.2.3 // indirect
2425
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
@@ -31,6 +32,7 @@ require (
3132
github.com/docker/docker-credential-helpers v0.9.5 // indirect
3233
github.com/fatih/color v1.18.0 // indirect
3334
github.com/go-akka/configuration v0.0.0-20200606091224-a002c0330665 // indirect
35+
github.com/go-test/deep v1.0.8 // indirect
3436
github.com/golang/protobuf v1.5.4 // indirect
3537
github.com/google/go-cmp v0.7.0 // indirect
3638
github.com/google/go-containerregistry v0.21.2 // indirect
@@ -80,5 +82,6 @@ require (
8082
google.golang.org/appengine v1.6.8 // indirect
8183
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
8284
google.golang.org/grpc v1.79.2 // indirect
85+
gopkg.in/yaml.v2 v2.4.0 // indirect
8386
gopkg.in/yaml.v3 v3.0.1 // indirect
8487
)

go.sum

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
2-
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
1+
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
2+
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
33
github.com/Jeffail/gabs/v2 v2.7.0 h1:Y2edYaTcE8ZpRsR2AtmPu5xQdFDIthFG0jYhu5PY8kg=
44
github.com/Jeffail/gabs/v2 v2.7.0/go.mod h1:dp5ocw1FvBBQYssgHsG7I1WYsiLRtkUaB1FEtSwvNUw=
55
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
@@ -57,8 +57,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
5757
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
5858
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
5959
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
60-
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
61-
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
60+
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
61+
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
6262
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
6363
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
6464
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
@@ -295,8 +295,9 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
295295
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
296296
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
297297
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
298-
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
299298
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
299+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
300+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
300301
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
301302
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
302303
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=

sysdig/common.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ const (
5252
SchemaScopeKey = "scope"
5353
SchemaScopesKey = "scopes"
5454
SchemaTargetTypeKey = "target_type"
55+
SchemaExpressionKey = "expression"
56+
SchemaFieldKey = "field"
57+
SchemaOperatorKey = "operator"
58+
SchemaValueKey = "value"
59+
SchemaValuesKey = "values"
5560
SchemaRoleKey = "role"
5661
SchemaSystemRoleKey = "system_role"
5762
SchemaRulesKey = "rules"

sysdig/data_source_sysdig_secure_zone.go

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,34 @@ func dataSourceSysdigSecureZone() *schema.Resource {
5050
Type: schema.TypeString,
5151
Computed: true,
5252
},
53+
// Not marked Deprecated: rules with v2-compatible syntax are fully supported.
54+
// Only v1 syntax (labels, labelValues, agentTags) is deprecated, but since
55+
// this is a Computed field, SDK v2 has no mechanism for conditional deprecation.
56+
// The resource-side ValidateDiagFunc handles the v1-only warning.
5357
SchemaRulesKey: {
5458
Type: schema.TypeString,
5559
Computed: true,
5660
},
61+
SchemaExpressionKey: {
62+
Type: schema.TypeList,
63+
Computed: true,
64+
Elem: &schema.Resource{
65+
Schema: map[string]*schema.Schema{
66+
SchemaFieldKey: {Type: schema.TypeString, Computed: true},
67+
SchemaOperatorKey: {Type: schema.TypeString, Computed: true},
68+
SchemaValueKey: {Type: schema.TypeString, Computed: true},
69+
SchemaValuesKey: {
70+
Type: schema.TypeList,
71+
Computed: true,
72+
Elem: &schema.Schema{Type: schema.TypeString},
73+
},
74+
},
75+
},
76+
},
5777
},
5878
},
5979
},
80+
6081
"id": {
6182
Type: schema.TypeString,
6283
Optional: true,
@@ -74,52 +95,80 @@ func dataSourceSysdigSecureZone() *schema.Resource {
7495
}
7596

7697
func dataSourceSysdigSecureZoneRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
77-
client, err := getZoneClient(m.(SysdigClients))
98+
clientV2, err := getZoneV2Client(m.(SysdigClients))
7899
if err != nil {
79100
return diag.FromErr(err)
80101
}
81-
82-
var zone *v2.Zone
102+
var zoneV2 *v2.ZoneV2
83103
zoneIDRaw, hasZoneID := d.GetOk("id")
84104
if hasZoneID {
85105
zoneID, err := strconv.Atoi(zoneIDRaw.(string))
86106
if err != nil {
87107
return diag.FromErr(fmt.Errorf("invalid zone id: %s", err))
88108
}
89-
zone, err = client.GetZoneByID(ctx, zoneID)
109+
zoneV2, err = clientV2.GetZoneV2(ctx, zoneID)
90110
if err != nil {
91-
return diag.FromErr(fmt.Errorf("error fetching zone by ID: %s", err))
111+
return diag.FromErr(fmt.Errorf("error fetching zone v2 by ID: %s", err))
92112
}
93113
} else if nameRaw, hasName := d.GetOk("name"); hasName {
94114
name := nameRaw.(string)
95-
zones, err := client.GetZones(ctx, name)
115+
zones, err := clientV2.GetZonesV2(ctx, name)
96116
if err != nil {
97117
return diag.FromErr(fmt.Errorf("error fetching zones: %s", err))
98118
}
99119
for _, z := range zones {
100120
if z.Name == name {
101-
zone = &z
121+
zoneV2 = &z
102122
break
103123
}
104124
}
105-
if zone == nil {
125+
if zoneV2 == nil {
106126
return diag.FromErr(fmt.Errorf("zone with name '%s' not found", name))
107127
}
128+
zoneV2, err = clientV2.GetZoneV2(ctx, zoneV2.ID)
129+
if err != nil {
130+
return diag.FromErr(fmt.Errorf("error fetching zone by name: %s", err))
131+
}
108132
} else {
109133
return diag.FromErr(fmt.Errorf("either id or name must be specified"))
110134
}
111135

112-
d.SetId(fmt.Sprintf("%d", zone.ID))
113-
_ = d.Set(SchemaNameKey, zone.Name)
114-
_ = d.Set(SchemaDescriptionKey, zone.Description)
115-
_ = d.Set(SchemaIsSystemKey, zone.IsSystem)
116-
_ = d.Set(SchemaAuthorKey, zone.Author)
117-
_ = d.Set(SchemaLastModifiedBy, zone.LastModifiedBy)
118-
_ = d.Set(SchemaLastUpdated, time.UnixMilli(zone.LastUpdated).Format(time.RFC3339))
136+
d.SetId(fmt.Sprintf("%d", zoneV2.ID))
137+
_ = d.Set(SchemaNameKey, zoneV2.Name)
138+
_ = d.Set(SchemaDescriptionKey, zoneV2.Description)
139+
_ = d.Set(SchemaIsSystemKey, zoneV2.IsSystem)
140+
_ = d.Set(SchemaAuthorKey, zoneV2.Author)
141+
_ = d.Set(SchemaLastModifiedBy, zoneV2.LastModifiedBy)
142+
_ = d.Set(SchemaLastUpdated, time.UnixMilli(zoneV2.LastUpdated).Format(time.RFC3339))
119143

120-
if err := d.Set(SchemaScopeKey, fromZoneScopesResponse(zone.Scopes)); err != nil {
144+
if err := d.Set(SchemaScopeKey, getZoneScopes(zoneV2)); err != nil {
121145
return diag.FromErr(fmt.Errorf("error setting scope: %s", err))
122146
}
123147

124148
return nil
125149
}
150+
151+
func getZoneScopes(zoneV2 *v2.ZoneV2) []any {
152+
// Build expression lookup by filter ID from the v2 response.
153+
out := make([]any, 0)
154+
if zoneV2 != nil {
155+
for _, s := range zoneV2.Scopes {
156+
for _, f := range s.Filters {
157+
if f.ID != 0 && (len(f.Expressions) > 0 || f.Rules != "") {
158+
var exprs []any
159+
for _, e := range f.Expressions {
160+
exprs = append(exprs, flattenExpressionV2(e))
161+
}
162+
m := map[string]any{
163+
SchemaIDKey: f.ID,
164+
SchemaTargetTypeKey: f.ResourceType,
165+
SchemaRulesKey: f.Rules,
166+
}
167+
m[SchemaExpressionKey] = exprs
168+
out = append(out, m)
169+
}
170+
}
171+
}
172+
}
173+
return out
174+
}

sysdig/data_source_sysdig_secure_zone_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package sysdig_test
44

55
import (
6+
"fmt"
67
"testing"
78

89
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
@@ -43,6 +44,125 @@ func TestAccDataSourceSysdigSecureZone(t *testing.T) {
4344
})
4445
}
4546

47+
func TestAccDataSourceSysdigSecureZone_ByName(t *testing.T) {
48+
zoneName := "Zone_DS_" + randomText(5)
49+
50+
resource.ParallelTest(t, resource.TestCase{
51+
PreCheck: preCheckAnyEnv(t, SysdigSecureApiTokenEnv, SysdigIBMSecureAPIKeyEnv),
52+
ProviderFactories: map[string]func() (*schema.Provider, error){
53+
"sysdig": func() (*schema.Provider, error) {
54+
return sysdig.Provider(), nil
55+
},
56+
},
57+
Steps: []resource.TestStep{
58+
{
59+
Config: testAccDataSourceSecureZoneByName(zoneName),
60+
Check: resource.ComposeTestCheckFunc(
61+
resource.TestCheckResourceAttr(
62+
"data.sysdig_secure_zone.test",
63+
"name",
64+
zoneName,
65+
),
66+
resource.TestCheckResourceAttr(
67+
"data.sysdig_secure_zone.test",
68+
"scope.0.target_type",
69+
"aws",
70+
),
71+
72+
// v2 expressions
73+
resource.TestCheckResourceAttr(
74+
"data.sysdig_secure_zone.test",
75+
"scope.0.expression.#",
76+
"1",
77+
),
78+
resource.TestCheckResourceAttr(
79+
"data.sysdig_secure_zone.test",
80+
"scope.0.expression.0.field",
81+
"organization",
82+
),
83+
),
84+
},
85+
},
86+
})
87+
}
88+
89+
func TestAccDataSourceSysdigSecureZone_ByID(t *testing.T) {
90+
zoneName := "Zone_DS_ID_" + randomText(5)
91+
92+
resource.ParallelTest(t, resource.TestCase{
93+
PreCheck: preCheckAnyEnv(t, SysdigSecureApiTokenEnv, SysdigIBMSecureAPIKeyEnv),
94+
ProviderFactories: map[string]func() (*schema.Provider, error){
95+
"sysdig": func() (*schema.Provider, error) {
96+
return sysdig.Provider(), nil
97+
},
98+
},
99+
Steps: []resource.TestStep{
100+
{
101+
Config: testAccDataSourceSecureZoneByID(zoneName),
102+
Check: resource.ComposeTestCheckFunc(
103+
resource.TestCheckResourceAttrSet(
104+
"data.sysdig_secure_zone.test",
105+
"id",
106+
),
107+
resource.TestCheckResourceAttr(
108+
"data.sysdig_secure_zone.test",
109+
"scope.0.expression.#",
110+
"1",
111+
),
112+
),
113+
},
114+
},
115+
})
116+
}
117+
118+
func testAccDataSourceSecureZoneByName(name string) string {
119+
return fmt.Sprintf(`
120+
resource "sysdig_secure_zone" "test" {
121+
name = "%s"
122+
description = "ds acceptance test"
123+
124+
scope {
125+
target_type = "aws"
126+
127+
expression {
128+
field = "organization"
129+
operator = "in"
130+
values = ["o1", "o2"]
131+
}
132+
}
133+
}
134+
135+
data "sysdig_secure_zone" "test" {
136+
depends_on = [sysdig_secure_zone.test]
137+
name = "%s"
138+
}
139+
`, name, name)
140+
}
141+
142+
func testAccDataSourceSecureZoneByID(name string) string {
143+
return fmt.Sprintf(`
144+
resource "sysdig_secure_zone" "test" {
145+
name = "%s"
146+
description = "ds acceptance test"
147+
148+
scope {
149+
target_type = "aws"
150+
151+
expression {
152+
field = "organization"
153+
operator = "in"
154+
values = ["o1", "o2"]
155+
}
156+
}
157+
}
158+
159+
data "sysdig_secure_zone" "test" {
160+
depends_on = [sysdig_secure_zone.test]
161+
id = sysdig_secure_zone.test.id
162+
}
163+
`, name)
164+
}
165+
46166
func testAccDataSourceSysdigSecureZoneConfig() string {
47167
return `
48168
resource "sysdig_secure_zone" "sample" {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package sysdig
2+
3+
import (
4+
"testing"
5+
6+
v2 "github.com/draios/terraform-provider-sysdig/sysdig/internal/client/v2"
7+
)
8+
9+
func TestGetZoneScopes_MatchByID(t *testing.T) {
10+
zoneV2 := &v2.ZoneV2{
11+
Scopes: []v2.ScopeV2{
12+
{
13+
Filters: []v2.FilterV2{
14+
{
15+
ID: 10,
16+
ResourceType: "kubernetes",
17+
Expressions: []v2.ExpressionV2{
18+
{Field: "cluster", Operator: "in", Values: []string{"a"}},
19+
},
20+
},
21+
{
22+
ID: 20,
23+
ResourceType: "kubernetes",
24+
Expressions: []v2.ExpressionV2{
25+
{Field: "cluster", Operator: "in", Values: []string{"b"}},
26+
},
27+
},
28+
},
29+
},
30+
},
31+
}
32+
33+
result := getZoneScopes(zoneV2)
34+
if len(result) != 2 {
35+
t.Fatalf("expected 2 scopes, got %d", len(result))
36+
}
37+
38+
// Verify each scope got its own expressions by checking the ID-expression pairing.
39+
for _, raw := range result {
40+
scope := raw.(map[string]any)
41+
id := scope[SchemaIDKey].(int)
42+
exprs, ok := scope[SchemaExpressionKey].([]any)
43+
if !ok || len(exprs) != 1 {
44+
t.Fatalf("scope ID=%d: expected 1 expression, got %v", id, scope[SchemaExpressionKey])
45+
}
46+
expr := exprs[0].(map[string]any)
47+
vals := expr["values"].([]string)
48+
if id == 10 && vals[0] != "a" {
49+
t.Errorf("scope ID=10: expected values [a], got %v", vals)
50+
}
51+
if id == 20 && vals[0] != "b" {
52+
t.Errorf("scope ID=20: expected values [b], got %v", vals)
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)