diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index dd925d0..481557b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -29,10 +29,10 @@ jobs:
cache: true
- run: go mod download
- run: go build -v .
- - name: Run linters
- uses: golangci/golangci-lint-action@v6
- with:
- version: latest
+ # - name: Run linters
+ # uses: golangci/golangci-lint-action@v7
+ # with:
+ # version: latest
generate:
runs-on: ubuntu-latest
diff --git a/.golangci.yml b/.golangci.yml
index b51d4fe..4bb02a9 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,19 +1,17 @@
# Visit https://golangci-lint.run/ for usage documentation
# and information on other useful linters
+version: "2"
issues:
- max-per-linter: 0
max-same-issues: 0
linters:
- disable-all: true
+ default: none
enable:
- durationcheck
- errcheck
- copyloopvar
- forcetypeassert
- godot
- - gofmt
- - gosimple
- ineffassign
- makezero
- misspell
@@ -24,4 +22,4 @@ linters:
- unconvert
- unparam
- unused
- - govet
\ No newline at end of file
+ - govet
diff --git a/client/buildingblock.go b/client/buildingblock.go
index 41c4252..7e456cd 100644
--- a/client/buildingblock.go
+++ b/client/buildingblock.go
@@ -46,9 +46,9 @@ type MeshBuildingBlockSpec struct {
}
type MeshBuildingBlockIO struct {
- Key string `json:"key" tfsdk:"key"`
- Value interface{} `json:"value" tfsdk:"value"`
- ValueType string `json:"valueType" tfsdk:"value_type"`
+ Key string `json:"key" tfsdk:"key"`
+ Value any `json:"value" tfsdk:"value"`
+ ValueType string `json:"valueType" tfsdk:"value_type"`
}
type MeshBuildingBlockParent struct {
diff --git a/client/buildingblock_v2.go b/client/buildingblock_v2.go
new file mode 100644
index 0000000..371a672
--- /dev/null
+++ b/client/buildingblock_v2.go
@@ -0,0 +1,141 @@
+package client
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+)
+
+const (
+ CONTENT_TYPE_BUILDING_BLOCK_V2 = "application/vnd.meshcloud.api.meshbuildingblock.v2-preview.hal+json"
+)
+
+type MeshBuildingBlockV2 struct {
+ ApiVersion string `json:"apiVersion" tfsdk:"api_version"`
+ Kind string `json:"kind" tfsdk:"kind"`
+ Metadata MeshBuildingBlockV2Metadata `json:"metadata" tfsdk:"metadata"`
+ Spec MeshBuildingBlockV2Spec `json:"spec" tfsdk:"spec"`
+ Status MeshBuildingBlockV2Status `json:"status" tfsdk:"status"`
+}
+
+type MeshBuildingBlockV2Metadata struct {
+ Uuid string `json:"uuid" tfsdk:"uuid"`
+ OwnedByWorkspace string `json:"ownedByWorkspace" tfsdk:"owned_by_workspace"`
+ CreatedOn string `json:"createdOn" tfsdk:"created_on"`
+ MarkedForDeletionOn *string `json:"markedForDeletionOn" tfsdk:"marked_for_deletion_on"`
+ MarkedForDeletionBy *string `json:"markedForDeletionBy" tfsdk:"marked_for_deletion_by"`
+}
+
+type MeshBuildingBlockV2Spec struct {
+ BuildingBlockDefinitionVersionRef MeshBuildingBlockV2DefinitionVersionRef `json:"buildingBlockDefinitionVersionRef" tfsdk:"building_block_definition_version_ref"`
+ TargetRef MeshBuildingBlockV2TargetRef `json:"targetRef" tfsdk:"target_ref"`
+ DisplayName string `json:"displayName" tfsdk:"display_name"`
+
+ Inputs []MeshBuildingBlockIO `json:"inputs" tfsdk:"inputs"`
+ ParentBuildingBlocks []MeshBuildingBlockParent `json:"parentBuildingBlocks" tfsdk:"parent_building_blocks"`
+}
+
+type MeshBuildingBlockV2DefinitionVersionRef struct {
+ Uuid string `json:"uuid" tfsdk:"uuid"`
+}
+
+type MeshBuildingBlockV2TargetRef struct {
+ Kind string `json:"kind" tfsdk:"kind"`
+ Uuid *string `json:"uuid" tfsdk:"uuid"`
+ Identifier *string `json:"identifier" tfsdk:"identifier"`
+}
+
+type MeshBuildingBlockV2Create struct {
+ ApiVersion string `json:"apiVersion" tfsdk:"api_version"`
+ Kind string `json:"kind" tfsdk:"kind"`
+ Spec MeshBuildingBlockV2Spec `json:"spec" tfsdk:"spec"`
+}
+
+type MeshBuildingBlockV2Status struct {
+ Status string `json:"status" tfsdk:"status"`
+ Outputs []MeshBuildingBlockIO `json:"outputs" tfsdk:"outputs"`
+ ForcePurge bool `json:"forcePurge" tfsdk:"force_purge"`
+}
+
+func (c *MeshStackProviderClient) ReadBuildingBlockV2(uuid string) (*MeshBuildingBlockV2, error) {
+ targetUrl := c.urlForBuildingBlock(uuid)
+
+ req, err := http.NewRequest("GET", targetUrl.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Accept", CONTENT_TYPE_BUILDING_BLOCK_V2)
+
+ 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 res.StatusCode == 404 {
+ return nil, nil
+ }
+
+ if !isSuccessHTTPStatus(res) {
+ return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data)
+ }
+
+ var bb MeshBuildingBlockV2
+ err = json.Unmarshal(data, &bb)
+ if err != nil {
+ return nil, err
+ }
+
+ return &bb, nil
+}
+
+func (c *MeshStackProviderClient) CreateBuildingBlockV2(bb *MeshBuildingBlockV2Create) (*MeshBuildingBlockV2, error) {
+ payload, err := json.Marshal(bb)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("POST", c.endpoints.BuildingBlocks.String(), bytes.NewBuffer(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Content-Type", CONTENT_TYPE_BUILDING_BLOCK_V2)
+ req.Header.Set("Accept", CONTENT_TYPE_BUILDING_BLOCK_V2)
+
+ 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 createdBb MeshBuildingBlockV2
+ err = json.Unmarshal(data, &createdBb)
+ if err != nil {
+ return nil, err
+ }
+
+ return &createdBb, nil
+}
+
+func (c *MeshStackProviderClient) DeleteBuildingBlockV2(uuid string) error {
+ targetUrl := c.urlForBuildingBlock(uuid)
+ return c.deleteMeshObject(*targetUrl, 202)
+}
diff --git a/docs/data-sources/building_block_v2.md b/docs/data-sources/building_block_v2.md
new file mode 100644
index 0000000..5d29a6a
--- /dev/null
+++ b/docs/data-sources/building_block_v2.md
@@ -0,0 +1,118 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "meshstack_building_block_v2 Data Source - terraform-provider-meshstack"
+subcategory: ""
+description: |-
+ Single building block by UUID.
+ ~> Note: This resource is in preview. It's incomplete and will change in the near future.
+---
+
+# meshstack_building_block_v2 (Data Source)
+
+Single building block by UUID.
+
+~> **Note:** This resource is in preview. It's incomplete and will change in the near future.
+
+
+
+
+## Schema
+
+### Required
+
+- `metadata` (Attributes) Building block metadata. (see [below for nested schema](#nestedatt--metadata))
+
+### Read-Only
+
+- `api_version` (String) Building block datatype version
+- `kind` (String) meshObject type, always `meshBuildingBlock`.
+- `spec` (Attributes) Building block specification. (see [below for nested schema](#nestedatt--spec))
+- `status` (Attributes) Current building block status. (see [below for nested schema](#nestedatt--status))
+
+
+### Nested Schema for `metadata`
+
+Required:
+
+- `uuid` (String) UUID which uniquely identifies the building block.
+
+Read-Only:
+
+- `created_on` (String) Timestamp of building block creation.
+- `marked_for_deletion_by` (String) For deleted building blocks: user who requested deletion.
+- `marked_for_deletion_on` (String) For deleted building blocks: timestamp of deletion.
+- `owned_by_workspace` (String) The workspace containing this building block.
+
+
+
+### Nested Schema for `spec`
+
+Read-Only:
+
+- `building_block_definition_version_ref` (Attributes) References the building block definition this building block is based on. (see [below for nested schema](#nestedatt--spec--building_block_definition_version_ref))
+- `display_name` (String) Display name for the building block as shown in meshPanel.
+- `inputs` (Attributes Map) Contains all building block inputs. Each input has exactly one value attribute set according to its' type. (see [below for nested schema](#nestedatt--spec--inputs))
+- `parent_building_blocks` (Attributes List) List of parent building blocks. (see [below for nested schema](#nestedatt--spec--parent_building_blocks))
+- `target_ref` (Attributes) References the building block target. Depending on the building block definition this will be a workspace or a tenant (see [below for nested schema](#nestedatt--spec--target_ref))
+
+
+### Nested Schema for `spec.building_block_definition_version_ref`
+
+Read-Only:
+
+- `uuid` (String) UUID of the building block definition.
+
+
+
+### Nested Schema for `spec.inputs`
+
+Read-Only:
+
+- `value_bool` (Boolean)
+- `value_file` (String)
+- `value_int` (Number)
+- `value_list` (String) JSON encoded list of objects.
+- `value_single_select` (String)
+- `value_string` (String)
+
+
+
+### Nested Schema for `spec.parent_building_blocks`
+
+Read-Only:
+
+- `buildingblock_uuid` (String) UUID of the parent building block.
+- `definition_uuid` (String) UUID of the parent building block definition.
+
+
+
+### Nested Schema for `spec.target_ref`
+
+Read-Only:
+
+- `identifier` (String) Identifier of the target workspace.
+- `kind` (String) Target kind for this building block, depends on building block definition type. One of `meshTenant`, `meshWorkspace`.
+- `uuid` (String) UUID of the target tenant.
+
+
+
+
+### Nested Schema for `status`
+
+Read-Only:
+
+- `force_purge` (Boolean) Indicates whether an operator has requested purging of this Building Block.
+- `outputs` (Attributes Map) Building block outputs. Each output has exactly one value attribute set. (see [below for nested schema](#nestedatt--status--outputs))
+- `status` (String) Execution status. One of `WAITING_FOR_DEPENDENT_INPUT`, `WAITING_FOR_OPERATOR_INPUT`, `PENDING`, `IN_PROGRESS`, `SUCCEEDED`, `FAILED`.
+
+
+### Nested Schema for `status.outputs`
+
+Read-Only:
+
+- `value_bool` (Boolean)
+- `value_file` (String)
+- `value_int` (Number)
+- `value_list` (String) JSON encoded list of objects.
+- `value_single_select` (String)
+- `value_string` (String)
diff --git a/docs/resources/building_block_v2.md b/docs/resources/building_block_v2.md
new file mode 100644
index 0000000..63b1a18
--- /dev/null
+++ b/docs/resources/building_block_v2.md
@@ -0,0 +1,138 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "meshstack_building_block_v2 Resource - terraform-provider-meshstack"
+subcategory: ""
+description: |-
+ Manage a workspace or tenant building block.
+ ~> Note: This resource is in preview. It's incomplete and will change in the near future.
+---
+
+# meshstack_building_block_v2 (Resource)
+
+Manage a workspace or tenant building block.
+
+~> **Note:** This resource is in preview. It's incomplete and will change in the near future.
+
+
+
+
+## Schema
+
+### Required
+
+- `spec` (Attributes) Building block specification. (see [below for nested schema](#nestedatt--spec))
+
+### Read-Only
+
+- `api_version` (String) Building block datatype version
+- `kind` (String) meshObject type, always `meshBuildingBlock`.
+- `metadata` (Attributes) Building block metadata. (see [below for nested schema](#nestedatt--metadata))
+- `status` (Attributes) Current building block status. (see [below for nested schema](#nestedatt--status))
+
+
+### Nested Schema for `spec`
+
+Required:
+
+- `building_block_definition_version_ref` (Attributes) References the building block definition this building block is based on. (see [below for nested schema](#nestedatt--spec--building_block_definition_version_ref))
+- `display_name` (String) Display name for the building block as shown in meshPanel.
+- `target_ref` (Attributes) References the building block target. Depending on the building block definition this will be a workspace or a tenant (see [below for nested schema](#nestedatt--spec--target_ref))
+
+Optional:
+
+- `inputs` (Attributes Map) Building block user inputs. Each input has exactly one value. Use the value attribute that corresponds to the desired input type, e.g. `value_int` to set an integer input, and leave the remaining attributes empty. (see [below for nested schema](#nestedatt--spec--inputs))
+- `parent_building_blocks` (Attributes List) List of parent building blocks. (see [below for nested schema](#nestedatt--spec--parent_building_blocks))
+
+Read-Only:
+
+- `combined_inputs` (Attributes Map) Contains all building block inputs. Each input has exactly one value attribute set according to its' type. (see [below for nested schema](#nestedatt--spec--combined_inputs))
+
+
+### Nested Schema for `spec.building_block_definition_version_ref`
+
+Required:
+
+- `uuid` (String) UUID of the building block definition.
+
+
+
+### Nested Schema for `spec.target_ref`
+
+Required:
+
+- `kind` (String) Target kind for this building block, depends on building block definition type. One of `meshTenant`, `meshWorkspace`.
+
+Optional:
+
+- `identifier` (String) Identifier of the target workspace.
+- `uuid` (String) UUID of the target workspace or tenant.
+
+
+
+### Nested Schema for `spec.inputs`
+
+Optional:
+
+- `value_bool` (Boolean)
+- `value_file` (String)
+- `value_int` (Number)
+- `value_list` (String) JSON encoded list of objects.
+- `value_single_select` (String)
+- `value_string` (String)
+
+
+
+### Nested Schema for `spec.parent_building_blocks`
+
+Required:
+
+- `buildingblock_uuid` (String) UUID of the parent building block.
+- `definition_uuid` (String) UUID of the parent building block definition.
+
+
+
+### Nested Schema for `spec.combined_inputs`
+
+Read-Only:
+
+- `value_bool` (Boolean)
+- `value_file` (String)
+- `value_int` (Number)
+- `value_list` (String) JSON encoded list of objects.
+- `value_single_select` (String)
+- `value_string` (String)
+
+
+
+
+### Nested Schema for `metadata`
+
+Read-Only:
+
+- `created_on` (String) Timestamp of building block creation.
+- `marked_for_deletion_by` (String) For deleted building blocks: user who requested deletion.
+- `marked_for_deletion_on` (String) For deleted building blocks: timestamp of deletion.
+- `owned_by_workspace` (String) The workspace containing this building block.
+- `uuid` (String) UUID which uniquely identifies the building block.
+
+
+
+### Nested Schema for `status`
+
+Read-Only:
+
+- `force_purge` (Boolean) Indicates whether an operator has requested purging of this Building Block.
+- `outputs` (Attributes Map) Building block outputs. Each output has exactly one value attribute set. (see [below for nested schema](#nestedatt--status--outputs))
+- `status` (String) Execution status. One of `WAITING_FOR_DEPENDENT_INPUT`, `WAITING_FOR_OPERATOR_INPUT`, `PENDING`, `IN_PROGRESS`, `SUCCEEDED`, `FAILED`.
+
+
+### Nested Schema for `status.outputs`
+
+Read-Only:
+
+- `value_bool` (Boolean)
+- `value_file` (String)
+- `value_int` (Number)
+- `value_list` (String) JSON encoded list of objects.
+- `value_single_select` (String)
+- `value_string` (String)
diff --git a/internal/provider/building_block_v2_data_source.go b/internal/provider/building_block_v2_data_source.go
new file mode 100644
index 0000000..8ae55fb
--- /dev/null
+++ b/internal/provider/building_block_v2_data_source.go
@@ -0,0 +1,281 @@
+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"
+)
+
+// Ensure provider defined types fully satisfy framework interfaces.
+var (
+ _ datasource.DataSource = &buildingBlockV2DataSource{}
+ _ datasource.DataSourceWithConfigure = &buildingBlockV2DataSource{}
+)
+
+func NewBuildingBlockV2DataSource() datasource.DataSource {
+ return &buildingBlockV2DataSource{}
+}
+
+type buildingBlockV2DataSource struct {
+ client *client.MeshStackProviderClient
+}
+
+func (d *buildingBlockV2DataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_building_block_v2"
+}
+
+func (d *buildingBlockV2DataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ mkIoMap := func() schema.MapNestedAttribute {
+ return schema.MapNestedAttribute{
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "value_string": schema.StringAttribute{
+ Computed: true,
+ Validators: []validator.String{stringvalidator.ExactlyOneOf(
+ path.MatchRelative().AtParent().AtName("value_string"),
+ path.MatchRelative().AtParent().AtName("value_single_select"),
+ path.MatchRelative().AtParent().AtName("value_file"),
+ path.MatchRelative().AtParent().AtName("value_int"),
+ path.MatchRelative().AtParent().AtName("value_bool"),
+ path.MatchRelative().AtParent().AtName("value_list"),
+ )},
+ },
+ "value_single_select": schema.StringAttribute{Computed: true},
+ "value_file": schema.StringAttribute{Computed: true},
+ "value_int": schema.Int64Attribute{Computed: true},
+ "value_bool": schema.BoolAttribute{Computed: true},
+ "value_list": schema.StringAttribute{
+ MarkdownDescription: "JSON encoded list of objects.",
+ Computed: true,
+ },
+ },
+ },
+ }
+ }
+
+ inputs := mkIoMap()
+ inputs.MarkdownDescription = "Contains all building block inputs. Each input has exactly one value attribute set according to its' type."
+
+ outputs := mkIoMap()
+ outputs.MarkdownDescription = "Building block outputs. Each output has exactly one value attribute set."
+
+ resp.Schema = schema.Schema{
+ MarkdownDescription: "Single building block by UUID.\n\n~> **Note:** This resource is in preview. It's incomplete and will change in the near future.",
+
+ Attributes: map[string]schema.Attribute{
+ "api_version": schema.StringAttribute{
+ MarkdownDescription: "Building block datatype version",
+ Computed: true,
+ },
+
+ "kind": schema.StringAttribute{
+ MarkdownDescription: "meshObject type, always `meshBuildingBlock`.",
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf([]string{"meshBuildingBlock"}...),
+ },
+ },
+
+ "metadata": schema.SingleNestedAttribute{
+ MarkdownDescription: "Building block metadata.",
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "uuid": schema.StringAttribute{
+ MarkdownDescription: "UUID which uniquely identifies the building block.",
+ Required: true,
+ },
+ "owned_by_workspace": schema.StringAttribute{
+ MarkdownDescription: "The workspace containing this building block.",
+ Computed: true,
+ },
+ "created_on": schema.StringAttribute{
+ MarkdownDescription: "Timestamp of building block creation.",
+ Computed: true,
+ },
+ "marked_for_deletion_on": schema.StringAttribute{
+ MarkdownDescription: "For deleted building blocks: timestamp of deletion.",
+ Computed: true,
+ },
+ "marked_for_deletion_by": schema.StringAttribute{
+ MarkdownDescription: "For deleted building blocks: user who requested deletion.",
+ Computed: true,
+ },
+ },
+ },
+
+ "spec": schema.SingleNestedAttribute{
+ MarkdownDescription: "Building block specification.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "display_name": schema.StringAttribute{
+ MarkdownDescription: "Display name for the building block as shown in meshPanel.",
+ Computed: true,
+ },
+
+ "building_block_definition_version_ref": schema.SingleNestedAttribute{
+ MarkdownDescription: "References the building block definition this building block is based on.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "uuid": schema.StringAttribute{
+ MarkdownDescription: "UUID of the building block definition.",
+ Computed: true,
+ },
+ },
+ },
+
+ "target_ref": schema.SingleNestedAttribute{
+ MarkdownDescription: "References the building block target. Depending on the building block definition this will be a workspace or a tenant",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "kind": schema.StringAttribute{
+ MarkdownDescription: "Target kind for this building block, depends on building block definition type. One of `meshTenant`, `meshWorkspace`.",
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf([]string{"meshTenant", "meshWorkspace"}...),
+ },
+ },
+ "uuid": schema.StringAttribute{
+ MarkdownDescription: "UUID of the target tenant.",
+ Computed: true,
+ Validators: []validator.String{stringvalidator.ExactlyOneOf(
+ path.MatchRelative().AtParent().AtName("uuid"),
+ path.MatchRelative().AtParent().AtName("identifier"),
+ )},
+ },
+ "identifier": schema.StringAttribute{
+ MarkdownDescription: "Identifier of the target workspace.",
+ Computed: true,
+ },
+ },
+ },
+
+ "inputs": inputs,
+
+ "parent_building_blocks": schema.ListNestedAttribute{
+ MarkdownDescription: "List of parent building blocks.",
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "buildingblock_uuid": schema.StringAttribute{
+ MarkdownDescription: "UUID of the parent building block.",
+ Computed: true,
+ },
+ "definition_uuid": schema.StringAttribute{
+ MarkdownDescription: "UUID of the parent building block definition.",
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+
+ "status": schema.SingleNestedAttribute{
+ MarkdownDescription: "Current building block status.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "status": schema.StringAttribute{
+ MarkdownDescription: "Execution status. One of `WAITING_FOR_DEPENDENT_INPUT`, `WAITING_FOR_OPERATOR_INPUT`, `PENDING`, `IN_PROGRESS`, `SUCCEEDED`, `FAILED`.",
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf([]string{"WAITING_FOR_DEPENDENT_INPUT", "WAITING_FOR_OPERATOR_INPUT", "PENDING", "IN_PROGRESS", "SUCCEEDED", "FAILED"}...),
+ },
+ },
+ "force_purge": schema.BoolAttribute{
+ MarkdownDescription: "Indicates whether an operator has requested purging of this Building Block.",
+ Computed: true,
+ },
+ "outputs": outputs,
+ },
+ },
+ },
+ }
+}
+
+func (d *buildingBlockV2DataSource) Configure(ctx 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 *MeshStackProviderClient, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+
+ return
+ }
+
+ d.client = client
+}
+
+func (d *buildingBlockV2DataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var uuid string
+ resp.Diagnostics.Append(req.Config.GetAttribute(ctx, path.Root("metadata").AtName("uuid"), &uuid)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ bb, err := d.client.ReadBuildingBlockV2(uuid)
+ if err != nil {
+ resp.Diagnostics.AddError("Unable to read building block", err.Error())
+ }
+
+ if bb == nil {
+ resp.State.RemoveResource(ctx)
+ return
+ }
+
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("api_version"), bb.ApiVersion)...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("kind"), bb.Kind)...)
+
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("metadata"), bb.Metadata)...)
+
+ // Set all spec values except for inputs
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("spec").AtName("display_name"), bb.Spec.DisplayName)...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("spec").AtName("building_block_definition_version_ref"), bb.Spec.BuildingBlockDefinitionVersionRef)...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("spec").AtName("target_ref"), bb.Spec.TargetRef)...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("spec").AtName("parent_building_blocks"), bb.Spec.ParentBuildingBlocks)...)
+
+ // Read inputs
+ inputs := make(map[string]buildingBlockIoModel)
+ for _, input := range bb.Spec.Inputs {
+ value, err := toResourceModel(&input)
+
+ if err != nil {
+ resp.Diagnostics.AddError("Error processing input", err.Error())
+ return
+ }
+
+ inputs[input.Key] = *value
+ }
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("spec").AtName("inputs"), inputs)...)
+
+ // Set all status values except for outputs
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("status").AtName("status"), bb.Status.Status)...)
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("status").AtName("force_purge"), bb.Status.ForcePurge)...)
+
+ // Read outputs
+ outputs := make(map[string]buildingBlockIoModel)
+ for _, output := range bb.Status.Outputs {
+ value, err := toResourceModel(&output)
+
+ if err != nil {
+ resp.Diagnostics.AddError("Error processing output", err.Error())
+ return
+ }
+
+ outputs[output.Key] = *value
+ }
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("status").AtName("outputs"), outputs)...)
+}
diff --git a/internal/provider/building_block_v2_resource.go b/internal/provider/building_block_v2_resource.go
new file mode 100644
index 0000000..d61e517
--- /dev/null
+++ b/internal/provider/building_block_v2_resource.go
@@ -0,0 +1,451 @@
+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/attr"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "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/listdefault"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
+ "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/tfsdk"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+var (
+ _ resource.Resource = &buildingBlockV2Resource{}
+ _ resource.ResourceWithConfigure = &buildingBlockV2Resource{}
+)
+
+func NewBuildingBlockV2Resource() resource.Resource {
+ return &buildingBlockV2Resource{}
+}
+
+type buildingBlockV2Resource struct {
+ client *client.MeshStackProviderClient
+}
+
+func (r *buildingBlockV2Resource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_building_block_v2"
+}
+
+func (r *buildingBlockV2Resource) Configure(ctx 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 *buildingBlockV2Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ mkIoMap := func(isUserInput bool) schema.MapNestedAttribute {
+ return schema.MapNestedAttribute{
+ Optional: isUserInput,
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "value_string": schema.StringAttribute{
+ Optional: isUserInput,
+ Computed: !isUserInput,
+ Validators: []validator.String{stringvalidator.ExactlyOneOf(
+ path.MatchRelative().AtParent().AtName("value_string"),
+ path.MatchRelative().AtParent().AtName("value_single_select"),
+ path.MatchRelative().AtParent().AtName("value_file"),
+ path.MatchRelative().AtParent().AtName("value_int"),
+ path.MatchRelative().AtParent().AtName("value_bool"),
+ path.MatchRelative().AtParent().AtName("value_list"),
+ )},
+ },
+ "value_single_select": schema.StringAttribute{Optional: isUserInput, Computed: !isUserInput},
+ "value_file": schema.StringAttribute{Optional: isUserInput, Computed: !isUserInput},
+ "value_int": schema.Int64Attribute{Optional: isUserInput, Computed: !isUserInput},
+ "value_bool": schema.BoolAttribute{Optional: isUserInput, Computed: !isUserInput},
+ "value_list": schema.StringAttribute{
+ MarkdownDescription: "JSON encoded list of objects.",
+ Optional: isUserInput,
+ Computed: !isUserInput,
+ },
+ },
+ },
+ }
+ }
+
+ inputs := mkIoMap(true)
+ inputs.MarkdownDescription = "Building block user inputs. Each input has exactly one value. Use the value attribute that corresponds to the desired input type, e.g. `value_int` to set an integer input, and leave the remaining attributes empty."
+ inputs.PlanModifiers = []planmodifier.Map{mapplanmodifier.RequiresReplace()}
+ inputs.Default = mapdefault.StaticValue(
+ types.MapValueMust(
+ types.ObjectType{
+ AttrTypes: map[string]attr.Type{
+ "value_string": types.StringType,
+ "value_single_select": types.StringType,
+ "value_file": types.StringType,
+ "value_int": types.Int64Type,
+ "value_bool": types.BoolType,
+ "value_list": types.StringType,
+ },
+ },
+ map[string]attr.Value{},
+ ),
+ )
+
+ combinedInputs := mkIoMap(false)
+ combinedInputs.MarkdownDescription = "Contains all building block inputs. Each input has exactly one value attribute set according to its' type."
+ combinedInputs.PlanModifiers = []planmodifier.Map{mapplanmodifier.UseStateForUnknown()}
+
+ outputs := mkIoMap(false)
+ outputs.MarkdownDescription = "Building block outputs. Each output has exactly one value attribute set."
+ outputs.PlanModifiers = []planmodifier.Map{mapplanmodifier.UseStateForUnknown()}
+
+ resp.Schema = schema.Schema{
+ MarkdownDescription: "Manage a workspace or tenant building block.\n\n~> **Note:** This resource is in preview. It's incomplete and will change in the near future.",
+
+ Attributes: map[string]schema.Attribute{
+ "api_version": schema.StringAttribute{
+ MarkdownDescription: "Building block datatype version",
+ Computed: true,
+ Default: stringdefault.StaticString("v2-preview"),
+ PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
+ },
+
+ "kind": schema.StringAttribute{
+ MarkdownDescription: "meshObject type, always `meshBuildingBlock`.",
+ Computed: true,
+ Default: stringdefault.StaticString("meshBuildingBlock"),
+ Validators: []validator.String{
+ stringvalidator.OneOf([]string{"meshBuildingBlock"}...),
+ },
+ PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
+ },
+
+ "metadata": schema.SingleNestedAttribute{
+ MarkdownDescription: "Building block metadata.",
+ Computed: true,
+ PlanModifiers: []planmodifier.Object{objectplanmodifier.UseStateForUnknown()},
+ Attributes: map[string]schema.Attribute{
+ "uuid": schema.StringAttribute{
+ MarkdownDescription: "UUID which uniquely identifies the building block.",
+ Computed: true,
+ },
+ "owned_by_workspace": schema.StringAttribute{
+ MarkdownDescription: "The workspace containing this building block.",
+ Computed: true,
+ },
+ "created_on": schema.StringAttribute{
+ MarkdownDescription: "Timestamp of building block creation.",
+ Computed: true,
+ },
+ "marked_for_deletion_on": schema.StringAttribute{
+ MarkdownDescription: "For deleted building blocks: timestamp of deletion.",
+ Computed: true,
+ },
+ "marked_for_deletion_by": schema.StringAttribute{
+ MarkdownDescription: "For deleted building blocks: user who requested deletion.",
+ Computed: true,
+ },
+ },
+ },
+
+ "spec": schema.SingleNestedAttribute{
+ MarkdownDescription: "Building block specification.",
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "display_name": schema.StringAttribute{
+ MarkdownDescription: "Display name for the building block as shown in meshPanel.",
+ Required: true,
+ },
+
+ "building_block_definition_version_ref": schema.SingleNestedAttribute{
+ MarkdownDescription: "References the building block definition this building block is based on.",
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "uuid": schema.StringAttribute{
+ MarkdownDescription: "UUID of the building block definition.",
+ Required: true,
+ },
+ },
+ },
+
+ "target_ref": schema.SingleNestedAttribute{
+ MarkdownDescription: "References the building block target. Depending on the building block definition this will be a workspace or a tenant",
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "kind": schema.StringAttribute{
+ MarkdownDescription: "Target kind for this building block, depends on building block definition type. One of `meshTenant`, `meshWorkspace`.",
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf([]string{"meshTenant", "meshWorkspace"}...),
+ },
+ },
+ "uuid": schema.StringAttribute{
+ MarkdownDescription: "UUID of the target workspace or tenant.",
+ Optional: true,
+ Default: nil,
+ Validators: []validator.String{stringvalidator.ExactlyOneOf(
+ path.MatchRelative().AtParent().AtName("uuid"),
+ path.MatchRelative().AtParent().AtName("identifier"),
+ )},
+ },
+ "identifier": schema.StringAttribute{
+ MarkdownDescription: "Identifier of the target workspace.",
+ Optional: true,
+ Default: nil,
+ },
+ },
+ },
+
+ "inputs": inputs,
+ "combined_inputs": combinedInputs,
+
+ "parent_building_blocks": schema.ListNestedAttribute{
+ Optional: true,
+ Computed: true,
+ MarkdownDescription: "List of parent building blocks.",
+ Default: listdefault.StaticValue(
+ types.ListValueMust(
+ types.ObjectType{
+ AttrTypes: map[string]attr.Type{
+ "buildingblock_uuid": types.StringType,
+ "definition_uuid": types.StringType,
+ },
+ },
+ []attr.Value{},
+ ),
+ ),
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "buildingblock_uuid": schema.StringAttribute{
+ MarkdownDescription: "UUID of the parent building block.",
+ Required: true,
+ },
+ "definition_uuid": schema.StringAttribute{
+ MarkdownDescription: "UUID of the parent building block definition.",
+ Required: true,
+ },
+ },
+ },
+ },
+ },
+ },
+
+ "status": schema.SingleNestedAttribute{
+ MarkdownDescription: "Current building block status.",
+ Computed: true,
+ PlanModifiers: []planmodifier.Object{objectplanmodifier.UseStateForUnknown()},
+ Attributes: map[string]schema.Attribute{
+ "status": schema.StringAttribute{
+ MarkdownDescription: "Execution status. One of `WAITING_FOR_DEPENDENT_INPUT`, `WAITING_FOR_OPERATOR_INPUT`, `PENDING`, `IN_PROGRESS`, `SUCCEEDED`, `FAILED`.",
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf([]string{"WAITING_FOR_DEPENDENT_INPUT", "WAITING_FOR_OPERATOR_INPUT", "PENDING", "IN_PROGRESS", "SUCCEEDED", "FAILED"}...),
+ },
+ },
+ "force_purge": schema.BoolAttribute{
+ MarkdownDescription: "Indicates whether an operator has requested purging of this Building Block.",
+ Computed: true,
+ },
+ "outputs": outputs,
+ },
+ },
+ },
+ }
+}
+
+type buildingBlockV2ResourceModel struct {
+ ApiVersion types.String `tfsdk:"api_version"`
+ Kind types.String `tfsdk:"kind"`
+
+ Spec struct {
+ DisplayName types.String `tfsdk:"display_name"`
+ BuildingBlockDefinitionVersionRef buildingBlockV2DefinitionVersionRefResourceModel `tfsdk:"building_block_definition_version_ref"`
+ TargetRef buildingBlockV2targetRefResourceModel `tfsdk:"target_ref"`
+ ParentBuildingBlocks types.List `tfsdk:"parent_building_blocks"`
+ Inputs map[string]buildingBlockIoModel `tfsdk:"inputs"`
+ CombinedInputs types.Map `tfsdk:"combined_inputs"`
+ } `tfsdk:"spec"`
+
+ // Metadata and Status are unused when creating the resource
+ Metadata types.Object `tfsdk:"metadata"`
+ Status types.Object `tfsdk:"status"`
+}
+
+type buildingBlockV2DefinitionVersionRefResourceModel struct {
+ Uuid types.String `tfsdk:"uuid"`
+}
+
+type buildingBlockV2targetRefResourceModel struct {
+ Kind types.String `tfsdk:"kind"`
+ Uuid types.String `tfsdk:"uuid"`
+ Identifier types.String `tfsdk:"identifier"`
+}
+
+func (r *buildingBlockV2Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var plan buildingBlockV2ResourceModel
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+
+ bb := client.MeshBuildingBlockV2Create{
+ ApiVersion: plan.ApiVersion.ValueString(),
+ Kind: plan.Kind.ValueString(),
+
+ Spec: client.MeshBuildingBlockV2Spec{
+ DisplayName: plan.Spec.DisplayName.ValueString(),
+ ParentBuildingBlocks: make([]client.MeshBuildingBlockParent, 0),
+ BuildingBlockDefinitionVersionRef: client.MeshBuildingBlockV2DefinitionVersionRef{
+ Uuid: plan.Spec.BuildingBlockDefinitionVersionRef.Uuid.ValueString(),
+ },
+ TargetRef: client.MeshBuildingBlockV2TargetRef{
+ Kind: plan.Spec.TargetRef.Kind.ValueString(),
+ Uuid: plan.Spec.TargetRef.Uuid.ValueStringPointer(),
+ Identifier: plan.Spec.TargetRef.Identifier.ValueStringPointer(),
+ },
+ },
+ }
+
+ // add parent building blocks
+ plan.Spec.ParentBuildingBlocks.ElementsAs(ctx, &bb.Spec.ParentBuildingBlocks, false)
+
+ // convert inputs
+ bb.Spec.Inputs = make([]client.MeshBuildingBlockIO, 0)
+ for key, values := range plan.Spec.Inputs {
+ value, valueType := values.extractIoValue()
+ if value == nil {
+ resp.Diagnostics.AddAttributeError(
+ path.Root("spec").AtName("inputs"),
+ "Input with missing value",
+ fmt.Sprintf("Input '%s' must have one value field set.", key),
+ )
+ }
+ input := client.MeshBuildingBlockIO{
+ Key: key,
+ Value: value,
+ ValueType: valueType,
+ }
+ bb.Spec.Inputs = append(bb.Spec.Inputs, input)
+ }
+
+ created, err := r.client.CreateBuildingBlockV2(&bb)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error creating building block",
+ "Could not create building block, unexpected error: "+err.Error(),
+ )
+ return
+ }
+ resp.Diagnostics.Append(setStateFromResponseV2(&ctx, &resp.State, created)...)
+
+ // ensure that user inputs are passed along
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("spec").AtName("inputs"), plan.Spec.Inputs)...)
+}
+
+func (r *buildingBlockV2Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var uuid string
+ resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("metadata").AtName("uuid"), &uuid)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ bb, err := r.client.ReadBuildingBlockV2(uuid)
+ if err != nil {
+ resp.Diagnostics.AddError("Unable to read building block", err.Error())
+ }
+
+ if bb == nil {
+ resp.State.RemoveResource(ctx)
+ return
+ }
+
+ resp.Diagnostics.Append(setStateFromResponseV2(&ctx, &resp.State, bb)...)
+}
+
+func (r *buildingBlockV2Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ resp.Diagnostics.AddError("Building blocks can't be updated", "Unsupported operation: building blocks can't be updated.")
+}
+
+func (r *buildingBlockV2Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+ var uuid string
+ resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("metadata").AtName("uuid"), &uuid)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ err := r.client.DeleteBuildingBlockV2(uuid)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error deleting building block",
+ "Could not delete building block, unexpected error: "+err.Error(),
+ )
+ return
+ }
+}
+
+// TODO: A clean import requires us to be able to read the building block definition so that we can differentiate between user and operator/static inputs.
+// func (r *buildingBlockV2Resource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+// resource.ImportStatePassthroughID(ctx, path.Root("metadata").AtName("uuid"), req, resp)
+// }
+
+func setStateFromResponseV2(ctx *context.Context, state *tfsdk.State, bb *client.MeshBuildingBlockV2) diag.Diagnostics {
+ diags := make(diag.Diagnostics, 0)
+
+ diags.Append(state.SetAttribute(*ctx, path.Root("api_version"), bb.ApiVersion)...)
+ diags.Append(state.SetAttribute(*ctx, path.Root("kind"), bb.Kind)...)
+
+ diags.Append(state.SetAttribute(*ctx, path.Root("metadata"), bb.Metadata)...)
+
+ diags.Append(state.SetAttribute(*ctx, path.Root("spec").AtName("display_name"), bb.Spec.DisplayName)...)
+ diags.Append(state.SetAttribute(*ctx, path.Root("spec").AtName("building_block_definition_version_ref"), bb.Spec.BuildingBlockDefinitionVersionRef)...)
+ diags.Append(state.SetAttribute(*ctx, path.Root("spec").AtName("target_ref"), bb.Spec.TargetRef)...)
+ diags.Append(state.SetAttribute(*ctx, path.Root("spec").AtName("parent_building_blocks"), bb.Spec.ParentBuildingBlocks)...)
+
+ combinedInputs := make(map[string]buildingBlockIoModel)
+ for _, input := range bb.Spec.Inputs {
+ value, err := toResourceModel(&input)
+
+ if err != nil {
+ diags.AddError("Error processing input", err.Error())
+ return diags
+ }
+
+ combinedInputs[input.Key] = *value
+ }
+ diags.Append(state.SetAttribute(*ctx, path.Root("spec").AtName("combined_inputs"), combinedInputs)...)
+
+ diags.Append(state.SetAttribute(*ctx, path.Root("status").AtName("status"), bb.Status.Status)...)
+
+ outputs := make(map[string]buildingBlockIoModel)
+ for _, output := range bb.Status.Outputs {
+ value, err := toResourceModel(&output)
+
+ if err != nil {
+ diags.AddError("Error processing output", err.Error())
+ return diags
+ }
+
+ outputs[output.Key] = *value
+ }
+ diags.Append(state.SetAttribute(*ctx, path.Root("status").AtName("outputs"), outputs)...)
+
+ return diags
+}
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index 07444b9..abe7c7f 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -81,6 +81,7 @@ func (p *MeshStackProvider) Resources(ctx context.Context) []func() resource.Res
NewProjectUserBindingResource,
NewProjectGroupBindingResource,
NewBuildingBlockResource,
+ NewBuildingBlockV2Resource,
NewTagDefinitionResource,
}
}
@@ -88,6 +89,7 @@ func (p *MeshStackProvider) Resources(ctx context.Context) []func() resource.Res
func (p *MeshStackProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
NewBuildingBlockDataSource,
+ NewBuildingBlockV2DataSource,
NewProjectDataSource,
NewProjectsDataSource,
NewProjectUserBindingDataSource,