feat: allow setting organization name via Terraform

- 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
  }
This commit is contained in:
Graham McIntire 2026-03-10 13:32:56 -05:00
parent 84bd97c120
commit 70c7a5cfbb
No known key found for this signature in database
6 changed files with 384 additions and 27 deletions

View file

@ -68,6 +68,19 @@ type Device struct {
InsertedAt string `json:"inserted_at,omitempty"` InsertedAt string `json:"inserted_at,omitempty"`
} }
// Organization represents a TowerOps organization.
type Organization struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Slug string `json:"slug,omitempty"`
UseSites bool `json:"use_sites"`
}
// organizationResponse wraps the API response for organization endpoints.
type organizationResponse struct {
Data Organization `json:"data"`
}
// APIError represents an error response from the API. // APIError represents an error response from the API.
type APIError struct { type APIError struct {
Error string `json:"error,omitempty"` Error string `json:"error,omitempty"`
@ -228,3 +241,34 @@ func (c *Client) DeleteDevice(id string) error {
_, err := c.doRequest(http.MethodDelete, "/api/v1/devices/"+id, nil) _, err := c.doRequest(http.MethodDelete, "/api/v1/devices/"+id, nil)
return err return err
} }
// GetOrganization retrieves the current organization settings.
func (c *Client) GetOrganization() (*Organization, error) {
respBody, err := c.doRequest(http.MethodGet, "/api/v1/organization", nil)
if err != nil {
return nil, err
}
var result organizationResponse
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result.Data, nil
}
// UpdateOrganization updates the current organization settings.
func (c *Client) UpdateOrganization(org Organization) (*Organization, error) {
body := map[string]Organization{"organization": org}
respBody, err := c.doRequest(http.MethodPatch, "/api/v1/organization", body)
if err != nil {
return nil, err
}
var result organizationResponse
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
return &result.Data, nil
}

View file

@ -7,6 +7,8 @@ import (
"testing" "testing"
) )
func strPtr(s string) *string { return &s }
func TestClient_ErrNotFound(t *testing.T) { func TestClient_ErrNotFound(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
@ -59,8 +61,8 @@ func TestClient_GetDevice_Success(t *testing.T) {
if device.ID != "device-123" { if device.ID != "device-123" {
t.Errorf("expected ID device-123, got %s", device.ID) t.Errorf("expected ID device-123, got %s", device.ID)
} }
if device.SiteID != "site-456" { if device.SiteID == nil || *device.SiteID != "site-456" {
t.Errorf("expected SiteID site-456, got %s", device.SiteID) t.Errorf("expected SiteID site-456, got %v", device.SiteID)
} }
if device.IPAddress != "192.168.1.1" { if device.IPAddress != "192.168.1.1" {
t.Errorf("expected IPAddress 192.168.1.1, got %s", device.IPAddress) t.Errorf("expected IPAddress 192.168.1.1, got %s", device.IPAddress)
@ -92,7 +94,7 @@ func TestClient_CreateDevice_Success(t *testing.T) {
client := NewClient("test-token", server.URL) client := NewClient("test-token", server.URL)
device := Device{ device := Device{
SiteID: "site-456", SiteID: strPtr("site-456"),
IPAddress: "192.168.1.100", IPAddress: "192.168.1.100",
} }
@ -129,7 +131,7 @@ func TestClient_UpdateDevice_Success(t *testing.T) {
client := NewClient("test-token", server.URL) client := NewClient("test-token", server.URL)
device := Device{ device := Device{
SiteID: "site-456", SiteID: strPtr("site-456"),
IPAddress: "192.168.1.200", IPAddress: "192.168.1.200",
} }
@ -153,7 +155,7 @@ func TestClient_UpdateDevice_NotFound(t *testing.T) {
client := NewClient("test-token", server.URL) client := NewClient("test-token", server.URL)
device := Device{ device := Device{
SiteID: "site-456", SiteID: strPtr("site-456"),
IPAddress: "192.168.1.200", IPAddress: "192.168.1.200",
} }
@ -218,7 +220,7 @@ func TestClient_ValidationError(t *testing.T) {
client := NewClient("test-token", server.URL) client := NewClient("test-token", server.URL)
device := Device{ device := Device{
SiteID: "site-456", SiteID: strPtr("site-456"),
IPAddress: "invalid", IPAddress: "invalid",
} }
@ -521,7 +523,7 @@ func TestClient_CreateDevice_InvalidJSON(t *testing.T) {
client := NewClient("test-token", server.URL) client := NewClient("test-token", server.URL)
device := Device{SiteID: "site-123", IPAddress: "192.168.1.1"} device := Device{SiteID: strPtr("site-123"), IPAddress: "192.168.1.1"}
_, err := client.CreateDevice(device) _, err := client.CreateDevice(device)
if err == nil { if err == nil {
t.Fatal("expected error for invalid JSON, got nil") t.Fatal("expected error for invalid JSON, got nil")
@ -537,7 +539,7 @@ func TestClient_UpdateDevice_InvalidJSON(t *testing.T) {
client := NewClient("test-token", server.URL) client := NewClient("test-token", server.URL)
device := Device{SiteID: "site-123", IPAddress: "192.168.1.1"} device := Device{SiteID: strPtr("site-123"), IPAddress: "192.168.1.1"}
_, err := client.UpdateDevice("device-123", device) _, err := client.UpdateDevice("device-123", device)
if err == nil { if err == nil {
t.Fatal("expected error for invalid JSON, got nil") t.Fatal("expected error for invalid JSON, got nil")

View file

@ -29,7 +29,7 @@ func TestAccDeviceResource_basic(t *testing.T) {
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: "192.168.1.1", IPAddress: "192.168.1.1",
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,
@ -42,7 +42,7 @@ func TestAccDeviceResource_basic(t *testing.T) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: "192.168.1.1", IPAddress: "192.168.1.1",
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,
@ -95,7 +95,7 @@ func TestAccDeviceResource_withAllAttributes(t *testing.T) {
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: "10.0.0.1", IPAddress: "10.0.0.1",
Description: &description, Description: &description,
@ -111,7 +111,7 @@ func TestAccDeviceResource_withAllAttributes(t *testing.T) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: "10.0.0.1", IPAddress: "10.0.0.1",
Description: &description, Description: &description,
@ -170,7 +170,7 @@ func TestAccDeviceResource_update(t *testing.T) {
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: currentIP, IPAddress: currentIP,
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,
@ -183,7 +183,7 @@ func TestAccDeviceResource_update(t *testing.T) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: currentIP, IPAddress: currentIP,
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,
@ -199,7 +199,7 @@ func TestAccDeviceResource_update(t *testing.T) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: currentIP, IPAddress: currentIP,
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,
@ -260,7 +260,7 @@ func TestAccDeviceResource_recreateOn404(t *testing.T) {
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: currentIP, IPAddress: currentIP,
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,
@ -280,7 +280,7 @@ func TestAccDeviceResource_recreateOn404(t *testing.T) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: currentIP, IPAddress: currentIP,
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,
@ -303,7 +303,7 @@ func TestAccDeviceResource_recreateOn404(t *testing.T) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: currentIP, IPAddress: currentIP,
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,
@ -367,7 +367,7 @@ func TestAccDeviceResource_importState(t *testing.T) {
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: "192.168.1.1", IPAddress: "192.168.1.1",
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,
@ -382,7 +382,7 @@ func TestAccDeviceResource_importState(t *testing.T) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: "imported-device-id", ID: "imported-device-id",
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: "192.168.1.1", IPAddress: "192.168.1.1",
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,
@ -491,7 +491,7 @@ func TestAccDeviceResource_updateError(t *testing.T) {
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: "192.168.1.1", IPAddress: "192.168.1.1",
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,
@ -506,7 +506,7 @@ func TestAccDeviceResource_updateError(t *testing.T) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: "192.168.1.1", IPAddress: "192.168.1.1",
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,
@ -562,7 +562,7 @@ func TestAccDeviceResource_deleteError(t *testing.T) {
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: "192.168.1.1", IPAddress: "192.168.1.1",
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,
@ -577,7 +577,7 @@ func TestAccDeviceResource_deleteError(t *testing.T) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: "192.168.1.1", IPAddress: "192.168.1.1",
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,
@ -642,7 +642,7 @@ func TestAccDeviceResource_recreateOn404_createError(t *testing.T) {
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: "192.168.1.1", IPAddress: "192.168.1.1",
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,
@ -662,7 +662,7 @@ func TestAccDeviceResource_recreateOn404_createError(t *testing.T) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: "192.168.1.1", IPAddress: "192.168.1.1",
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,
@ -682,7 +682,7 @@ func TestAccDeviceResource_recreateOn404_createError(t *testing.T) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(Device{ json.NewEncoder(w).Encode(Device{
ID: deviceID, ID: deviceID,
SiteID: "site-123", SiteID: strPtr("site-123"),
Name: &name, Name: &name,
IPAddress: "192.168.1.2", IPAddress: "192.168.1.2",
MonitoringEnabled: &monitoringEnabled, MonitoringEnabled: &monitoringEnabled,

View file

@ -0,0 +1,171 @@
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.
}

View file

@ -0,0 +1,139 @@
package provider
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"sync"
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestAccOrganizationResource_basic(t *testing.T) {
var mu sync.Mutex
currentUseSites := false
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
switch {
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/organization":
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"data": Organization{
ID: "org-123",
Name: "Test ISP",
Slug: "test-isp",
UseSites: currentUseSites,
},
})
case r.Method == http.MethodPatch && r.URL.Path == "/api/v1/organization":
var body map[string]Organization
json.NewDecoder(r.Body).Decode(&body)
currentUseSites = body["organization"].UseSites
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"data": Organization{
ID: "org-123",
Name: "Test ISP",
Slug: "test-isp",
UseSites: currentUseSites,
},
})
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer server.Close()
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(server.URL),
Steps: []resource.TestStep{
{
Config: testAccOrganizationResourceConfig(server.URL, true),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("towerops_organization.settings", "use_sites", "true"),
resource.TestCheckResourceAttr("towerops_organization.settings", "name", "Test ISP"),
resource.TestCheckResourceAttr("towerops_organization.settings", "slug", "test-isp"),
),
},
},
})
}
func TestAccOrganizationResource_update(t *testing.T) {
var mu sync.Mutex
currentUseSites := false
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
switch {
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/organization":
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"data": Organization{
ID: "org-123",
Name: "Test ISP",
Slug: "test-isp",
UseSites: currentUseSites,
},
})
case r.Method == http.MethodPatch && r.URL.Path == "/api/v1/organization":
var body map[string]Organization
json.NewDecoder(r.Body).Decode(&body)
currentUseSites = body["organization"].UseSites
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"data": Organization{
ID: "org-123",
Name: "Test ISP",
Slug: "test-isp",
UseSites: currentUseSites,
},
})
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer server.Close()
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories(server.URL),
Steps: []resource.TestStep{
{
Config: testAccOrganizationResourceConfig(server.URL, true),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("towerops_organization.settings", "use_sites", "true"),
),
},
{
Config: testAccOrganizationResourceConfig(server.URL, false),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("towerops_organization.settings", "use_sites", "false"),
),
},
},
})
}
func testAccOrganizationResourceConfig(apiURL string, useSites bool) string {
return fmt.Sprintf(`
provider "towerops" {
token = "test-token"
api_url = %q
}
resource "towerops_organization" "settings" {
use_sites = %t
}
`, apiURL, useSites)
}

View file

@ -86,6 +86,7 @@ func (p *ToweropsProvider) Configure(ctx context.Context, req provider.Configure
func (p *ToweropsProvider) Resources(ctx context.Context) []func() resource.Resource { func (p *ToweropsProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{ return []func() resource.Resource{
NewOrganizationResource,
NewSiteResource, NewSiteResource,
NewDeviceResource, NewDeviceResource,
} }