Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changes/unreleased/Added-20250807-074148.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Added
body: Add new contentful_team resource with name and description properties
time: 2025-08-07T07:41:48.590843179Z
2 changes: 2 additions & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/labd/terraform-provider-contentful/internal/resources/preview_environment"
"github.com/labd/terraform-provider-contentful/internal/resources/role"
"github.com/labd/terraform-provider-contentful/internal/resources/space"
"github.com/labd/terraform-provider-contentful/internal/resources/team"
"github.com/labd/terraform-provider-contentful/internal/resources/webhook"
"github.com/labd/terraform-provider-contentful/internal/utils"
)
Expand Down Expand Up @@ -161,6 +162,7 @@ func (c contentfulProvider) Resources(_ context.Context) []func() resource.Resou
preview_environment.NewPreviewEnvironmentResource,
role.NewRoleResource,
space.NewSpaceResource,
team.NewTeamResource,
webhook.NewWebhookResource,
}
}
56 changes: 56 additions & 0 deletions internal/resources/team/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package team

import (
"github.com/hashicorp/terraform-plugin-framework/types"

"github.com/labd/terraform-provider-contentful/internal/sdk"
)

// Team is the main resource schema data
type Team struct {
ID types.String `tfsdk:"id"`
Version types.Int64 `tfsdk:"version"`
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
}

// Import populates the Team struct from an SDK team object
func (t *Team) Import(team *sdk.Team) {
t.ID = types.StringValue(team.Sys.Id)
t.Version = types.Int64Value(int64(team.Sys.Version))
t.Name = types.StringValue(team.Name)

if team.Description != nil {
t.Description = types.StringValue(*team.Description)
} else {
t.Description = types.StringNull()
}
}

// DraftForCreate creates a TeamCreate object for creating a new team
func (t *Team) DraftForCreate() sdk.TeamCreate {
draft := sdk.TeamCreate{
Name: t.Name.ValueString(),
}

if !t.Description.IsNull() && !t.Description.IsUnknown() {
description := t.Description.ValueString()
draft.Description = &description
}

return draft
}

// DraftForUpdate creates a TeamUpdate object for updating an existing team
func (t *Team) DraftForUpdate() sdk.TeamUpdate {
draft := sdk.TeamUpdate{
Name: t.Name.ValueString(),
}

if !t.Description.IsNull() && !t.Description.IsUnknown() {
description := t.Description.ValueString()
draft.Description = &description
}

return draft
}
321 changes: 321 additions & 0 deletions internal/resources/team/resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
package team

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"

"github.com/labd/terraform-provider-contentful/internal/sdk"
"github.com/labd/terraform-provider-contentful/internal/utils"
)

// Ensure the implementation satisfies the expected interfaces.
var (
_ resource.Resource = &teamResource{}
_ resource.ResourceWithConfigure = &teamResource{}
_ resource.ResourceWithImportState = &teamResource{}
)

func NewTeamResource() resource.Resource {
return &teamResource{}
}

// teamResource is the resource implementation.
type teamResource struct {
client *sdk.ClientWithResponses
organizationId string
}

func (t *teamResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
response.TypeName = request.ProviderTypeName + "_team"
}

func (t *teamResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) {
response.Schema = schema.Schema{
Description: "A Contentful Team represents a team in a Contentful organization.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
Description: "Team ID",
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"version": schema.Int64Attribute{
Computed: true,
Description: "The current version of the team",
},
"name": schema.StringAttribute{
Required: true,
Description: "Name of the team",
},
"description": schema.StringAttribute{
Optional: true,
Description: "Description of the team",
},
},
}
}

func (t *teamResource) Configure(_ context.Context, request resource.ConfigureRequest, _ *resource.ConfigureResponse) {
if request.ProviderData == nil {
return
}

data := request.ProviderData.(utils.ProviderData)
t.client = data.Client
t.organizationId = data.OrganizationId
}

func (t *teamResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
// Get plan values
var plan Team
response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...)
if response.Diagnostics.HasError() {
return
}

// Check if organization ID is configured
if t.organizationId == "" {
response.Diagnostics.AddError(
"Organization ID not configured",
"The organization_id must be set in the provider configuration to create teams",
)
return
}

// Create the team
draft := plan.DraftForCreate()

resp, err := t.client.CreateTeamWithResponse(ctx, t.organizationId, draft)
if err != nil {
response.Diagnostics.AddError(
"Error creating team",
"Could not create team: "+err.Error(),
)
return
}

if resp.StatusCode() != 201 {
response.Diagnostics.AddError(
"Error creating team",
fmt.Sprintf("Received unexpected status code: %d", resp.StatusCode()),
)
return
}

// Map response to state
plan.Import(resp.JSON201)

// Set state
response.Diagnostics.Append(response.State.Set(ctx, plan)...)
}

func (t *teamResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
// Get current state
var state Team
diags := request.State.Get(ctx, &state)
response.Diagnostics.Append(diags...)
if response.Diagnostics.HasError() {
return
}

// Check if organization ID is configured
if t.organizationId == "" {
response.Diagnostics.AddError(
"Organization ID not configured",
"The organization_id must be set in the provider configuration to read teams",
)
return
}

resp, err := t.client.GetTeamWithResponse(ctx, t.organizationId, state.ID.ValueString())
if err != nil {
response.Diagnostics.AddError(
"Error reading team",
"Could not read team: "+err.Error(),
)
return
}

// Handle 404 Not Found
if resp.StatusCode() == 404 {
response.Diagnostics.AddWarning(
"Team not found",
fmt.Sprintf("Team %s was not found, removing from state",
state.ID.ValueString()),
)
response.State.RemoveResource(ctx)
return
}

if resp.StatusCode() != 200 {
response.Diagnostics.AddError(
"Error reading team",
fmt.Sprintf("Received unexpected status code: %d", resp.StatusCode()),
)
return
}

// Map response to state
state.Import(resp.JSON200)

// Set state
response.Diagnostics.Append(response.State.Set(ctx, state)...)
}

func (t *teamResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
// Get plan values
var plan Team
response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...)
if response.Diagnostics.HasError() {
return
}

// Get current state
var state Team
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}

// Check if organization ID is configured
if t.organizationId == "" {
response.Diagnostics.AddError(
"Organization ID not configured",
"The organization_id must be set in the provider configuration to update teams",
)
return
}

// Create update parameters with version
params := &sdk.UpdateTeamParams{
XContentfulVersion: state.Version.ValueInt64(),
}

// Update the team
draft := plan.DraftForUpdate()
resp, err := t.client.UpdateTeamWithResponse(
ctx,
t.organizationId,
state.ID.ValueString(),
params,
draft,
)

if err != nil {
response.Diagnostics.AddError(
"Error updating team",
"Could not update team: "+err.Error(),
)
return
}

if resp.StatusCode() != 200 {
response.Diagnostics.AddError(
"Error updating team",
fmt.Sprintf("Received unexpected status code: %d", resp.StatusCode()),
)
return
}

// Map response to state
plan.Import(resp.JSON200)

// Set state
response.Diagnostics.Append(response.State.Set(ctx, plan)...)
}

func (t *teamResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
// Get current state
var state Team
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}

// Check if organization ID is configured
if t.organizationId == "" {
response.Diagnostics.AddError(
"Organization ID not configured",
"The organization_id must be set in the provider configuration to delete teams",
)
return
}

// Create delete parameters with version
params := &sdk.DeleteTeamParams{
XContentfulVersion: state.Version.ValueInt64(),
}

// Delete the team
resp, err := t.client.DeleteTeamWithResponse(
ctx,
t.organizationId,
state.ID.ValueString(),
params,
)

if err != nil {
response.Diagnostics.AddError(
"Error deleting team",
"Could not delete team: "+err.Error(),
)
return
}

if resp.StatusCode() != 204 && resp.StatusCode() != 404 {
response.Diagnostics.AddError(
"Error deleting team",
fmt.Sprintf("Received unexpected status code: %d", resp.StatusCode()),
)
return
}
}

func (t *teamResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), request, response)

// Set the team ID in state and then read the team to populate other attributes
futureState := &Team{
ID: types.StringValue(request.ID),
}

// Check if organization ID is configured
if t.organizationId == "" {
response.Diagnostics.AddError(
"Organization ID not configured",
"The organization_id must be set in the provider configuration to import teams",
)
return
}

resp, err := t.client.GetTeamWithResponse(ctx, t.organizationId, request.ID)
if err != nil {
response.Diagnostics.AddError(
"Error reading team",
"Could not read team: "+err.Error(),
)
return
}

if resp.StatusCode() != 200 {
response.Diagnostics.AddError(
"Error reading team",
fmt.Sprintf("Received unexpected status code: %d", resp.StatusCode()),
)
return
}

// Map response to state
futureState.Import(resp.JSON200)

// Set state
response.Diagnostics.Append(response.State.Set(ctx, futureState)...)
}
Loading