diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09644e6..7e8c441 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,8 @@
-## v0.7.2
+## v0.8.0
+
+FEATURES:
+- Added `meshstack_workspace` resource.
+- Added `meshstack_workspace` data source.
FIXES:
- Allow `value_code` in `meshstack_building_block_v2` and `meshstack_building_block` resources.
diff --git a/client/client.go b/client/client.go
index c6c629e..7a5e42c 100644
--- a/client/client.go
+++ b/client/client.go
@@ -37,6 +37,7 @@ type endpoints struct {
Projects *url.URL `json:"meshprojects"`
ProjectUserBindings *url.URL `json:"meshprojectuserbindings"`
ProjectGroupBindings *url.URL `json:"meshprojectgroupbindings"`
+ Workspaces *url.URL `json:"meshworkspaces"`
Tenants *url.URL `json:"meshtenants"`
TagDefinitions *url.URL `json:"meshtagdefinitions"`
}
@@ -63,6 +64,7 @@ func NewClient(rootUrl *url.URL, apiKey string, apiSecret string) (*MeshStackPro
Projects: rootUrl.JoinPath(apiMeshObjectsRoot, "meshprojects"),
ProjectUserBindings: rootUrl.JoinPath(apiMeshObjectsRoot, "meshprojectbindings", "userbindings"),
ProjectGroupBindings: rootUrl.JoinPath(apiMeshObjectsRoot, "meshprojectbindings", "groupbindings"),
+ Workspaces: rootUrl.JoinPath(apiMeshObjectsRoot, "meshworkspaces"),
Tenants: rootUrl.JoinPath(apiMeshObjectsRoot, "meshtenants"),
TagDefinitions: rootUrl.JoinPath(apiMeshObjectsRoot, "meshtagdefinitions"),
}
diff --git a/client/project.go b/client/project.go
index 683b2ee..eca293a 100644
--- a/client/project.go
+++ b/client/project.go
@@ -67,7 +67,7 @@ func (c *MeshStackProviderClient) ReadProject(workspace string, name string) (*M
return nil, err
}
- if res.StatusCode == 404 {
+ if res.StatusCode == http.StatusNotFound {
return nil, nil
}
diff --git a/client/workspace.go b/client/workspace.go
new file mode 100644
index 0000000..0ade57c
--- /dev/null
+++ b/client/workspace.go
@@ -0,0 +1,160 @@
+package client
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+)
+
+const CONTENT_TYPE_WORKSPACE = "application/vnd.meshcloud.api.meshworkspace.v2.hal+json"
+
+type MeshWorkspace struct {
+ ApiVersion string `json:"apiVersion" tfsdk:"api_version"`
+ Kind string `json:"kind" tfsdk:"kind"`
+ Metadata MeshWorkspaceMetadata `json:"metadata" tfsdk:"metadata"`
+ Spec MeshWorkspaceSpec `json:"spec" tfsdk:"spec"`
+}
+
+type MeshWorkspaceMetadata struct {
+ Name string `json:"name" tfsdk:"name"`
+ CreatedOn string `json:"createdOn" tfsdk:"created_on"`
+ DeletedOn *string `json:"deletedOn" tfsdk:"deleted_on"`
+ Tags map[string][]string `json:"tags" tfsdk:"tags"`
+}
+
+type MeshWorkspaceSpec struct {
+ DisplayName string `json:"displayName" tfsdk:"display_name"`
+ PlatformBuilderAccessEnabled *bool `json:"platformBuilderAccessEnabled,omitempty" tfsdk:"platform_builder_access_enabled"`
+}
+
+type MeshWorkspaceCreate struct {
+ ApiVersion string `json:"apiVersion" tfsdk:"api_version"`
+ Metadata MeshWorkspaceCreateMetadata `json:"metadata" tfsdk:"metadata"`
+ Spec MeshWorkspaceSpec `json:"spec" tfsdk:"spec"`
+}
+type MeshWorkspaceCreateMetadata struct {
+ Name string `json:"name" tfsdk:"name"`
+ Tags map[string][]string `json:"tags" tfsdk:"tags"`
+}
+
+func (c *MeshStackProviderClient) urlForWorkspace(name string) *url.URL {
+ return c.endpoints.Workspaces.JoinPath(name)
+}
+
+func (c *MeshStackProviderClient) ReadWorkspace(name string) (*MeshWorkspace, error) {
+ targetUrl := c.urlForWorkspace(name)
+ req, err := http.NewRequest("GET", targetUrl.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Accept", CONTENT_TYPE_WORKSPACE)
+
+ res, err := c.doAuthenticatedRequest(req)
+ if err != nil {
+ return nil, err
+ }
+
+ defer res.Body.Close()
+
+ if res.StatusCode == http.StatusNotFound {
+ return nil, nil // Not found is not an error
+ }
+
+ data, err := io.ReadAll(res.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ if !isSuccessHTTPStatus(res) {
+ return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data)
+ }
+
+ var workspace MeshWorkspace
+ err = json.Unmarshal(data, &workspace)
+ if err != nil {
+ return nil, err
+ }
+ return &workspace, nil
+}
+
+func (c *MeshStackProviderClient) CreateWorkspace(workspace *MeshWorkspaceCreate) (*MeshWorkspace, error) {
+ paylod, err := json.Marshal(workspace)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("POST", c.endpoints.Workspaces.String(), bytes.NewBuffer(paylod))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Content-Type", CONTENT_TYPE_WORKSPACE)
+ req.Header.Set("Accept", CONTENT_TYPE_WORKSPACE)
+
+ res, err := c.doAuthenticatedRequest(req)
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+
+ data, err := io.ReadAll(res.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ if !isSuccessHTTPStatus(res) {
+ return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data)
+ }
+
+ var createdWorkspace MeshWorkspace
+ err = json.Unmarshal(data, &createdWorkspace)
+ if err != nil {
+ return nil, err
+ }
+ return &createdWorkspace, nil
+}
+
+func (c *MeshStackProviderClient) UpdateWorkspace(name string, workspace *MeshWorkspaceCreate) (*MeshWorkspace, error) {
+ targetUrl := c.urlForWorkspace(name)
+
+ paylod, err := json.Marshal(workspace)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("PUT", targetUrl.String(), bytes.NewBuffer(paylod))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Content-Type", CONTENT_TYPE_WORKSPACE)
+ req.Header.Set("Accept", CONTENT_TYPE_WORKSPACE)
+
+ res, err := c.doAuthenticatedRequest(req)
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+
+ data, err := io.ReadAll(res.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ if !isSuccessHTTPStatus(res) {
+ return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data)
+ }
+
+ var updatedWorkspace MeshWorkspace
+ err = json.Unmarshal(data, &updatedWorkspace)
+ if err != nil {
+ return nil, err
+ }
+ return &updatedWorkspace, nil
+}
+
+func (c *MeshStackProviderClient) DeleteWorkspace(name string) error {
+ targetUrl := c.urlForWorkspace(name)
+ return c.deleteMeshObject(*targetUrl, 204)
+}
diff --git a/docs/data-sources/project.md b/docs/data-sources/project.md
index 5e49416..66218a7 100644
--- a/docs/data-sources/project.md
+++ b/docs/data-sources/project.md
@@ -31,7 +31,7 @@ data "meshstack_project" "example" {
### Read-Only
- `api_version` (String) Project datatype version
-- `kind` (String) meshObject type, always `meshBuildingBlock`.
+- `kind` (String) meshObject type, always `meshProject`.
- `spec` (Attributes) Project specification. (see [below for nested schema](#nestedatt--spec))
diff --git a/docs/data-sources/workspace.md b/docs/data-sources/workspace.md
new file mode 100644
index 0000000..9a665d5
--- /dev/null
+++ b/docs/data-sources/workspace.md
@@ -0,0 +1,56 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "meshstack_workspace Data Source - terraform-provider-meshstack"
+subcategory: ""
+description: |-
+ Read a single workspace by identifier.
+---
+
+# meshstack_workspace (Data Source)
+
+Read a single workspace by identifier.
+
+## Example Usage
+
+```terraform
+data "meshstack_workspace" "example" {
+ metadata = {
+ name = "my-workspace-identifier"
+ }
+}
+```
+
+
+## Schema
+
+### Required
+
+- `metadata` (Attributes) (see [below for nested schema](#nestedatt--metadata))
+
+### Read-Only
+
+- `api_version` (String) Workspace API version.
+- `kind` (String) meshObject type, always `meshWorkspace`.
+- `spec` (Attributes) (see [below for nested schema](#nestedatt--spec))
+
+
+### Nested Schema for `metadata`
+
+Required:
+
+- `name` (String) Workspace identifier.
+
+Read-Only:
+
+- `created_on` (String) Creation date of the workspace.
+- `deleted_on` (String) Deletion date of the workspace.
+- `tags` (Map of List of String) Tags of the workspace.
+
+
+
+### Nested Schema for `spec`
+
+Read-Only:
+
+- `display_name` (String) Display name of the workspace.
+- `platform_builder_access_enabled` (Boolean) Whether platform builder access is enabled for the workspace.
diff --git a/docs/resources/workspace.md b/docs/resources/workspace.md
new file mode 100644
index 0000000..7723622
--- /dev/null
+++ b/docs/resources/workspace.md
@@ -0,0 +1,82 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "meshstack_workspace Resource - terraform-provider-meshstack"
+subcategory: ""
+description: |-
+ Represents a meshStack workspace.
+ ~> Note: Managing workspaces requires an API key with sufficient admin permissions.
+---
+
+# meshstack_workspace (Resource)
+
+Represents a meshStack workspace.
+
+~> **Note:** Managing workspaces requires an API key with sufficient admin permissions.
+
+## Example Usage
+
+```terraform
+resource "meshstack_workspace" "example" {
+ metadata = {
+ name = "my-workspace-identifier"
+ tags = {
+ "cost-center" = [
+ "12345"
+ ]
+ }
+ }
+ spec = {
+ display_name = "My Workspace's Display Name"
+ }
+}
+```
+
+
+## Schema
+
+### Required
+
+- `metadata` (Attributes) (see [below for nested schema](#nestedatt--metadata))
+- `spec` (Attributes) (see [below for nested schema](#nestedatt--spec))
+
+### Read-Only
+
+- `api_version` (String) Workspace datatype version
+- `kind` (String) meshObject type, always `meshWorkspace`.
+
+
+### Nested Schema for `metadata`
+
+Required:
+
+- `name` (String) Workspace identifier.
+
+Optional:
+
+- `tags` (Map of List of String) Tags of the workspace.
+
+Read-Only:
+
+- `created_on` (String) Creation date of the workspace.
+- `deleted_on` (String) Deletion date of the workspace.
+
+
+
+### Nested Schema for `spec`
+
+Required:
+
+- `display_name` (String) Display name of the workspace.
+
+Optional:
+
+- `platform_builder_access_enabled` (Boolean) Whether platform builder access is enabled for the workspace.
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+# import via workspace identifier
+terraform import meshstack_workspace.example my-workspace-identifier
+```
diff --git a/examples/data-sources/meshstack_workspace/data-source.tf b/examples/data-sources/meshstack_workspace/data-source.tf
new file mode 100644
index 0000000..18c6dfe
--- /dev/null
+++ b/examples/data-sources/meshstack_workspace/data-source.tf
@@ -0,0 +1,5 @@
+data "meshstack_workspace" "example" {
+ metadata = {
+ name = "my-workspace-identifier"
+ }
+}
diff --git a/examples/resources/meshstack_workspace/import.sh b/examples/resources/meshstack_workspace/import.sh
new file mode 100644
index 0000000..4f545f3
--- /dev/null
+++ b/examples/resources/meshstack_workspace/import.sh
@@ -0,0 +1,2 @@
+# import via workspace identifier
+terraform import meshstack_workspace.example my-workspace-identifier
diff --git a/examples/resources/meshstack_workspace/resource.tf b/examples/resources/meshstack_workspace/resource.tf
new file mode 100644
index 0000000..007389f
--- /dev/null
+++ b/examples/resources/meshstack_workspace/resource.tf
@@ -0,0 +1,13 @@
+resource "meshstack_workspace" "example" {
+ metadata = {
+ name = "my-workspace-identifier"
+ tags = {
+ "cost-center" = [
+ "12345"
+ ]
+ }
+ }
+ spec = {
+ display_name = "My Workspace's Display Name"
+ }
+}
diff --git a/internal/provider/project_data_source.go b/internal/provider/project_data_source.go
index 7fcb9f2..8b47a58 100644
--- a/internal/provider/project_data_source.go
+++ b/internal/provider/project_data_source.go
@@ -43,7 +43,7 @@ func (d *projectDataSource) Schema(ctx context.Context, req datasource.SchemaReq
},
"kind": schema.StringAttribute{
- MarkdownDescription: "meshObject type, always `meshBuildingBlock`.",
+ MarkdownDescription: "meshObject type, always `meshProject`.",
Computed: true,
Validators: []validator.String{
stringvalidator.OneOf([]string{"meshProject"}...),
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index 94fe699..bd46b4e 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -107,6 +107,7 @@ func (p *MeshStackProvider) Resources(ctx context.Context) []func() resource.Res
NewTenantResource,
NewProjectUserBindingResource,
NewProjectGroupBindingResource,
+ NewWorkspaceResource,
NewBuildingBlockResource,
NewBuildingBlockV2Resource,
NewTagDefinitionResource,
@@ -121,6 +122,7 @@ func (p *MeshStackProvider) DataSources(ctx context.Context) []func() datasource
NewProjectsDataSource,
NewProjectUserBindingDataSource,
NewProjectGroupBindingDataSource,
+ NewWorkspaceDataSource,
NewTenantDataSource,
NewTagDefinitionDataSource,
NewTagDefinitionsDataSource,
diff --git a/internal/provider/workspace_data_source.go b/internal/provider/workspace_data_source.go
new file mode 100644
index 0000000..e374e48
--- /dev/null
+++ b/internal/provider/workspace_data_source.go
@@ -0,0 +1,140 @@
+package provider
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/meshcloud/terraform-provider-meshstack/client"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+// Ensure provider defined types fully satisfy framework interfaces.
+var (
+ _ datasource.DataSource = &workspaceDataSource{}
+ _ datasource.DataSourceWithConfigure = &workspaceDataSource{}
+)
+
+func NewWorkspaceDataSource() datasource.DataSource {
+ return &workspaceDataSource{}
+}
+
+type workspaceDataSource struct {
+ client *client.MeshStackProviderClient
+}
+
+func (d *workspaceDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_workspace"
+}
+
+// Schema defines the schema for the data source.
+func (d *workspaceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ MarkdownDescription: "Read a single workspace by identifier.",
+
+ Attributes: map[string]schema.Attribute{
+ "api_version": schema.StringAttribute{
+ MarkdownDescription: "Workspace API version.",
+ Computed: true,
+ },
+ "kind": schema.StringAttribute{
+ MarkdownDescription: "meshObject type, always `meshWorkspace`.",
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf([]string{"meshWorkspace"}...),
+ },
+ },
+
+ "metadata": schema.SingleNestedAttribute{
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{
+ MarkdownDescription: "Workspace identifier.",
+ Required: true,
+ },
+ "created_on": schema.StringAttribute{
+ MarkdownDescription: "Creation date of the workspace.",
+ Computed: true,
+ },
+ "deleted_on": schema.StringAttribute{
+ MarkdownDescription: "Deletion date of the workspace.",
+ Computed: true,
+ },
+ "tags": schema.MapAttribute{
+ MarkdownDescription: "Tags of the workspace.",
+ ElementType: types.ListType{ElemType: types.StringType},
+ Computed: true,
+ },
+ },
+ },
+
+ "spec": schema.SingleNestedAttribute{
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "display_name": schema.StringAttribute{
+ MarkdownDescription: "Display name of the workspace.",
+ Computed: true,
+ },
+ "platform_builder_access_enabled": schema.BoolAttribute{
+ MarkdownDescription: "Whether platform builder access is enabled for the workspace.",
+ Computed: true,
+ },
+ },
+ },
+ },
+ }
+}
+
+// Configure adds the provider configured client to the data source.
+func (d *workspaceDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ client, ok := req.ProviderData.(*client.MeshStackProviderClient)
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Data Source Configure Type",
+ fmt.Sprintf("Expected *client.MeshStackProviderClient, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+ return
+ }
+ d.client = client
+}
+
+// Read refreshes the Terraform state with the latest data.
+func (d *workspaceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var name string
+
+ // Read Terraform configuration data into the model
+ resp.Diagnostics.Append(req.Config.GetAttribute(ctx, path.Root("metadata").AtName("name"), &name)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ workspace, err := d.client.ReadWorkspace(name)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ fmt.Sprintf("Could not read workspace '%s'", name),
+ err.Error(),
+ )
+ return
+ }
+
+ if workspace == nil {
+ resp.Diagnostics.AddError(
+ "Workspace not found",
+ fmt.Sprintf("The requested workspace '%s' was not found.", name),
+ )
+ return
+ }
+
+ // client data maps directly to the schema so we just need to set the state
+ resp.Diagnostics.Append(resp.State.Set(ctx, workspace)...)
+}
diff --git a/internal/provider/workspace_resource.go b/internal/provider/workspace_resource.go
new file mode 100644
index 0000000..dc93988
--- /dev/null
+++ b/internal/provider/workspace_resource.go
@@ -0,0 +1,244 @@
+package provider
+
+import (
+ "context"
+ "fmt"
+ "regexp"
+
+ "github.com/meshcloud/terraform-provider-meshstack/client"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "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/booldefault"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault"
+ "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/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ resource.Resource = &workspaceResource{}
+ _ resource.ResourceWithConfigure = &workspaceResource{}
+ _ resource.ResourceWithImportState = &workspaceResource{}
+)
+
+// NewWorkspaceResource is a helper function to simplify the provider implementation.
+func NewWorkspaceResource() resource.Resource {
+ return &workspaceResource{}
+}
+
+// workspaceResource is the resource implementation.
+type workspaceResource struct {
+ client *client.MeshStackProviderClient
+}
+
+// Metadata returns the resource type name.
+func (r *workspaceResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_workspace"
+}
+
+// Configure adds the provider configured client to the resource.
+func (r *workspaceResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ client, ok := req.ProviderData.(*client.MeshStackProviderClient)
+
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Resource Configure Type",
+ fmt.Sprintf("Expected *MeshStackProviderClient, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+
+ return
+ }
+
+ r.client = client
+}
+
+func (r *workspaceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ MarkdownDescription: "Represents a meshStack workspace.\n\n~> **Note:** Managing workspaces requires an API key with sufficient admin permissions.",
+
+ Attributes: map[string]schema.Attribute{
+ "api_version": schema.StringAttribute{
+ MarkdownDescription: "Workspace datatype version",
+ Computed: true,
+ Default: stringdefault.StaticString("v2"),
+ PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
+ },
+ "kind": schema.StringAttribute{
+ MarkdownDescription: "meshObject type, always `meshWorkspace`.",
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf([]string{"meshWorkspace"}...),
+ },
+ },
+
+ "metadata": schema.SingleNestedAttribute{
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{
+ MarkdownDescription: "Workspace identifier.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ stringvalidator.RegexMatches(
+ regexp.MustCompile(`^[a-z0-9]+(-[a-z0-9]+)*$`),
+ "must be alphanumeric with dashes, must be lowercase, and have no leading, trailing or consecutive dashes",
+ ),
+ },
+ },
+ "created_on": schema.StringAttribute{
+ MarkdownDescription: "Creation date of the workspace.",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
+ },
+ "deleted_on": schema.StringAttribute{
+ MarkdownDescription: "Deletion date of the workspace.",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
+ },
+ "tags": schema.MapAttribute{
+ MarkdownDescription: "Tags of the workspace.",
+ ElementType: types.ListType{ElemType: types.StringType},
+ Optional: true,
+ Computed: true,
+ Default: mapdefault.StaticValue(types.MapValueMust(types.ListType{ElemType: types.StringType}, map[string]attr.Value{})),
+ },
+ },
+ },
+
+ "spec": schema.SingleNestedAttribute{
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "display_name": schema.StringAttribute{
+ MarkdownDescription: "Display name of the workspace.",
+ Required: true,
+ },
+ "platform_builder_access_enabled": schema.BoolAttribute{
+ MarkdownDescription: "Whether platform builder access is enabled for the workspace.",
+ Optional: true,
+ Computed: true,
+ Default: booldefault.StaticBool(false),
+ },
+ },
+ },
+ },
+ }
+}
+
+func (r *workspaceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ workspace := client.MeshWorkspaceCreate{
+ Metadata: client.MeshWorkspaceCreateMetadata{},
+ }
+
+ // Retrieve values from plan
+ resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("api_version"), &workspace.ApiVersion)...)
+ resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("spec"), &workspace.Spec)...)
+ resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("metadata").AtName("name"), &workspace.Metadata.Name)...)
+ resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("metadata").AtName("tags"), &workspace.Metadata.Tags)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ createdWorkspace, err := r.client.CreateWorkspace(&workspace)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error Creating Workspace",
+ "Could not create workspace, unexpected error: "+err.Error(),
+ )
+ return
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, createdWorkspace)...)
+}
+
+func (r *workspaceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var name string
+
+ // Read Terraform state data into the model
+ resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("metadata").AtName("name"), &name)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ workspace, err := r.client.ReadWorkspace(name)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ fmt.Sprintf("Could not read workspace '%s'", name),
+ err.Error(),
+ )
+ return
+ }
+
+ if workspace == nil {
+ // The workspace was deleted outside of Terraform, so we remove it from the state
+ resp.State.RemoveResource(ctx)
+ return
+ }
+
+ // client data maps directly to the schema so we just need to set the state
+ resp.Diagnostics.Append(resp.State.Set(ctx, workspace)...)
+}
+
+func (r *workspaceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ workspace := client.MeshWorkspaceCreate{
+ Metadata: client.MeshWorkspaceCreateMetadata{},
+ }
+
+ // Retrieve values from plan
+ resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("api_version"), &workspace.ApiVersion)...)
+ resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("spec"), &workspace.Spec)...)
+ resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("metadata").AtName("name"), &workspace.Metadata.Name)...)
+ resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, path.Root("metadata").AtName("tags"), &workspace.Metadata.Tags)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ updatedWorkspace, err := r.client.UpdateWorkspace(workspace.Metadata.Name, &workspace)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error Updating Workspace",
+ "Could not update workspace, unexpected error: "+err.Error(),
+ )
+ return
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, updatedWorkspace)...)
+}
+
+func (r *workspaceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var name string
+
+ resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("metadata").AtName("name"), &name)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ err := r.client.DeleteWorkspace(name)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ fmt.Sprintf("Could not delete workspace '%s'", name),
+ err.Error(),
+ )
+ return
+ }
+}
+
+func (r *workspaceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("metadata").AtName("name"), req, resp)
+}