From bf328e5b7de956c4b1bdb8ebd93d4183852cf32a Mon Sep 17 00:00:00 2001 From: Witold Duranek Date: Wed, 17 Jan 2024 09:07:16 +0100 Subject: [PATCH] feat: add rooms support and space claim token --- CHANGELOG.md | 1 + README.md | 34 --- docs/data-sources/room.md | 33 +++ docs/data-sources/space.md | 3 +- docs/resources/room.md | 47 ++++ docs/resources/space.md | 3 +- examples/complete/main.tf | 35 +++ .../data-sources/netdata_room/data-source.tf | 4 + .../data-sources/netdata_space/data-source.tf | 2 +- examples/resources/netdata_room/import.sh | 3 + examples/resources/netdata_room/resource.tf | 5 + examples/resources/netdata_space/import.sh | 2 +- examples/spaces/main.tf | 26 -- go.mod | 2 +- internal/client/models.go | 6 + internal/client/rooms.go | 123 ++++++++++ internal/client/spaces.go | 37 ++- internal/provider/provider.go | 2 + internal/provider/room_data_source.go | 105 +++++++++ internal/provider/room_data_source_test.go | 34 +++ internal/provider/room_resource.go | 223 ++++++++++++++++++ internal/provider/room_resource_test.go | 30 +++ internal/provider/space_data_source.go | 24 +- internal/provider/space_data_source_test.go | 2 + internal/provider/space_resource.go | 21 ++ internal/provider/space_resource_test.go | 16 +- 26 files changed, 739 insertions(+), 84 deletions(-) create mode 100644 docs/data-sources/room.md create mode 100644 docs/resources/room.md create mode 100644 examples/complete/main.tf create mode 100644 examples/data-sources/netdata_room/data-source.tf create mode 100644 examples/resources/netdata_room/import.sh create mode 100644 examples/resources/netdata_room/resource.tf delete mode 100644 examples/spaces/main.tf create mode 100644 internal/client/rooms.go create mode 100644 internal/provider/room_data_source.go create mode 100644 internal/provider/room_data_source_test.go create mode 100644 internal/provider/room_resource.go create mode 100644 internal/provider/room_resource_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ac01df..ab60c77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,3 +5,4 @@ Initial version. FEATURES: - support for the Netdata Cloud Spaces +- support for the Netdata Cloud Rooms diff --git a/README.md b/README.md index aee43a9..52667d9 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ This provider allows you to install and manage Netdata Cloud resources using Ter * [Requirements](#requirements) * [Getting Started](#getting-started) -* [Example](#example) ## Requirements @@ -42,36 +41,3 @@ TODO ```console $ make local-build ``` - -## Example - -```hcl -terraform { - required_providers { - netdata = { - # TODO: Update this string with the published name of your provider. - source = "netdata.cloud/todo/netdata" - } - } - required_version = ">= 1.1.0" -} - -provider "netdata" { - url = "https://app.netdata.cloud" - authtoken = "" -} - -resource "netdata_space" "test" { - name = "MyTestingSpace" - description = "Created by Terraform" -} - -data "netdata_space" "test" { - id = "ee3ec76d-0180-4ef4-93ae-c94c1e7ed2f1" -} - -output "datasource" { - value = data.netdata_space.test.name -} - -``` diff --git a/docs/data-sources/room.md b/docs/data-sources/room.md new file mode 100644 index 0000000..c48effa --- /dev/null +++ b/docs/data-sources/room.md @@ -0,0 +1,33 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "netdata_room Data Source - terraform-provider-netdata" +subcategory: "" +description: |- + +--- + +# netdata_room (Data Source) + + + +## Example Usage + +```terraform +data "netdata_room" "test" { + spaceid = "" + id = "" +} +``` + + +## Schema + +### Required + +- `id` (String) The ID of the room +- `spaceid` (String) The ID of the space + +### Read-Only + +- `description` (String) The description of the room +- `name` (String) The name of the room diff --git a/docs/data-sources/space.md b/docs/data-sources/space.md index 55b7f7b..1f967b6 100644 --- a/docs/data-sources/space.md +++ b/docs/data-sources/space.md @@ -14,7 +14,7 @@ description: |- ```terraform data "netdata_space" "test" { - id = "ee3ec76d-0180-4ef4-93ae-c94c1e7ed2f1" + id = "" } ``` @@ -27,5 +27,6 @@ data "netdata_space" "test" { ### Read-Only +- `claimtoken` (String) The claim token of the space - `description` (String) The description of the space - `name` (String) The name of the space diff --git a/docs/resources/room.md b/docs/resources/room.md new file mode 100644 index 0000000..63ff9f1 --- /dev/null +++ b/docs/resources/room.md @@ -0,0 +1,47 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "netdata_room Resource - terraform-provider-netdata" +subcategory: "" +description: |- + +--- + +# netdata_room (Resource) + + + +## Example Usage + +```terraform +resource "netdata_room" "test" { + spaceid = "" + name = "MyTestingSpace" + description = "Created by Terraform" +} +``` + + +## Schema + +### Required + +- `name` (String) The name of the room +- `spaceid` (String) The ID of the space + +### Optional + +- `description` (String) The description of the room + +### Read-Only + +- `id` (String) The ID of the room + +## Import + +Import is supported using the following syntax: + +```shell +#!/bin/sh + +terraform import netdata_room.test spaceid,roomid +``` diff --git a/docs/resources/space.md b/docs/resources/space.md index 74ae55e..ed38ec0 100644 --- a/docs/resources/space.md +++ b/docs/resources/space.md @@ -32,6 +32,7 @@ resource "netdata_space" "test" { ### Read-Only +- `claimtoken` (String) The claim token of the space - `id` (String) The ID of the space ## Import @@ -41,5 +42,5 @@ Import is supported using the following syntax: ```shell #!/bin/sh -terraform import netdata_space.test ee3ec76d-0180-4ef4-93ae-c94c1e7ed2f1 +terraform import netdata_space.test spaceid ``` diff --git a/examples/complete/main.tf b/examples/complete/main.tf new file mode 100644 index 0000000..b68aea6 --- /dev/null +++ b/examples/complete/main.tf @@ -0,0 +1,35 @@ +terraform { + required_providers { + netdata = { + # TODO: Update this string with the published name of your provider. + source = "netdata.cloud/todo/netdata" + } + } + required_version = ">= 1.1.0" +} + +provider "netdata" {} + +resource "netdata_space" "test" { + name = "MyTestingSpace" + description = "Created by Terraform" +} + +resource "netdata_room" "test" { + spaceid = netdata_space.test.id + name = "MyTestingRoom" + description = "Created by Terraform2" +} + +data "netdata_space" "test" { + id = netdata_space.test.id +} + +data "netdata_room" "test" { + id = netdata_room.test.id + spaceid = netdata_space.test.id +} + +output "datasource" { + value = data.netdata_space.test.name +} diff --git a/examples/data-sources/netdata_room/data-source.tf b/examples/data-sources/netdata_room/data-source.tf new file mode 100644 index 0000000..3ce473f --- /dev/null +++ b/examples/data-sources/netdata_room/data-source.tf @@ -0,0 +1,4 @@ +data "netdata_room" "test" { + spaceid = "" + id = "" +} diff --git a/examples/data-sources/netdata_space/data-source.tf b/examples/data-sources/netdata_space/data-source.tf index 8776bdb..368877d 100644 --- a/examples/data-sources/netdata_space/data-source.tf +++ b/examples/data-sources/netdata_space/data-source.tf @@ -1,3 +1,3 @@ data "netdata_space" "test" { - id = "ee3ec76d-0180-4ef4-93ae-c94c1e7ed2f1" + id = "" } diff --git a/examples/resources/netdata_room/import.sh b/examples/resources/netdata_room/import.sh new file mode 100644 index 0000000..c225151 --- /dev/null +++ b/examples/resources/netdata_room/import.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +terraform import netdata_room.test spaceid,roomid diff --git a/examples/resources/netdata_room/resource.tf b/examples/resources/netdata_room/resource.tf new file mode 100644 index 0000000..536b06a --- /dev/null +++ b/examples/resources/netdata_room/resource.tf @@ -0,0 +1,5 @@ +resource "netdata_room" "test" { + spaceid = "" + name = "MyTestingSpace" + description = "Created by Terraform" +} diff --git a/examples/resources/netdata_space/import.sh b/examples/resources/netdata_space/import.sh index 389bb77..520f0c6 100644 --- a/examples/resources/netdata_space/import.sh +++ b/examples/resources/netdata_space/import.sh @@ -1,3 +1,3 @@ #!/bin/sh -terraform import netdata_space.test ee3ec76d-0180-4ef4-93ae-c94c1e7ed2f1 +terraform import netdata_space.test spaceid diff --git a/examples/spaces/main.tf b/examples/spaces/main.tf deleted file mode 100644 index 1ed65c6..0000000 --- a/examples/spaces/main.tf +++ /dev/null @@ -1,26 +0,0 @@ -terraform { - required_providers { - netdata = { - source = "netdata.cloud/todo/netdata" - } - } - required_version = ">= 1.1.0" -} - -provider "netdata" { - url = "https://app.netdata.cloud" - authtoken = "" -} - -resource "netdata_space" "test" { - name = "MyTestingSpace" - description = "Created by Terraform" -} - -data "netdata_space" "test" { - id = "ee3ec76d-0180-4ef4-93ae-c94c1e7ed2f1" -} - -output "datasource" { - value = data.netdata_space.test.name -} diff --git a/go.mod b/go.mod index d403b18..1972a09 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/hashicorp/terraform-plugin-framework v1.5.0 github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 github.com/hashicorp/terraform-plugin-go v0.20.0 + github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.6.0 ) @@ -38,7 +39,6 @@ require ( github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.19.0 // indirect github.com/hashicorp/terraform-json v0.18.0 // indirect - github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect github.com/hashicorp/terraform-plugin-sdk/v2 v2.30.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect diff --git a/internal/client/models.go b/internal/client/models.go index ff5c3f9..654b2a1 100644 --- a/internal/client/models.go +++ b/internal/client/models.go @@ -5,3 +5,9 @@ type SpaceInfo struct { Name string `json:"name"` Description string `json:"description"` } + +type RoomInfo struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` +} diff --git a/internal/client/rooms.go b/internal/client/rooms.go new file mode 100644 index 0000000..f7bafd2 --- /dev/null +++ b/internal/client/rooms.go @@ -0,0 +1,123 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" +) + +func (c *Client) GetRooms(spaceid string) (*[]RoomInfo, error) { + if spaceid == "" { + return nil, fmt.Errorf("spaceid is empty") + } + req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/v2/spaces/%s/rooms", c.HostURL, spaceid), nil) + if err != nil { + return nil, err + } + + body, err := c.doRequest(req) + if err != nil { + return nil, err + } + + var rooms []RoomInfo + err = json.Unmarshal(body, &rooms) + if err != nil { + return nil, err + } + + return &rooms, nil +} + +func (c *Client) GetRoomByID(id, spaceid string) (*RoomInfo, error) { + rooms, err := c.GetRooms(spaceid) + if err != nil { + return nil, err + } + for _, room := range *rooms { + if room.ID == id { + return &room, nil + } + } + return nil, ErrNotFound +} + +func (c *Client) CreateRoom(spaceid, name, description string) (*RoomInfo, error) { + if spaceid == "" { + return nil, fmt.Errorf("spaceid is empty") + } + reqBody, err := json.Marshal(map[string]string{ + "name": name, + "description": description, + }) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/v1/spaces/%s/rooms", c.HostURL, spaceid), bytes.NewReader(reqBody)) + if err != nil { + return nil, err + } + + respBody, err := c.doRequest(req) + if err != nil { + return nil, err + } + + var room RoomInfo + err = json.Unmarshal(respBody, &room) + if err != nil { + return nil, err + } + + room.Name = name + room.Description = description + + return &room, nil +} + +func (c *Client) UpdateRoomByID(id, spaceid, name, description string) error { + if id == "" { + return fmt.Errorf("id is empty") + } + if spaceid == "" { + return fmt.Errorf("spaceid is empty") + } + reqBody, err := json.Marshal(map[string]string{ + "name": name, + "description": description, + }) + if err != nil { + return err + } + req, err := http.NewRequest("PATCH", fmt.Sprintf("%s/api/v1/spaces/%s/rooms/%s", c.HostURL, spaceid, id), bytes.NewReader(reqBody)) + if err != nil { + return err + } + _, err = c.doRequest(req) + if err != nil { + return err + } + return nil +} + +func (c *Client) DeleteRoomByID(id, spaceid string) error { + if id == "" { + return fmt.Errorf("id is empty") + } + if spaceid == "" { + return fmt.Errorf("spaceid is empty") + } + req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/api/v1/spaces/%s/rooms/%s", c.HostURL, spaceid, id), nil) + if err != nil { + return err + } + + _, err = c.doRequest(req) + if err != nil { + return err + } + + return nil +} diff --git a/internal/client/spaces.go b/internal/client/spaces.go index 86b4181..a3ba3bd 100644 --- a/internal/client/spaces.go +++ b/internal/client/spaces.go @@ -1,10 +1,10 @@ package client import ( + "bytes" "encoding/json" "fmt" "net/http" - "strings" ) func (c *Client) GetSpaces() (*[]SpaceInfo, error) { @@ -46,7 +46,7 @@ func (c *Client) CreateSpace(name, description string) (*SpaceInfo, error) { return nil, err } - req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/v1/spaces", c.HostURL), strings.NewReader(string(reqBody))) + req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/v1/spaces", c.HostURL), bytes.NewReader(reqBody)) if err != nil { return nil, err } @@ -83,9 +83,9 @@ func (c *Client) UpdateSpaceByID(id, name, description string) error { if err != nil { return err } - req, err := http.NewRequest("PATCH", fmt.Sprintf("%s/api/v1/spaces/%s", c.HostURL, id), strings.NewReader(string(reqBody))) + req, err := http.NewRequest("PATCH", fmt.Sprintf("%s/api/v1/spaces/%s", c.HostURL, id), bytes.NewReader(reqBody)) if err != nil { - return fmt.Errorf("req %+v", req) + return err } _, err = c.doRequest(req) if err != nil { @@ -110,3 +110,32 @@ func (c *Client) DeleteSpaceByID(id string) error { return nil } + +func (c *Client) GetSpaceClaimToken(id string) (*string, error) { + if id == "" { + return nil, fmt.Errorf("id is empty") + } + req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/v1/spaces/%s/tokens", c.HostURL, id), nil) + if err != nil { + return nil, err + } + + respBody, err := c.doRequest(req) + if err != nil { + return nil, err + } + + var data map[string]interface{} + + err = json.Unmarshal(respBody, &data) + if err != nil { + return nil, err + } + + token, ok := data["token"].(string) + if !ok { + return nil, fmt.Errorf("token not found") + } + + return &token, nil +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index e36a3ba..1a991e3 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -88,12 +88,14 @@ func (p *netdataCloudProvider) Configure(ctx context.Context, req provider.Confi func (p *netdataCloudProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ NewSpaceResource, + NewRoomResource, } } func (p *netdataCloudProvider) DataSources(ctx context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ NewSpaceDataSource, + NewRoomDataSource, } } diff --git a/internal/provider/room_data_source.go b/internal/provider/room_data_source.go new file mode 100644 index 0000000..804e4f7 --- /dev/null +++ b/internal/provider/room_data_source.go @@ -0,0 +1,105 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/netdata/terraform-provider-netdata/internal/client" +) + +var ( + _ datasource.DataSource = &roomDataSource{} + _ datasource.DataSourceWithConfigure = &roomDataSource{} +) + +func NewRoomDataSource() datasource.DataSource { + return &roomDataSource{} +} + +type roomDataSource struct { + client *client.Client +} + +type roomDataSourceModel struct { + ID types.String `tfsdk:"id"` + SpaceID types.String `tfsdk:"spaceid"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` +} + +func (s *roomDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_room" +} + +func (s *roomDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of the room", + Required: true, + }, + "spaceid": schema.StringAttribute{ + Description: "The ID of the space", + Required: true, + }, + "name": schema.StringAttribute{ + Description: "The name of the room", + Computed: true, + }, + "description": schema.StringAttribute{ + Description: "The description of the room", + Computed: true, + }, + }, + } +} + +func (s *roomDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + s.client = client +} + +func (s *roomDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var state roomDataSourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) + + roomInfo, err := s.client.GetRoomByID(state.ID.ValueString(), state.SpaceID.ValueString()) + + switch { + case err == client.ErrNotFound: + resp.State.RemoveResource(ctx) + return + case err != nil: + resp.Diagnostics.AddError( + "Error Getting Room", + "Could Not Read Room ID: "+state.ID.ValueString()+": err: "+err.Error(), + ) + return + default: + state.ID = types.StringValue(roomInfo.ID) + state.Name = types.StringValue(roomInfo.Name) + state.Description = types.StringValue(roomInfo.Description) + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + } +} diff --git a/internal/provider/room_data_source_test.go b/internal/provider/room_data_source_test.go new file mode 100644 index 0000000..6ef649f --- /dev/null +++ b/internal/provider/room_data_source_test.go @@ -0,0 +1,34 @@ +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccRoomDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + resource "netdata_space" "test" { + name = "testAcc" + } + resource "netdata_room" "test" { + spaceid = netdata_space.test.id + name = "testAcc" + } + data "netdata_room" "test" { + spaceid = netdata_space.test.id + id = netdata_room.test.id + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.netdata_room.test", "name", "testAcc"), + ), + }, + }, + }, + ) +} diff --git a/internal/provider/room_resource.go b/internal/provider/room_resource.go new file mode 100644 index 0000000..9313a17 --- /dev/null +++ b/internal/provider/room_resource.go @@ -0,0 +1,223 @@ +package provider + +import ( + "context" + "fmt" + "strings" + + "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/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/netdata/terraform-provider-netdata/internal/client" +) + +var ( + _ resource.Resource = &roomResource{} + _ resource.ResourceWithConfigure = &roomResource{} +) + +func NewRoomResource() resource.Resource { + return &roomResource{} +} + +type roomResource struct { + client *client.Client +} + +type roomResourceModel struct { + ID types.String `tfsdk:"id"` + SpaceID types.String `tfsdk:"spaceid"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` +} + +func (s *roomResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_room" +} + +func (s *roomResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The ID of the room", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "spaceid": schema.StringAttribute{ + Description: "The ID of the space", + Required: true, + }, + "name": schema.StringAttribute{ + Description: "The name of the room", + Required: true, + }, + "description": schema.StringAttribute{ + Description: "The description of the room", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + }, + } +} + +func (s *roomResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + s.client = client +} + +func (s *roomResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan roomResourceModel + + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if resp.Diagnostics.HasError() { + return + } + + roomInfo, err := s.client.CreateRoom(plan.SpaceID.ValueString(), plan.Name.ValueString(), plan.Description.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating Room", + "err: "+err.Error(), + ) + return + } + + plan.ID = types.StringValue(roomInfo.ID) + plan.Name = types.StringValue(roomInfo.Name) + plan.Description = types.StringValue(roomInfo.Description) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (s *roomResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state roomResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + roomInfo, err := s.client.GetRoomByID(state.ID.ValueString(), state.SpaceID.ValueString()) + + switch { + case err == client.ErrNotFound: + resp.State.RemoveResource(ctx) + return + case err != nil: + resp.Diagnostics.AddError( + "Error Getting Room", + "Could Not Read Room ID: "+state.ID.ValueString()+": err: "+err.Error(), + ) + return + default: + state.ID = types.StringValue(roomInfo.ID) + state.Name = types.StringValue(roomInfo.Name) + state.Description = types.StringValue(roomInfo.Description) + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } +} + +func (s *roomResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan roomResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + err := s.client.UpdateRoomByID(plan.ID.ValueString(), plan.SpaceID.ValueString(), plan.Name.ValueString(), plan.Description.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error Updating room", + "Could Not Update Room ID: "+plan.ID.ValueString()+": err: "+err.Error(), + ) + return + } + + roomInfo, err := s.client.GetRoomByID(plan.ID.ValueString(), plan.SpaceID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error Getting Room", + "Could Not Read Room ID: "+plan.ID.ValueString()+": err: "+err.Error(), + ) + return + } + + plan.ID = types.StringValue(roomInfo.ID) + plan.Name = types.StringValue(roomInfo.Name) + plan.Description = types.StringValue(roomInfo.Description) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + +} + +func (s *roomResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state roomResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + err := s.client.DeleteRoomByID(state.ID.ValueString(), state.SpaceID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Error Deleting Room", + "Could Not Delete Room ID: "+state.ID.ValueString()+": err: "+err.Error(), + ) + return + } +} + +func (s *roomResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, ",") + + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: spaceid,id. Got: %q", req.ID), + ) + return + } + + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("spaceid"), idParts[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), idParts[1])...) +} diff --git a/internal/provider/room_resource_test.go b/internal/provider/room_resource_test.go new file mode 100644 index 0000000..6eccf28 --- /dev/null +++ b/internal/provider/room_resource_test.go @@ -0,0 +1,30 @@ +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccRoomResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + resource "netdata_space" "test" { + name = "testAcc" + } + resource "netdata_room" "test" { + spaceid = netdata_space.test.id + name = "testAcc" + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("netdata_room.test", "name", "testAcc"), + resource.TestCheckResourceAttr("netdata_room.test", "description", ""), + ), + }, + }, + }) +} diff --git a/internal/provider/space_data_source.go b/internal/provider/space_data_source.go index 1c18a06..d6ecdc7 100644 --- a/internal/provider/space_data_source.go +++ b/internal/provider/space_data_source.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netdata/terraform-provider-netdata/internal/client" ) @@ -27,6 +28,7 @@ type spaceDataSourceModel struct { ID types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` + ClaimToken types.String `tfsdk:"claimtoken"` } func (s *spaceDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { @@ -48,6 +50,10 @@ func (s *spaceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, Description: "The description of the space", Computed: true, }, + "claimtoken": schema.StringAttribute{ + Description: "The claim token of the space", + Computed: true, + }, }, } } @@ -76,6 +82,8 @@ func (s *spaceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) + tflog.Info(ctx, "Reading Space ID:"+state.ID.ValueString()) + spaceInfo, err := s.client.GetSpaceByID(state.ID.ValueString()) switch { @@ -92,9 +100,23 @@ func (s *spaceDataSource) Read(ctx context.Context, req datasource.ReadRequest, state.ID = types.StringValue(spaceInfo.ID) state.Name = types.StringValue(spaceInfo.Name) state.Description = types.StringValue(spaceInfo.Description) - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) if resp.Diagnostics.HasError() { return } } + + if state.ClaimToken.IsNull() { + tflog.Info(ctx, "Creating Claim Token for Space ID: "+state.ID.ValueString()) + claimToken, err := s.client.GetSpaceClaimToken(spaceInfo.ID) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating Claim Token", + "Could Not Create Claim Token for Space ID: "+state.ID.ValueString()+": err: "+err.Error(), + ) + return + } + state.ClaimToken = types.StringValue(*claimToken) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } diff --git a/internal/provider/space_data_source_test.go b/internal/provider/space_data_source_test.go index ee99c22..5b70f30 100644 --- a/internal/provider/space_data_source_test.go +++ b/internal/provider/space_data_source_test.go @@ -1,6 +1,7 @@ package provider import ( + "regexp" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -21,6 +22,7 @@ func TestAccSpaceDataSource(t *testing.T) { `, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("data.netdata_space.test", "name", "testAcc"), + resource.TestMatchResourceAttr("data.netdata_space.test", "claimtoken", regexp.MustCompile(`^.{135}$`)), ), }, }, diff --git a/internal/provider/space_resource.go b/internal/provider/space_resource.go index d933e7d..22f61b8 100644 --- a/internal/provider/space_resource.go +++ b/internal/provider/space_resource.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/netdata/terraform-provider-netdata/internal/client" ) @@ -33,6 +34,7 @@ type spaceResourceModel struct { ID types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` Description types.String `tfsdk:"description"` + ClaimToken types.String `tfsdk:"claimtoken"` } func (s *spaceResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -62,6 +64,10 @@ func (s *spaceResource) Schema(ctx context.Context, req resource.SchemaRequest, Computed: true, Default: stringdefault.StaticString(""), }, + "claimtoken": schema.StringAttribute{ + Description: "The claim token of the space", + Computed: true, + }, }, } } @@ -98,6 +104,8 @@ func (s *spaceResource) Create(ctx context.Context, req resource.CreateRequest, return } + tflog.Info(ctx, "Creating space: "+plan.Name.ValueString()) + spaceInfo, err := s.client.CreateSpace(plan.Name.ValueString(), plan.Description.ValueString()) if err != nil { resp.Diagnostics.AddError( @@ -111,6 +119,19 @@ func (s *spaceResource) Create(ctx context.Context, req resource.CreateRequest, plan.Name = types.StringValue(spaceInfo.Name) plan.Description = types.StringValue(spaceInfo.Description) + tflog.Info(ctx, "Creating Claim Token for Space ID: "+spaceInfo.ID) + + claimToken, err := s.client.GetSpaceClaimToken(spaceInfo.ID) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating Claim Token", + "Could Not Create Claim Token for Space ID: "+spaceInfo.ID+": err: "+err.Error(), + ) + return + } + + plan.ClaimToken = types.StringValue(*claimToken) + diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { diff --git a/internal/provider/space_resource_test.go b/internal/provider/space_resource_test.go index e012914..eff82bf 100644 --- a/internal/provider/space_resource_test.go +++ b/internal/provider/space_resource_test.go @@ -1,6 +1,7 @@ package provider import ( + "regexp" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -10,25 +11,12 @@ func TestAccSpaceResource(t *testing.T) { resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ - // Create and Read testing { Config: `resource "netdata_space" "test" { name = "testAcc" }`, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("netdata_space.test", "name", "testAcc"), resource.TestCheckResourceAttr("netdata_space.test", "description", ""), - ), - }, - // Update and Read testing - { - Config: ` - resource "netdata_space" "test" { - name = "testAccUpdated" - description ="testDesc" - } - `, - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("netdata_space.test", "name", "testAccUpdated"), - resource.TestCheckResourceAttr("netdata_space.test", "description", "testDesc"), + resource.TestMatchResourceAttr("netdata_space.test", "claimtoken", regexp.MustCompile(`^.{135}$`)), ), }, },