Skip to content

Commit 48c5b13

Browse files
Feature: Provider level teams configuration (#589)
* Adding support for improved features * Adding ability to merge provider level teams * Adding support for mergeing team details * Add field to provider * Fixing docs
1 parent 6039d36 commit 48c5b13

File tree

12 files changed

+213
-4
lines changed

12 files changed

+213
-4
lines changed

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,5 @@ provider "signalfx" {
106106
- `retry_wait_max_seconds` (Number) Maximum retry wait for a single HTTP call in seconds. Defaults to 30
107107
- `retry_wait_min_seconds` (Number) Minimum retry wait for a single HTTP call in seconds. Defaults to 1
108108
- `tags` (List of String) Allows for Tags to be added by default to resources that allow for tags to be included. If there is already tags configured, the global tags are added in prefix.
109+
- `teams` (List of String) Allows for teams to be defined at a provider level, and apply to all applicable resources created.
109110
- `timeout_seconds` (Number) Timeout duration for a single HTTP call in seconds. Defaults to 120

internal/definition/detector/resource.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func resourceCreate(ctx context.Context, data *schema.ResourceData, meta any) (i
6868
pmeta.LoadProviderTags(ctx, meta),
6969
dt.Tags,
7070
),
71-
Teams: dt.Teams,
71+
Teams: pmeta.MergeProviderTeams(ctx, meta, dt.Teams),
7272
VisualizationOptions: dt.VisualizationOptions,
7373
ParentDetectorId: dt.ParentDetectorId,
7474
DetectorOrigin: dt.DetectorOrigin,
@@ -152,7 +152,7 @@ func resourceUpdate(ctx context.Context, data *schema.ResourceData, meta any) (i
152152
pmeta.LoadProviderTags(ctx, meta),
153153
dt.Tags,
154154
),
155-
Teams: dt.Teams,
155+
Teams: pmeta.MergeProviderTeams(ctx, meta, dt.Teams),
156156
VisualizationOptions: dt.VisualizationOptions,
157157
ParentDetectorId: dt.ParentDetectorId,
158158
DetectorOrigin: dt.DetectorOrigin,

internal/definition/provider/provider.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,15 @@ func New() *schema.Provider {
108108
Optional: true,
109109
Description: "Allows for Tags to be added by default to resources that allow for tags to be included. If there is already tags configured, the global tags are added in prefix.",
110110
},
111+
"teams": {
112+
Type: schema.TypeList,
113+
Elem: &schema.Schema{
114+
Type: schema.TypeString,
115+
ValidateFunc: validation.StringIsNotEmpty,
116+
},
117+
Optional: true,
118+
Description: "Allows for teams to be defined at a provider level, and apply to all applicable resources created.",
119+
},
111120
},
112121
ResourcesMap: map[string]*schema.Resource{
113122
team.ResourceName: team.NewResource(),
@@ -126,6 +135,7 @@ func configureProvider(ctx context.Context, data *schema.ResourceData) (any, dia
126135
Password: data.Get("password").(string),
127136
OrganizationID: data.Get("organization_id").(string),
128137
Tags: convert.SliceAll(data.Get("tags").([]any), convert.ToString),
138+
Teams: convert.SliceAll(data.Get("teams").([]any), convert.ToString),
129139
}
130140

131141
for _, lookup := range pmeta.NewDefaultProviderLookups() {

internal/definition/provider/provider_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func TestProviderConfiguration(t *testing.T) {
6767
APIURL: "api.us.signalfx.com",
6868
CustomAppURL: "https://app.signalfx.com",
6969
Tags: []string{},
70+
Teams: []string{},
7071
},
7172
expect: nil,
7273
},
@@ -106,6 +107,7 @@ func TestProviderConfiguration(t *testing.T) {
106107
"battery",
107108
"staple",
108109
},
110+
Teams: []string{},
109111
},
110112
expect: nil,
111113
},

internal/feature/definitions.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright Splunk, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package feature
5+
6+
// This file is used to define all the existing feature previews
7+
// that are currently used within the provider.
8+
// Having the values defined in one place makes it easier to
9+
// update and maintain these values.
10+
// Furthermore, if these are to be used within an autogen script
11+
// then it does not require loading each individual module
12+
// which will speed up documentation generation.
13+
//
14+
// When defining a new feature preview, it should:
15+
// - named in terms of scope and and feature
16+
// - ie: `provider.<feature>`, `detectors.<feature>`
17+
// - Add a description that informs the user of what will happen once enabled.
18+
// - Set the version added in (this helps sorting oldest previews to newest)
19+
20+
const (
21+
PreviewProviderTeams = "provider.teams"
22+
)
23+
24+
var (
25+
_ = GetGlobalRegistry().MustRegister(
26+
PreviewProviderTeams,
27+
WithPreviewDescription("Allows for team(s) to set at a provider level, and apply to all applicable resources"),
28+
WithPreviewAddInVersion("v9.9.1"),
29+
)
30+
)

internal/feature/preview.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33

44
package feature
55

6-
import "sync/atomic"
6+
import (
7+
"errors"
8+
"sync/atomic"
9+
10+
tfext "github.com/splunk-terraform/terraform-provider-signalfx/internal/tfextension"
11+
)
712

813
// Preview allows for features to be guarded
914
// to allow users to opt in for the new functionality.
@@ -39,6 +44,18 @@ func NewPreview(opts ...PreviewOption) (*Preview, error) {
3944
return p, nil
4045
}
4146

47+
func NewPreviewLogFields(name string, p *Preview) tfext.LogFields {
48+
lf := tfext.NewLogFields().Field("feature.name", name)
49+
if p != nil {
50+
lf = lf.
51+
Field("feature.enabled", p.Enabled()).
52+
Field("feature.ga", p.GlobalAvailable())
53+
} else {
54+
lf = lf.Error(errors.New("feature preview is unset"))
55+
}
56+
return lf
57+
}
58+
4259
func (p Preview) Enabled() bool {
4360
return p.enabled.Load()
4461
}

internal/feature/preview_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88

99
"github.com/stretchr/testify/assert"
1010
"github.com/stretchr/testify/require"
11+
12+
tfext "github.com/splunk-terraform/terraform-provider-signalfx/internal/tfextension"
1113
)
1214

1315
func TestNewDefaultPreview(t *testing.T) {
@@ -24,3 +26,46 @@ func TestNewDefaultPreview(t *testing.T) {
2426
p.SetEnabled(true)
2527
assert.True(t, p.Enabled(), "Must be enabled once set")
2628
}
29+
30+
func TestPreviewLogFields(t *testing.T) {
31+
t.Parallel()
32+
33+
for _, tc := range []struct {
34+
name string
35+
preview *Preview
36+
expect tfext.LogFields
37+
}{
38+
{
39+
name: "no preview set",
40+
preview: nil,
41+
expect: tfext.LogFields{
42+
"feature.name": "feature.example-01",
43+
"error": "feature preview is unset",
44+
},
45+
},
46+
{
47+
name: "preview set",
48+
preview: func(tb testing.TB) *Preview {
49+
p, err := NewPreview()
50+
assert.NoError(tb, err, "Must not error creating preview")
51+
return p
52+
}(t),
53+
expect: tfext.LogFields{
54+
"feature.name": "feature.example-01",
55+
"feature.ga": false,
56+
"feature.enabled": false,
57+
},
58+
},
59+
} {
60+
t.Run(tc.name, func(t *testing.T) {
61+
t.Parallel()
62+
63+
assert.Equal(
64+
t,
65+
tc.expect,
66+
NewPreviewLogFields("feature.example-01", tc.preview),
67+
"Must match the expected values",
68+
)
69+
})
70+
}
71+
}

internal/providermeta/meta.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ import (
88
"errors"
99
"net/url"
1010
"path"
11+
"slices"
1112
"strings"
1213

1314
"github.com/hashicorp/terraform-plugin-log/tflog"
1415
"github.com/signalfx/signalfx-go"
1516
"github.com/signalfx/signalfx-go/sessiontoken"
1617
"go.uber.org/multierr"
1718

19+
"github.com/splunk-terraform/terraform-provider-signalfx/internal/common"
1820
"github.com/splunk-terraform/terraform-provider-signalfx/internal/feature"
1921
tfext "github.com/splunk-terraform/terraform-provider-signalfx/internal/tfextension"
2022
)
@@ -45,6 +47,7 @@ type Meta struct {
4547
Password string `json:"password"`
4648
OrganizationID string `json:"org_id"`
4749
Tags []string `json:"tags"`
50+
Teams []string `json:"teams"`
4851
}
4952

5053
// LoadClient returns the configured [signalfx.Client] ready to use.
@@ -95,7 +98,12 @@ func LoadPreviewRegistry(ctx context.Context, meta any) *feature.Registry {
9598
//
9699
// Requires preview to be enabled in order to return values.
97100
func LoadProviderTags(ctx context.Context, meta any) []string {
98-
if g, ok := LoadPreviewRegistry(ctx, meta).Get(previewGlobalTagsKey); ok && !g.Enabled() {
101+
if g, ok := LoadPreviewRegistry(ctx, meta).Get(previewGlobalTagsKey); !ok || !g.Enabled() {
102+
tflog.Debug(
103+
ctx,
104+
"Feature Preview is not enabled, using default value",
105+
feature.NewPreviewLogFields(feature.PreviewProviderTeams, g),
106+
)
99107
return nil
100108
}
101109

@@ -134,6 +142,28 @@ func (m *Meta) LoadSessionToken(ctx context.Context) (string, error) {
134142
return resp.AccessToken, nil
135143
}
136144

145+
// MergeProviderTeams will prepend the provider set teams to the resource level teams.
146+
//
147+
// Note: This currently requires the feature preview `feature.PreviewProviderTeam` to be enabled.
148+
func MergeProviderTeams(ctx context.Context, meta any, teams []string) []string {
149+
if g, ok := LoadPreviewRegistry(ctx, meta).Get(feature.PreviewProviderTeams); !ok || !g.Enabled() {
150+
tflog.Debug(
151+
ctx,
152+
"Feature Preview is not enabled, using default value",
153+
feature.NewPreviewLogFields(feature.PreviewProviderTeams, g),
154+
)
155+
return teams
156+
}
157+
158+
os := common.NewOrderedSet[string]()
159+
if m, ok := meta.(*Meta); ok {
160+
os.Append(m.Teams...)
161+
}
162+
163+
os.Append(teams...)
164+
return slices.Collect(os.All())
165+
}
166+
137167
func (m *Meta) Validate() (errs error) {
138168
if m.AuthToken == "" && (m.Email == "" || m.Password == "") {
139169
errs = multierr.Append(errs, errors.New("missing auth token or email and password"))

internal/providermeta/meta_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,3 +325,58 @@ func TestLoadProviderTags(t *testing.T) {
325325
})
326326
}
327327
}
328+
329+
func TestMergeProviderTeams(t *testing.T) {
330+
t.Parallel()
331+
332+
for _, tc := range []struct {
333+
name string
334+
meta any
335+
teams []string
336+
expect []string
337+
}{
338+
{
339+
name: "no details provided",
340+
meta: nil,
341+
teams: nil,
342+
expect: nil,
343+
},
344+
{
345+
name: "provide has details, preview not enabled",
346+
meta: &Meta{
347+
reg: func() *feature.Registry {
348+
r := feature.NewRegistry()
349+
_ = r.MustRegister(feature.PreviewProviderTeams)
350+
return r
351+
}(),
352+
Teams: []string{"team-00", "team-02", "team-03"},
353+
},
354+
teams: []string{"team-01", "team-02"},
355+
expect: []string{"team-01", "team-02"},
356+
},
357+
{
358+
name: "provider has details, preview enabled",
359+
meta: &Meta{
360+
reg: func() *feature.Registry {
361+
r := feature.NewRegistry()
362+
_ = r.MustRegister(feature.PreviewProviderTeams, feature.WithPreviewGlobalAvailable())
363+
return r
364+
}(),
365+
Teams: []string{"team-00", "team-02", "team-03"},
366+
},
367+
teams: []string{"team-01", "team-02"},
368+
expect: []string{"team-00", "team-02", "team-03", "team-01"},
369+
},
370+
} {
371+
t.Run(tc.name, func(t *testing.T) {
372+
t.Parallel()
373+
374+
assert.Equal(
375+
t,
376+
tc.expect,
377+
MergeProviderTeams(t.Context(), tc.meta, tc.teams),
378+
"Must match the expected details",
379+
)
380+
})
381+
}
382+
}

signalfx/provider.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,15 @@ func Provider() *schema.Provider {
122122
Optional: true,
123123
Description: "Allows for Tags to be added by default to resources that allow for tags to be included. If there is already tags configured, the global tags are added in prefix.",
124124
},
125+
"teams": {
126+
Type: schema.TypeList,
127+
Elem: &schema.Schema{
128+
Type: schema.TypeString,
129+
ValidateFunc: validation.StringIsNotEmpty,
130+
},
131+
Optional: true,
132+
Description: "Allows for teams to be defined at a provider level, and apply to all applicable resources created.",
133+
},
125134
},
126135
DataSourcesMap: map[string]*schema.Resource{
127136
"signalfx_dimension_values": dataSourceDimensionValues(),
@@ -172,6 +181,7 @@ func signalfxConfigure(data *schema.ResourceData) (interface{}, error) {
172181
Password: data.Get("password").(string),
173182
OrganizationID: data.Get("organization_id").(string),
174183
Tags: convert.SliceAll(data.Get("tags").([]any), convert.ToString),
184+
Teams: convert.SliceAll(data.Get("teams").([]any), convert.ToString),
175185
}
176186

177187
// /etc/signalfx.conf has the lowest priority

signalfx/resource_signalfx_dashboard_group.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1414
dashboard_group "github.com/signalfx/signalfx-go/dashboard_group"
1515
"github.com/splunk-terraform/terraform-provider-signalfx/internal/convert"
16+
pmeta "github.com/splunk-terraform/terraform-provider-signalfx/internal/providermeta"
1617
)
1718

1819
func dashboardGroupResource() *schema.Resource {
@@ -408,6 +409,7 @@ func dashboardgroupCreate(d *schema.ResourceData, meta interface{}) error {
408409
config := meta.(*signalfxConfig)
409410
payload := getPayloadDashboardGroup(d)
410411

412+
payload.Teams = pmeta.MergeProviderTeams(context.TODO(), meta, payload.Teams)
411413
debugOutput, _ := json.Marshal(payload)
412414
log.Printf("[DEBUG] SignalFx: Dashboard Group Create Payload: %s", debugOutput)
413415

@@ -574,6 +576,7 @@ func dashboardgroupUpdate(d *schema.ResourceData, meta interface{}) error {
574576
if err != nil {
575577
return fmt.Errorf("failed to get current dashboard list for %s: %v", d.Id(), err)
576578
}
579+
payload.Teams = pmeta.MergeProviderTeams(context.TODO(), meta, payload.Teams)
577580

578581
payload.DashboardConfigs = append(payload.DashboardConfigs, nonMirroredDashes...)
579582
debugOutput, _ := json.Marshal(payload)

signalfx/resource_signalfx_detector.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,12 @@ func detectorCreate(d *schema.ResourceData, meta interface{}) error {
502502
payload.Tags,
503503
)
504504

505+
payload.Teams = pmeta.MergeProviderTeams(
506+
context.TODO(),
507+
meta,
508+
payload.Tags,
509+
)
510+
505511
debugOutput, _ := json.Marshal(payload)
506512
log.Printf("[DEBUG] SignalFx: Create Detector Payload: %s", string(debugOutput))
507513

0 commit comments

Comments
 (0)