Skip to content

Commit 5ece1a8

Browse files
[OMG-709] Add datadog_team_connection and datadog_team_sync resources
Introduce two new Terraform resources for managing team connections and team sync configurations, with acceptance tests and example files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c3a9023 commit 5ece1a8

18 files changed

Lines changed: 1923 additions & 0 deletions

datadog/fwprovider/framework_provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ var Resources = []func() resource.Resource{
7676
NewTeamPermissionSettingResource,
7777
NewTeamResource,
7878
NewTeamHierarchyLinksResource,
79+
NewTeamConnectionResource,
80+
NewTeamSyncResource,
7981
NewUserRoleResource,
8082
NewSecurityMonitoringSuppressionResource,
8183
NewSecurityMonitoringCriticalAssetResource,
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
package fwprovider
2+
3+
import (
4+
"context"
5+
6+
"github.com/DataDog/datadog-api-client-go/v2/api/datadogV2"
7+
"github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator"
8+
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
9+
frameworkPath "github.com/hashicorp/terraform-plugin-framework/path"
10+
"github.com/hashicorp/terraform-plugin-framework/resource"
11+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
12+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
13+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
14+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
15+
"github.com/hashicorp/terraform-plugin-framework/types"
16+
17+
"github.com/terraform-providers/terraform-provider-datadog/datadog/internal/utils"
18+
)
19+
20+
var (
21+
_ resource.ResourceWithConfigure = &teamConnectionResource{}
22+
_ resource.ResourceWithImportState = &teamConnectionResource{}
23+
)
24+
25+
type teamConnectionResource struct {
26+
Api *datadogV2.TeamsApi
27+
Auth context.Context
28+
}
29+
30+
type teamConnectionModel struct {
31+
ID types.String `tfsdk:"id"`
32+
Team *teamConnectionRef `tfsdk:"team"`
33+
ConnectedTeam *teamConnectionRef `tfsdk:"connected_team"`
34+
Source types.String `tfsdk:"source"`
35+
}
36+
37+
type teamConnectionRef struct {
38+
ID types.String `tfsdk:"id"`
39+
Type types.String `tfsdk:"type"`
40+
}
41+
42+
func NewTeamConnectionResource() resource.Resource {
43+
return &teamConnectionResource{}
44+
}
45+
46+
func (r *teamConnectionResource) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) {
47+
providerData, _ := request.ProviderData.(*FrameworkProvider)
48+
r.Api = providerData.DatadogApiInstances.GetTeamsApiV2()
49+
r.Auth = providerData.Auth
50+
}
51+
52+
func (r *teamConnectionResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
53+
response.TypeName = "team_connection"
54+
}
55+
56+
func (r *teamConnectionResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) {
57+
response.Schema = schema.Schema{
58+
Description: "Provides a Datadog Team Connection resource. This can be used to create and manage connections between a Datadog team and an external team (e.g. GitHub).",
59+
Attributes: map[string]schema.Attribute{
60+
"id": utils.ResourceIDAttribute(),
61+
"source": schema.StringAttribute{
62+
Optional: true,
63+
Computed: true,
64+
Description: "The source of the connection (e.g. github).",
65+
PlanModifiers: []planmodifier.String{
66+
stringplanmodifier.RequiresReplace(),
67+
},
68+
},
69+
},
70+
Blocks: map[string]schema.Block{
71+
"team": schema.SingleNestedBlock{
72+
Description: "The Datadog team reference.",
73+
Validators: []validator.Object{
74+
objectvalidator.IsRequired(),
75+
},
76+
Attributes: map[string]schema.Attribute{
77+
"id": schema.StringAttribute{
78+
Required: true,
79+
Description: "The ID of the Datadog team.",
80+
PlanModifiers: []planmodifier.String{
81+
stringplanmodifier.RequiresReplace(),
82+
},
83+
},
84+
"type": schema.StringAttribute{
85+
Required: true,
86+
Description: "The resource type of the Datadog team.",
87+
PlanModifiers: []planmodifier.String{
88+
stringplanmodifier.RequiresReplace(),
89+
},
90+
Validators: []validator.String{
91+
stringvalidator.OneOf("team"),
92+
},
93+
},
94+
},
95+
},
96+
"connected_team": schema.SingleNestedBlock{
97+
Description: "The external connected team reference (e.g. a GitHub team).",
98+
Validators: []validator.Object{
99+
objectvalidator.IsRequired(),
100+
},
101+
Attributes: map[string]schema.Attribute{
102+
"id": schema.StringAttribute{
103+
Required: true,
104+
Description: "The ID of the external connected team.",
105+
PlanModifiers: []planmodifier.String{
106+
stringplanmodifier.RequiresReplace(),
107+
},
108+
},
109+
"type": schema.StringAttribute{
110+
Required: true,
111+
Description: "The resource type of the external connected team.",
112+
PlanModifiers: []planmodifier.String{
113+
stringplanmodifier.RequiresReplace(),
114+
},
115+
Validators: []validator.String{
116+
stringvalidator.OneOf("github_team"),
117+
},
118+
},
119+
},
120+
},
121+
},
122+
}
123+
}
124+
125+
func (r *teamConnectionResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
126+
resource.ImportStatePassthroughID(ctx, frameworkPath.Root("id"), request, response)
127+
}
128+
129+
func (r *teamConnectionResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
130+
var state teamConnectionModel
131+
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
132+
if response.Diagnostics.HasError() {
133+
return
134+
}
135+
136+
id := state.ID.ValueString()
137+
opts := datadogV2.NewListTeamConnectionsOptionalParameters().WithFilterConnectionIds([]string{id})
138+
139+
resp, httpResp, err := r.Api.ListTeamConnections(r.Auth, *opts)
140+
if err != nil {
141+
if httpResp != nil && httpResp.StatusCode == 404 {
142+
response.State.RemoveResource(ctx)
143+
return
144+
}
145+
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error retrieving TeamConnection"))
146+
return
147+
}
148+
if err := utils.CheckForUnparsed(resp); err != nil {
149+
response.Diagnostics.AddError("response contains unparsedObject", err.Error())
150+
return
151+
}
152+
153+
data := resp.GetData()
154+
if len(data) == 0 {
155+
response.State.RemoveResource(ctx)
156+
return
157+
}
158+
159+
r.updateState(&state, &data[0])
160+
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
161+
}
162+
163+
func (r *teamConnectionResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
164+
var state teamConnectionModel
165+
response.Diagnostics.Append(request.Plan.Get(ctx, &state)...)
166+
if response.Diagnostics.HasError() {
167+
return
168+
}
169+
170+
body := r.buildCreateRequestBody(&state)
171+
172+
resp, _, err := r.Api.CreateTeamConnections(r.Auth, *body)
173+
if err != nil {
174+
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error creating TeamConnection"))
175+
return
176+
}
177+
if err := utils.CheckForUnparsed(resp); err != nil {
178+
response.Diagnostics.AddError("response contains unparsedObject", err.Error())
179+
return
180+
}
181+
182+
data := resp.GetData()
183+
if len(data) == 0 {
184+
response.Diagnostics.AddError("empty response", "no team connection returned in create response")
185+
return
186+
}
187+
188+
r.updateState(&state, &data[0])
189+
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
190+
}
191+
192+
func (r *teamConnectionResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
193+
response.Diagnostics.AddError("Update not supported for this resource", "Team connections are immutable. All fields require replacement.")
194+
}
195+
196+
func (r *teamConnectionResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
197+
var state teamConnectionModel
198+
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
199+
if response.Diagnostics.HasError() {
200+
return
201+
}
202+
203+
id := state.ID.ValueString()
204+
deleteItem := datadogV2.NewTeamConnectionDeleteRequestDataItem(id, datadogV2.TEAMCONNECTIONTYPE_TEAM_CONNECTION)
205+
body := datadogV2.NewTeamConnectionDeleteRequest([]datadogV2.TeamConnectionDeleteRequestDataItem{*deleteItem})
206+
207+
httpResp, err := r.Api.DeleteTeamConnections(r.Auth, *body)
208+
if err != nil {
209+
if httpResp != nil && httpResp.StatusCode == 404 {
210+
return
211+
}
212+
response.Diagnostics.Append(utils.FrameworkErrorDiag(err, "error deleting TeamConnection"))
213+
return
214+
}
215+
}
216+
217+
func (r *teamConnectionResource) updateState(state *teamConnectionModel, conn *datadogV2.TeamConnection) {
218+
state.ID = types.StringValue(conn.GetId())
219+
220+
if attrs, ok := conn.GetAttributesOk(); ok {
221+
if source, ok := attrs.GetSourceOk(); ok {
222+
state.Source = types.StringValue(*source)
223+
}
224+
}
225+
226+
if rels, ok := conn.GetRelationshipsOk(); ok {
227+
if team, ok := rels.GetTeamOk(); ok {
228+
if data, ok := team.GetDataOk(); ok {
229+
if state.Team == nil {
230+
state.Team = &teamConnectionRef{}
231+
}
232+
state.Team.ID = types.StringValue(data.GetId())
233+
state.Team.Type = types.StringValue(string(data.GetType()))
234+
}
235+
}
236+
if connTeam, ok := rels.GetConnectedTeamOk(); ok {
237+
if data, ok := connTeam.GetDataOk(); ok {
238+
if state.ConnectedTeam == nil {
239+
state.ConnectedTeam = &teamConnectionRef{}
240+
}
241+
state.ConnectedTeam.ID = types.StringValue(data.GetId())
242+
state.ConnectedTeam.Type = types.StringValue(string(data.GetType()))
243+
}
244+
}
245+
}
246+
}
247+
248+
func (r *teamConnectionResource) buildCreateRequestBody(state *teamConnectionModel) *datadogV2.TeamConnectionCreateRequest {
249+
createData := datadogV2.NewTeamConnectionCreateData(datadogV2.TEAMCONNECTIONTYPE_TEAM_CONNECTION)
250+
251+
attrs := datadogV2.NewTeamConnectionAttributes()
252+
if !state.Source.IsNull() && !state.Source.IsUnknown() {
253+
attrs.SetSource(state.Source.ValueString())
254+
}
255+
createData.SetAttributes(*attrs)
256+
257+
teamRefData := datadogV2.NewTeamRefData(state.Team.ID.ValueString(), datadogV2.TeamRefDataType(state.Team.Type.ValueString()))
258+
teamRef := datadogV2.NewTeamRef()
259+
teamRef.SetData(*teamRefData)
260+
261+
connTeamRefData := datadogV2.NewConnectedTeamRefData(state.ConnectedTeam.ID.ValueString(), datadogV2.ConnectedTeamRefDataType(state.ConnectedTeam.Type.ValueString()))
262+
connTeamRef := datadogV2.NewConnectedTeamRef()
263+
connTeamRef.SetData(*connTeamRefData)
264+
265+
rels := datadogV2.NewTeamConnectionRelationships()
266+
rels.SetTeam(*teamRef)
267+
rels.SetConnectedTeam(*connTeamRef)
268+
createData.SetRelationships(*rels)
269+
270+
return datadogV2.NewTeamConnectionCreateRequest([]datadogV2.TeamConnectionCreateData{*createData})
271+
}

0 commit comments

Comments
 (0)