- Make 'name' field optional and computed in organization resource
- Include name in Create and Update operations when provided
- Name can only be changed by organization owners (enforced by API)
- Matches API capability to update organization name
- Backwards compatible: name remains optional
Example usage:
resource "towerops_organization" "main" {
name = "My Organization"
use_sites = true
}
171 lines
5.2 KiB
Go
171 lines
5.2 KiB
Go
package provider
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"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"
|
|
)
|
|
|
|
var _ resource.Resource = &OrganizationResource{}
|
|
|
|
// OrganizationResource manages organization settings.
|
|
type OrganizationResource struct {
|
|
client *Client
|
|
}
|
|
|
|
// OrganizationResourceModel describes the resource data model.
|
|
type OrganizationResourceModel struct {
|
|
ID types.String `tfsdk:"id"`
|
|
Name types.String `tfsdk:"name"`
|
|
Slug types.String `tfsdk:"slug"`
|
|
UseSites types.Bool `tfsdk:"use_sites"`
|
|
}
|
|
|
|
// NewOrganizationResource creates a new organization resource.
|
|
func NewOrganizationResource() resource.Resource {
|
|
return &OrganizationResource{}
|
|
}
|
|
|
|
func (r *OrganizationResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
|
resp.TypeName = req.ProviderTypeName + "_organization"
|
|
}
|
|
|
|
func (r *OrganizationResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
|
resp.Schema = schema.Schema{
|
|
Description: "Manages organization settings for the organization associated with the API token. There is exactly one organization per token, so this resource manages settings rather than creating or deleting organizations.",
|
|
Attributes: map[string]schema.Attribute{
|
|
"id": schema.StringAttribute{
|
|
Description: "The unique identifier of the organization.",
|
|
Computed: true,
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.UseStateForUnknown(),
|
|
},
|
|
},
|
|
"name": schema.StringAttribute{
|
|
Description: "The name of the organization. Can only be set by organization owners.",
|
|
Optional: true,
|
|
Computed: true,
|
|
},
|
|
"slug": schema.StringAttribute{
|
|
Description: "The URL-friendly slug of the organization.",
|
|
Computed: true,
|
|
},
|
|
"use_sites": schema.BoolAttribute{
|
|
Description: "Whether the organization uses sites to group devices. When true, devices are organized under sites. When false, devices belong directly to the organization.",
|
|
Required: true,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (r *OrganizationResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
|
if req.ProviderData == nil {
|
|
return
|
|
}
|
|
|
|
client, ok := req.ProviderData.(*Client)
|
|
if !ok {
|
|
resp.Diagnostics.AddError(
|
|
"Unexpected Resource Configure Type",
|
|
fmt.Sprintf("Expected *Client, got: %T", req.ProviderData),
|
|
)
|
|
return
|
|
}
|
|
|
|
r.client = client
|
|
}
|
|
|
|
func (r *OrganizationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
|
var data OrganizationResourceModel
|
|
|
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
org := Organization{
|
|
UseSites: data.UseSites.ValueBool(),
|
|
}
|
|
|
|
// Include name if provided
|
|
if !data.Name.IsNull() && !data.Name.IsUnknown() {
|
|
org.Name = data.Name.ValueString()
|
|
}
|
|
|
|
updated, err := r.client.UpdateOrganization(org)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Failed to update organization", err.Error())
|
|
return
|
|
}
|
|
|
|
data.ID = types.StringValue(updated.ID)
|
|
data.Name = types.StringValue(updated.Name)
|
|
data.Slug = types.StringValue(updated.Slug)
|
|
data.UseSites = types.BoolValue(updated.UseSites)
|
|
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
|
}
|
|
|
|
func (r *OrganizationResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
|
var data OrganizationResourceModel
|
|
|
|
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
org, err := r.client.GetOrganization()
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Failed to read organization", err.Error())
|
|
return
|
|
}
|
|
|
|
data.ID = types.StringValue(org.ID)
|
|
data.Name = types.StringValue(org.Name)
|
|
data.Slug = types.StringValue(org.Slug)
|
|
data.UseSites = types.BoolValue(org.UseSites)
|
|
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
|
}
|
|
|
|
func (r *OrganizationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
|
var data OrganizationResourceModel
|
|
|
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
org := Organization{
|
|
UseSites: data.UseSites.ValueBool(),
|
|
}
|
|
|
|
// Include name if provided
|
|
if !data.Name.IsNull() && !data.Name.IsUnknown() {
|
|
org.Name = data.Name.ValueString()
|
|
}
|
|
|
|
updated, err := r.client.UpdateOrganization(org)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Failed to update organization", err.Error())
|
|
return
|
|
}
|
|
|
|
data.ID = types.StringValue(updated.ID)
|
|
data.Name = types.StringValue(updated.Name)
|
|
data.Slug = types.StringValue(updated.Slug)
|
|
data.UseSites = types.BoolValue(updated.UseSites)
|
|
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
|
}
|
|
|
|
func (r *OrganizationResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
|
// Organizations cannot be deleted via the API. When this resource is
|
|
// removed from Terraform config, we simply remove it from state.
|
|
// The organization continues to exist in TowerOps.
|
|
}
|