Skip to content

Commit b22c8ae

Browse files
Implement contentful_team resource with name and description properties
Co-authored-by: mvantellingen <245297+mvantellingen@users.noreply.github.com>
1 parent 190e1df commit b22c8ae

File tree

7 files changed

+1463
-2
lines changed

7 files changed

+1463
-2
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
kind: Added
2+
body: Add new contentful_team resource with name and description properties
3+
time: 2025-08-07T07:41:48.590843179Z

internal/provider/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/labd/terraform-provider-contentful/internal/resources/preview_environment"
2424
"github.com/labd/terraform-provider-contentful/internal/resources/role"
2525
"github.com/labd/terraform-provider-contentful/internal/resources/space"
26+
"github.com/labd/terraform-provider-contentful/internal/resources/team"
2627
"github.com/labd/terraform-provider-contentful/internal/resources/webhook"
2728
"github.com/labd/terraform-provider-contentful/internal/utils"
2829
)
@@ -161,6 +162,7 @@ func (c contentfulProvider) Resources(_ context.Context) []func() resource.Resou
161162
preview_environment.NewPreviewEnvironmentResource,
162163
role.NewRoleResource,
163164
space.NewSpaceResource,
165+
team.NewTeamResource,
164166
webhook.NewWebhookResource,
165167
}
166168
}

internal/resources/team/models.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package team
2+
3+
import (
4+
"github.com/hashicorp/terraform-plugin-framework/types"
5+
6+
"github.com/labd/terraform-provider-contentful/internal/sdk"
7+
)
8+
9+
// Team is the main resource schema data
10+
type Team struct {
11+
ID types.String `tfsdk:"id"`
12+
Version types.Int64 `tfsdk:"version"`
13+
Name types.String `tfsdk:"name"`
14+
Description types.String `tfsdk:"description"`
15+
}
16+
17+
// Import populates the Team struct from an SDK team object
18+
func (t *Team) Import(team *sdk.Team) {
19+
t.ID = types.StringValue(team.Sys.Id)
20+
t.Version = types.Int64Value(int64(team.Sys.Version))
21+
t.Name = types.StringValue(team.Name)
22+
23+
if team.Description != nil {
24+
t.Description = types.StringValue(*team.Description)
25+
} else {
26+
t.Description = types.StringNull()
27+
}
28+
}
29+
30+
// DraftForCreate creates a TeamCreate object for creating a new team
31+
func (t *Team) DraftForCreate() sdk.TeamCreate {
32+
draft := sdk.TeamCreate{
33+
Name: t.Name.ValueString(),
34+
}
35+
36+
if !t.Description.IsNull() && !t.Description.IsUnknown() {
37+
description := t.Description.ValueString()
38+
draft.Description = &description
39+
}
40+
41+
return draft
42+
}
43+
44+
// DraftForUpdate creates a TeamUpdate object for updating an existing team
45+
func (t *Team) DraftForUpdate() sdk.TeamUpdate {
46+
draft := sdk.TeamUpdate{
47+
Name: t.Name.ValueString(),
48+
}
49+
50+
if !t.Description.IsNull() && !t.Description.IsUnknown() {
51+
description := t.Description.ValueString()
52+
draft.Description = &description
53+
}
54+
55+
return draft
56+
}
Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
package team
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/path"
8+
"github.com/hashicorp/terraform-plugin-framework/resource"
9+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
10+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
11+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
12+
"github.com/hashicorp/terraform-plugin-framework/types"
13+
14+
"github.com/labd/terraform-provider-contentful/internal/sdk"
15+
"github.com/labd/terraform-provider-contentful/internal/utils"
16+
)
17+
18+
// Ensure the implementation satisfies the expected interfaces.
19+
var (
20+
_ resource.Resource = &teamResource{}
21+
_ resource.ResourceWithConfigure = &teamResource{}
22+
_ resource.ResourceWithImportState = &teamResource{}
23+
)
24+
25+
func NewTeamResource() resource.Resource {
26+
return &teamResource{}
27+
}
28+
29+
// teamResource is the resource implementation.
30+
type teamResource struct {
31+
client *sdk.ClientWithResponses
32+
organizationId string
33+
}
34+
35+
func (t *teamResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
36+
response.TypeName = request.ProviderTypeName + "_team"
37+
}
38+
39+
func (t *teamResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) {
40+
response.Schema = schema.Schema{
41+
Description: "A Contentful Team represents a team in a Contentful organization.",
42+
Attributes: map[string]schema.Attribute{
43+
"id": schema.StringAttribute{
44+
Computed: true,
45+
Description: "Team ID",
46+
PlanModifiers: []planmodifier.String{
47+
stringplanmodifier.UseStateForUnknown(),
48+
},
49+
},
50+
"version": schema.Int64Attribute{
51+
Computed: true,
52+
Description: "The current version of the team",
53+
},
54+
"name": schema.StringAttribute{
55+
Required: true,
56+
Description: "Name of the team",
57+
},
58+
"description": schema.StringAttribute{
59+
Optional: true,
60+
Description: "Description of the team",
61+
},
62+
},
63+
}
64+
}
65+
66+
func (t *teamResource) Configure(_ context.Context, request resource.ConfigureRequest, _ *resource.ConfigureResponse) {
67+
if request.ProviderData == nil {
68+
return
69+
}
70+
71+
data := request.ProviderData.(utils.ProviderData)
72+
t.client = data.Client
73+
t.organizationId = data.OrganizationId
74+
}
75+
76+
func (t *teamResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
77+
// Get plan values
78+
var plan Team
79+
response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...)
80+
if response.Diagnostics.HasError() {
81+
return
82+
}
83+
84+
// Check if organization ID is configured
85+
if t.organizationId == "" {
86+
response.Diagnostics.AddError(
87+
"Organization ID not configured",
88+
"The organization_id must be set in the provider configuration to create teams",
89+
)
90+
return
91+
}
92+
93+
// Create the team
94+
draft := plan.DraftForCreate()
95+
96+
resp, err := t.client.CreateTeamWithResponse(ctx, t.organizationId, draft)
97+
if err != nil {
98+
response.Diagnostics.AddError(
99+
"Error creating team",
100+
"Could not create team: "+err.Error(),
101+
)
102+
return
103+
}
104+
105+
if resp.StatusCode() != 201 {
106+
response.Diagnostics.AddError(
107+
"Error creating team",
108+
fmt.Sprintf("Received unexpected status code: %d", resp.StatusCode()),
109+
)
110+
return
111+
}
112+
113+
// Map response to state
114+
plan.Import(resp.JSON201)
115+
116+
// Set state
117+
response.Diagnostics.Append(response.State.Set(ctx, plan)...)
118+
}
119+
120+
func (t *teamResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
121+
// Get current state
122+
var state Team
123+
diags := request.State.Get(ctx, &state)
124+
response.Diagnostics.Append(diags...)
125+
if response.Diagnostics.HasError() {
126+
return
127+
}
128+
129+
// Check if organization ID is configured
130+
if t.organizationId == "" {
131+
response.Diagnostics.AddError(
132+
"Organization ID not configured",
133+
"The organization_id must be set in the provider configuration to read teams",
134+
)
135+
return
136+
}
137+
138+
resp, err := t.client.GetTeamWithResponse(ctx, t.organizationId, state.ID.ValueString())
139+
if err != nil {
140+
response.Diagnostics.AddError(
141+
"Error reading team",
142+
"Could not read team: "+err.Error(),
143+
)
144+
return
145+
}
146+
147+
// Handle 404 Not Found
148+
if resp.StatusCode() == 404 {
149+
response.Diagnostics.AddWarning(
150+
"Team not found",
151+
fmt.Sprintf("Team %s was not found, removing from state",
152+
state.ID.ValueString()),
153+
)
154+
response.State.RemoveResource(ctx)
155+
return
156+
}
157+
158+
if resp.StatusCode() != 200 {
159+
response.Diagnostics.AddError(
160+
"Error reading team",
161+
fmt.Sprintf("Received unexpected status code: %d", resp.StatusCode()),
162+
)
163+
return
164+
}
165+
166+
// Map response to state
167+
state.Import(resp.JSON200)
168+
169+
// Set state
170+
response.Diagnostics.Append(response.State.Set(ctx, state)...)
171+
}
172+
173+
func (t *teamResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
174+
// Get plan values
175+
var plan Team
176+
response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...)
177+
if response.Diagnostics.HasError() {
178+
return
179+
}
180+
181+
// Get current state
182+
var state Team
183+
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
184+
if response.Diagnostics.HasError() {
185+
return
186+
}
187+
188+
// Check if organization ID is configured
189+
if t.organizationId == "" {
190+
response.Diagnostics.AddError(
191+
"Organization ID not configured",
192+
"The organization_id must be set in the provider configuration to update teams",
193+
)
194+
return
195+
}
196+
197+
// Create update parameters with version
198+
params := &sdk.UpdateTeamParams{
199+
XContentfulVersion: state.Version.ValueInt64(),
200+
}
201+
202+
// Update the team
203+
draft := plan.DraftForUpdate()
204+
resp, err := t.client.UpdateTeamWithResponse(
205+
ctx,
206+
t.organizationId,
207+
state.ID.ValueString(),
208+
params,
209+
draft,
210+
)
211+
212+
if err != nil {
213+
response.Diagnostics.AddError(
214+
"Error updating team",
215+
"Could not update team: "+err.Error(),
216+
)
217+
return
218+
}
219+
220+
if resp.StatusCode() != 200 {
221+
response.Diagnostics.AddError(
222+
"Error updating team",
223+
fmt.Sprintf("Received unexpected status code: %d", resp.StatusCode()),
224+
)
225+
return
226+
}
227+
228+
// Map response to state
229+
plan.Import(resp.JSON200)
230+
231+
// Set state
232+
response.Diagnostics.Append(response.State.Set(ctx, plan)...)
233+
}
234+
235+
func (t *teamResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
236+
// Get current state
237+
var state Team
238+
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
239+
if response.Diagnostics.HasError() {
240+
return
241+
}
242+
243+
// Check if organization ID is configured
244+
if t.organizationId == "" {
245+
response.Diagnostics.AddError(
246+
"Organization ID not configured",
247+
"The organization_id must be set in the provider configuration to delete teams",
248+
)
249+
return
250+
}
251+
252+
// Create delete parameters with version
253+
params := &sdk.DeleteTeamParams{
254+
XContentfulVersion: state.Version.ValueInt64(),
255+
}
256+
257+
// Delete the team
258+
resp, err := t.client.DeleteTeamWithResponse(
259+
ctx,
260+
t.organizationId,
261+
state.ID.ValueString(),
262+
params,
263+
)
264+
265+
if err != nil {
266+
response.Diagnostics.AddError(
267+
"Error deleting team",
268+
"Could not delete team: "+err.Error(),
269+
)
270+
return
271+
}
272+
273+
if resp.StatusCode() != 204 && resp.StatusCode() != 404 {
274+
response.Diagnostics.AddError(
275+
"Error deleting team",
276+
fmt.Sprintf("Received unexpected status code: %d", resp.StatusCode()),
277+
)
278+
return
279+
}
280+
}
281+
282+
func (t *teamResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
283+
resource.ImportStatePassthroughID(ctx, path.Root("id"), request, response)
284+
285+
// Set the team ID in state and then read the team to populate other attributes
286+
futureState := &Team{
287+
ID: types.StringValue(request.ID),
288+
}
289+
290+
// Check if organization ID is configured
291+
if t.organizationId == "" {
292+
response.Diagnostics.AddError(
293+
"Organization ID not configured",
294+
"The organization_id must be set in the provider configuration to import teams",
295+
)
296+
return
297+
}
298+
299+
resp, err := t.client.GetTeamWithResponse(ctx, t.organizationId, request.ID)
300+
if err != nil {
301+
response.Diagnostics.AddError(
302+
"Error reading team",
303+
"Could not read team: "+err.Error(),
304+
)
305+
return
306+
}
307+
308+
if resp.StatusCode() != 200 {
309+
response.Diagnostics.AddError(
310+
"Error reading team",
311+
fmt.Sprintf("Received unexpected status code: %d", resp.StatusCode()),
312+
)
313+
return
314+
}
315+
316+
// Map response to state
317+
futureState.Import(resp.JSON200)
318+
319+
// Set state
320+
response.Diagnostics.Append(response.State.Set(ctx, futureState)...)
321+
}

0 commit comments

Comments
 (0)