diff --git a/client/client.go b/client/client.go
index 9158f69..281d040 100644
--- a/client/client.go
+++ b/client/client.go
@@ -20,11 +20,6 @@ const (
ERROR_GENERIC_API_ERROR = "api error"
ERROR_AUTHENTICATION_FAILURE = "Not authorized. Check api key and secret."
ERROR_ENDPOINT_LOOKUP = "Could not fetch endpoints for meshStack."
-
- CONTENT_TYPE_PROJECT = "application/vnd.meshcloud.api.meshproject.v2.hal+json"
- CONTENT_TYPE_TENANT = "application/vnd.meshcloud.api.meshtenant.v3.hal+json"
- CONTENT_TYPE_PROJECT_USER_BINDINGS = "application/vnd.meshcloud.api.meshprojectuserbinding.v1.hal+json"
- CONTENT_TYPE_PROJECT_USER_BINDING = "application/vnd.meshcloud.api.meshprojectuserbinding.v3.hal+json"
)
type MeshStackProviderClient struct {
@@ -38,10 +33,11 @@ type MeshStackProviderClient struct {
}
type endpoints struct {
- BuildingBlocks *url.URL `json:"meshbuildingblocks"`
- Projects *url.URL `json:"meshprojects"`
- ProjectUserBindings *url.URL `json:"meshprojectuserbindings"`
- Tenants *url.URL `json:"meshtenants"`
+ BuildingBlocks *url.URL `json:"meshbuildingblocks"`
+ Projects *url.URL `json:"meshprojects"`
+ ProjectUserBindings *url.URL `json:"meshprojectuserbindings"`
+ ProjectGroupBindings *url.URL `json:"meshprojectgroupbindings"`
+ Tenants *url.URL `json:"meshtenants"`
}
type loginResponse struct {
@@ -62,10 +58,11 @@ func NewClient(rootUrl *url.URL, apiKey string, apiSecret string) (*MeshStackPro
// TODO: lookup endpoints
client.endpoints = endpoints{
- BuildingBlocks: rootUrl.JoinPath(apiMeshObjectsRoot, "meshbuildingblocks"),
- Projects: rootUrl.JoinPath(apiMeshObjectsRoot, "meshprojects"),
- ProjectUserBindings: rootUrl.JoinPath(apiMeshObjectsRoot, "meshprojectbindings", "userbindings"),
- Tenants: rootUrl.JoinPath(apiMeshObjectsRoot, "meshtenants"),
+ BuildingBlocks: rootUrl.JoinPath(apiMeshObjectsRoot, "meshbuildingblocks"),
+ Projects: rootUrl.JoinPath(apiMeshObjectsRoot, "meshprojects"),
+ ProjectUserBindings: rootUrl.JoinPath(apiMeshObjectsRoot, "meshprojectbindings", "userbindings"),
+ ProjectGroupBindings: rootUrl.JoinPath(apiMeshObjectsRoot, "meshprojectbindings", "groupbindings"),
+ Tenants: rootUrl.JoinPath(apiMeshObjectsRoot, "meshtenants"),
}
return client, nil
diff --git a/client/project.go b/client/project.go
index f9d97f8..eca75ca 100644
--- a/client/project.go
+++ b/client/project.go
@@ -9,6 +9,8 @@ import (
"net/url"
)
+const CONTENT_TYPE_PROJECT = "application/vnd.meshcloud.api.meshproject.v2.hal+json"
+
type MeshProject struct {
ApiVersion string `json:"apiVersion" tfsdk:"api_version"`
Kind string `json:"kind" tfsdk:"kind"`
diff --git a/client/project_binding.go b/client/project_binding.go
new file mode 100644
index 0000000..f3b4d41
--- /dev/null
+++ b/client/project_binding.go
@@ -0,0 +1,134 @@
+package client
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+)
+
+type MeshProjectBinding struct {
+ ApiVersion string `json:"apiVersion" tfsdk:"api_version"`
+ Kind string `json:"kind" tfsdk:"kind"`
+ Metadata MeshProjectBindingMetadata `json:"metadata" tfsdk:"metadata"`
+ RoleRef MeshProjectRoleRef `json:"roleRef" tfsdk:"role_ref"`
+ TargetRef MeshProjectTargetRef `json:"targetRef" tfsdk:"target_ref"`
+ Subject MeshSubject `json:"subject" tfsdk:"subject"`
+}
+
+type MeshProjectBindingMetadata struct {
+ Name string `json:"name" tfsdk:"name"`
+}
+
+type MeshProjectRoleRef struct {
+ Name string `json:"name" tfsdk:"name"`
+}
+
+type MeshProjectTargetRef struct {
+ Name string `json:"name" tfsdk:"name"`
+ OwnedByWorkspace string `json:"ownedByWorkspace" tfsdk:"owned_by_workspace"`
+}
+
+type MeshSubject struct {
+ Name string `json:"name" tfsdk:"name"`
+}
+
+func (c *MeshStackProviderClient) readProjectBinding(name string, contentType string) (*MeshProjectBinding, error) {
+ var targetUrl *url.URL
+ switch contentType {
+ case CONTENT_TYPE_PROJECT_USER_BINDING:
+ targetUrl = c.urlForPojectUserBinding(name)
+
+ case CONTENT_TYPE_PROJECT_GROUP_BINDING:
+ targetUrl = c.urlForPojectGroupBinding(name)
+
+ default:
+ return nil, fmt.Errorf("Unexpected content type: %s", contentType)
+ }
+
+ req, err := http.NewRequest("GET", targetUrl.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Accept", contentType)
+
+ 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 res.StatusCode != 200 {
+ return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data)
+ }
+
+ var binding MeshProjectBinding
+ err = json.Unmarshal(data, &binding)
+ if err != nil {
+ return nil, err
+ }
+
+ return &binding, nil
+}
+
+func (c *MeshStackProviderClient) createProjectBinding(binding *MeshProjectBinding, contentType string) (*MeshProjectBinding, error) {
+ var targetUrl *url.URL
+ switch contentType {
+ case CONTENT_TYPE_PROJECT_USER_BINDING:
+ targetUrl = c.endpoints.ProjectUserBindings
+
+ case CONTENT_TYPE_PROJECT_GROUP_BINDING:
+ targetUrl = c.endpoints.ProjectGroupBindings
+
+ default:
+ return nil, fmt.Errorf("Unexpected content type: %s", contentType)
+ }
+
+ payload, err := json.Marshal(binding)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("POST", targetUrl.String(), bytes.NewBuffer(payload))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Content-Type", CONTENT_TYPE_PROJECT_GROUP_BINDING)
+ req.Header.Set("Accept", CONTENT_TYPE_PROJECT_GROUP_BINDING)
+
+ 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 != 200 {
+ return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data)
+ }
+
+ var createdBinding MeshProjectBinding
+ err = json.Unmarshal(data, &createdBinding)
+ if err != nil {
+ return nil, err
+ }
+
+ return &createdBinding, nil
+}
diff --git a/client/project_group_binding.go b/client/project_group_binding.go
new file mode 100644
index 0000000..8b66818
--- /dev/null
+++ b/client/project_group_binding.go
@@ -0,0 +1,26 @@
+package client
+
+import (
+ "net/url"
+)
+
+const CONTENT_TYPE_PROJECT_GROUP_BINDING = "application/vnd.meshcloud.api.meshprojectgroupbinding.v3.hal+json"
+
+type MeshProjectGroupBinding = MeshProjectBinding
+
+func (c *MeshStackProviderClient) urlForPojectGroupBinding(name string) *url.URL {
+ return c.endpoints.ProjectGroupBindings.JoinPath(name)
+}
+
+func (c *MeshStackProviderClient) ReadProjectGroupBinding(name string) (*MeshProjectGroupBinding, error) {
+ return c.readProjectBinding(name, CONTENT_TYPE_PROJECT_GROUP_BINDING)
+}
+
+func (c *MeshStackProviderClient) CreateProjectGroupBinding(binding *MeshProjectGroupBinding) (*MeshProjectGroupBinding, error) {
+ return c.createProjectBinding(binding, CONTENT_TYPE_PROJECT_GROUP_BINDING)
+}
+
+func (c *MeshStackProviderClient) DeleteProjecGroupBinding(name string) error {
+ targetUrl := c.urlForPojectGroupBinding(name)
+ return c.deleteMeshObject(*targetUrl, 204)
+}
diff --git a/client/project_user_binding.go b/client/project_user_binding.go
index 0c6f601..3de8e22 100644
--- a/client/project_user_binding.go
+++ b/client/project_user_binding.go
@@ -1,117 +1,23 @@
package client
import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
"net/url"
)
-type MeshProjectUserBinding struct {
- ApiVersion string `json:"apiVersion" tfsdk:"api_version"`
- Kind string `json:"kind" tfsdk:"kind"`
- Metadata MeshProjectUserBindingMetadata `json:"metadata" tfsdk:"metadata"`
- RoleRef MeshProjectRoleRef `json:"roleRef" tfsdk:"role_ref"`
- TargetRef MeshProjectTargetRef `json:"targetRef" tfsdk:"target_ref"`
- Subject MeshSubject `json:"subject" tfsdk:"subject"`
-}
-
-type MeshProjectUserBindingMetadata struct {
- Name string `json:"name" tfsdk:"name"`
-}
-
-type MeshProjectRoleRef struct {
- Name string `json:"name" tfsdk:"name"`
-}
-
-type MeshProjectTargetRef struct {
- Name string `json:"name" tfsdk:"name"`
- OwnedByWorkspace string `json:"ownedByWorkspace" tfsdk:"owned_by_workspace"`
-}
+const CONTENT_TYPE_PROJECT_USER_BINDING = "application/vnd.meshcloud.api.meshprojectuserbinding.v3.hal+json"
-type MeshSubject struct {
- Name string `json:"name" tfsdk:"name"`
-}
+type MeshProjectUserBinding = MeshProjectBinding
func (c *MeshStackProviderClient) urlForPojectUserBinding(name string) *url.URL {
return c.endpoints.ProjectUserBindings.JoinPath(name)
}
func (c *MeshStackProviderClient) ReadProjectUserBinding(name string) (*MeshProjectUserBinding, error) {
- targetUrl := c.urlForPojectUserBinding(name)
- req, err := http.NewRequest("GET", targetUrl.String(), nil)
- if err != nil {
- return nil, err
- }
- req.Header.Set("Accept", CONTENT_TYPE_PROJECT_USER_BINDING)
-
- 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 res.StatusCode != 200 {
- return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data)
- }
-
- var binding MeshProjectUserBinding
- err = json.Unmarshal(data, &binding)
- if err != nil {
- return nil, err
- }
-
- return &binding, nil
+ return c.readProjectBinding(name, CONTENT_TYPE_PROJECT_USER_BINDING)
}
func (c *MeshStackProviderClient) CreateProjectUserBinding(binding *MeshProjectUserBinding) (*MeshProjectUserBinding, error) {
- payload, err := json.Marshal(binding)
- if err != nil {
- return nil, err
- }
-
- req, err := http.NewRequest("POST", c.endpoints.ProjectUserBindings.String(), bytes.NewBuffer(payload))
- if err != nil {
- return nil, err
- }
- req.Header.Set("Content-Type", CONTENT_TYPE_PROJECT_USER_BINDING)
- req.Header.Set("Accept", CONTENT_TYPE_PROJECT_USER_BINDING)
-
- 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 != 200 {
- return nil, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, data)
- }
-
- var createdBinding MeshProjectUserBinding
- err = json.Unmarshal(data, &createdBinding)
- if err != nil {
- return nil, err
- }
-
- return &createdBinding, nil
+ return c.createProjectBinding(binding, CONTENT_TYPE_PROJECT_USER_BINDING)
}
func (c *MeshStackProviderClient) DeleteProjecUserBinding(name string) error {
diff --git a/client/tenant.go b/client/tenant.go
index b8026aa..b83c5e6 100644
--- a/client/tenant.go
+++ b/client/tenant.go
@@ -9,6 +9,8 @@ import (
"net/url"
)
+const CONTENT_TYPE_TENANT = "application/vnd.meshcloud.api.meshtenant.v3.hal+json"
+
type MeshTenant struct {
ApiVersion string `json:"apiVersion" tfsdk:"api_version"`
Kind string `json:"kind" tfsdk:"kind"`
diff --git a/docs/data-sources/project_group_binding.md b/docs/data-sources/project_group_binding.md
new file mode 100644
index 0000000..ceee7c9
--- /dev/null
+++ b/docs/data-sources/project_group_binding.md
@@ -0,0 +1,68 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "meshstack_project_group_binding Data Source - terraform-provider-meshstack"
+subcategory: ""
+description: |-
+ Single project group binding by name.
+---
+
+# meshstack_project_group_binding (Data Source)
+
+Single project group binding by name.
+
+## Example Usage
+
+```terraform
+data "meshstack_project_group_binding" "example" {
+ metadata = {
+ name = "my-project-group-binding"
+ }
+}
+```
+
+
+## Schema
+
+### Required
+
+- `metadata` (Attributes) Project role assigned by this binding. (see [below for nested schema](#nestedatt--metadata))
+
+### Read-Only
+
+- `api_version` (String) Project group binding datatype version
+- `kind` (String) meshObject type, always `meshProjectGroupBinding`.
+- `role_ref` (Attributes) Project role assigned by this binding. (see [below for nested schema](#nestedatt--role_ref))
+- `subject` (Attributes) Group assigned by this binding. (see [below for nested schema](#nestedatt--subject))
+- `target_ref` (Attributes) Project, identified by workspace and project identifier. (see [below for nested schema](#nestedatt--target_ref))
+
+
+### Nested Schema for `metadata`
+
+Required:
+
+- `name` (String)
+
+
+
+### Nested Schema for `role_ref`
+
+Read-Only:
+
+- `name` (String)
+
+
+
+### Nested Schema for `subject`
+
+Read-Only:
+
+- `name` (String) Groupname.
+
+
+
+### Nested Schema for `target_ref`
+
+Read-Only:
+
+- `name` (String)
+- `owned_by_workspace` (String)
diff --git a/docs/data-sources/project_user_binding.md b/docs/data-sources/project_user_binding.md
index bdfc64e..03c6f2f 100644
--- a/docs/data-sources/project_user_binding.md
+++ b/docs/data-sources/project_user_binding.md
@@ -15,7 +15,7 @@ Single project user binding by name.
```terraform
data "meshstack_project_user_binding" "example" {
metadata = {
- name = "214cb14d-2e21-11ef-8e80-0242ac130003"
+ name = "my-project-user-binding"
}
}
```
@@ -32,7 +32,7 @@ data "meshstack_project_user_binding" "example" {
- `api_version` (String) Project user binding datatype version
- `kind` (String) meshObject type, always `meshProjectUserBinding`.
- `role_ref` (Attributes) Project role assigned by this binding. (see [below for nested schema](#nestedatt--role_ref))
-- `subject` (Attributes) Users assigned by this binding. (see [below for nested schema](#nestedatt--subject))
+- `subject` (Attributes) User assigned by this binding. (see [below for nested schema](#nestedatt--subject))
- `target_ref` (Attributes) Project, identified by workspace and project identifier. (see [below for nested schema](#nestedatt--target_ref))
diff --git a/docs/resources/project_group_binding.md b/docs/resources/project_group_binding.md
new file mode 100644
index 0000000..0dde0ba
--- /dev/null
+++ b/docs/resources/project_group_binding.md
@@ -0,0 +1,90 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "meshstack_project_group_binding Resource - terraform-provider-meshstack"
+subcategory: ""
+description: |-
+ Project group binding assigns a group with a specific role to a project.
+---
+
+# meshstack_project_group_binding (Resource)
+
+Project group binding assigns a group with a specific role to a project.
+
+## Example Usage
+
+```terraform
+resource "meshstack_project_group_binding" "example" {
+ metadata = {
+ name = "this-is-an-example"
+ }
+
+ role_ref = {
+ name = "Project Reader"
+ }
+
+ target_ref = {
+ owned_by_workspace = "my-customer"
+ name = "my-project"
+ }
+
+ subject = {
+ name = "my-user-group"
+ }
+}
+```
+
+
+## Schema
+
+### Required
+
+- `metadata` (Attributes) Project group binding metadata. (see [below for nested schema](#nestedatt--metadata))
+- `role_ref` (Attributes) Selects the role to use for this project binding. (see [below for nested schema](#nestedatt--role_ref))
+- `subject` (Attributes) Selects the group for this binding. (see [below for nested schema](#nestedatt--subject))
+- `target_ref` (Attributes) Selects the project to which this binding applies. (see [below for nested schema](#nestedatt--target_ref))
+
+### Read-Only
+
+- `api_version` (String) Project group binding datatype version
+- `kind` (String) meshObject type, always `meshProjectGroupBinding`.
+
+
+### Nested Schema for `metadata`
+
+Required:
+
+- `name` (String) The name identifies the binding and must be unique across the meshStack.
+
+
+
+### Nested Schema for `role_ref`
+
+Required:
+
+- `name` (String)
+
+
+
+### Nested Schema for `subject`
+
+Required:
+
+- `name` (String) Groupname.
+
+
+
+### Nested Schema for `target_ref`
+
+Required:
+
+- `name` (String) Project identifier.
+- `owned_by_workspace` (String) Identifier of workspace containing the target project.
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+# import via project binding name
+terraform import 'meshstack_project_user_binding.example' 'my-binding-name'
+```
diff --git a/examples/data-sources/meshstack_project_group_binding/data-source.tf b/examples/data-sources/meshstack_project_group_binding/data-source.tf
new file mode 100644
index 0000000..3c36fed
--- /dev/null
+++ b/examples/data-sources/meshstack_project_group_binding/data-source.tf
@@ -0,0 +1,5 @@
+data "meshstack_project_group_binding" "example" {
+ metadata = {
+ name = "my-project-group-binding"
+ }
+}
diff --git a/examples/data-sources/meshstack_project_user_binding/data-source.tf b/examples/data-sources/meshstack_project_user_binding/data-source.tf
index c002e76..b048066 100644
--- a/examples/data-sources/meshstack_project_user_binding/data-source.tf
+++ b/examples/data-sources/meshstack_project_user_binding/data-source.tf
@@ -1,5 +1,5 @@
data "meshstack_project_user_binding" "example" {
metadata = {
- name = "214cb14d-2e21-11ef-8e80-0242ac130003"
+ name = "my-project-user-binding"
}
}
diff --git a/examples/resources/meshstack_project_group_binding/import.sh b/examples/resources/meshstack_project_group_binding/import.sh
new file mode 100644
index 0000000..98debba
--- /dev/null
+++ b/examples/resources/meshstack_project_group_binding/import.sh
@@ -0,0 +1,2 @@
+# import via project binding name
+terraform import 'meshstack_project_user_binding.example' 'my-binding-name'
diff --git a/examples/resources/meshstack_project_group_binding/resource.tf b/examples/resources/meshstack_project_group_binding/resource.tf
new file mode 100644
index 0000000..16f1d5d
--- /dev/null
+++ b/examples/resources/meshstack_project_group_binding/resource.tf
@@ -0,0 +1,18 @@
+resource "meshstack_project_group_binding" "example" {
+ metadata = {
+ name = "this-is-an-example"
+ }
+
+ role_ref = {
+ name = "Project Reader"
+ }
+
+ target_ref = {
+ owned_by_workspace = "my-customer"
+ name = "my-project"
+ }
+
+ subject = {
+ name = "my-user-group"
+ }
+}
diff --git a/internal/provider/project_group_binding_data_source.go b/internal/provider/project_group_binding_data_source.go
new file mode 100644
index 0000000..10b534a
--- /dev/null
+++ b/internal/provider/project_group_binding_data_source.go
@@ -0,0 +1,133 @@
+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"
+)
+
+var (
+ _ datasource.DataSource = &projectGroupBindingDataSource{}
+ _ datasource.DataSourceWithConfigure = &projectGroupBindingDataSource{}
+)
+
+func NewProjectGroupBindingDataSource() datasource.DataSource {
+ return &projectGroupBindingDataSource{}
+}
+
+type projectGroupBindingDataSource struct {
+ client *client.MeshStackProviderClient
+}
+
+func (d *projectGroupBindingDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_project_group_binding"
+
+}
+
+func (d *projectGroupBindingDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ MarkdownDescription: "Single project group binding by name.",
+
+ Attributes: map[string]schema.Attribute{
+ "api_version": schema.StringAttribute{
+ MarkdownDescription: "Project group binding datatype version",
+ Computed: true,
+ },
+
+ "kind": schema.StringAttribute{
+ MarkdownDescription: "meshObject type, always `meshProjectGroupBinding`.",
+ Computed: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf([]string{"meshProjectGroupBinding"}...),
+ },
+ },
+
+ "metadata": schema.SingleNestedAttribute{
+ MarkdownDescription: "Project role assigned by this binding.",
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.LengthBetween(1, 45),
+ },
+ },
+ },
+ },
+
+ "role_ref": schema.SingleNestedAttribute{
+ MarkdownDescription: "Project role assigned by this binding.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{Computed: true},
+ },
+ },
+
+ "target_ref": schema.SingleNestedAttribute{
+ MarkdownDescription: "Project, identified by workspace and project identifier.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+
+ "name": schema.StringAttribute{Computed: true},
+ "owned_by_workspace": schema.StringAttribute{Computed: true},
+ },
+ },
+ "subject": schema.SingleNestedAttribute{
+ MarkdownDescription: "Group assigned by this binding.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{
+ MarkdownDescription: "Groupname.",
+ Computed: true,
+ },
+ },
+ },
+ },
+ }
+}
+
+func (d *projectGroupBindingDataSource) 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 *projectGroupBindingDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var name string
+ resp.Diagnostics.Append(req.Config.GetAttribute(ctx, path.Root("metadata").AtName("name"), &name)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ binding, err := d.client.ReadProjectGroupBinding(name)
+ if err != nil {
+ resp.Diagnostics.AddError("Unable to read project group binding", err.Error())
+ }
+
+ if binding == nil {
+ resp.Diagnostics.AddError("Project group binding not found", fmt.Sprintf("Can't find project group binding with name '%s'.", name))
+ return
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, binding)...)
+}
diff --git a/internal/provider/project_group_binding_resource.go b/internal/provider/project_group_binding_resource.go
new file mode 100644
index 0000000..aa683a3
--- /dev/null
+++ b/internal/provider/project_group_binding_resource.go
@@ -0,0 +1,211 @@
+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/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/schema/validator"
+)
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ resource.Resource = &projectGroupBindingResource{}
+ _ resource.ResourceWithConfigure = &projectGroupBindingResource{}
+ _ resource.ResourceWithImportState = &projectGroupBindingResource{}
+)
+
+// NewProjectGroupBindingResource is a helper function to simplify the provider implementation.
+func NewProjectGroupBindingResource() resource.Resource {
+ return &projectGroupBindingResource{}
+}
+
+// projectGroupBindingResource is the resource implementation.
+type projectGroupBindingResource struct {
+ client *client.MeshStackProviderClient
+}
+
+// Metadata returns the resource type name.
+func (r *projectGroupBindingResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_project_group_binding"
+}
+
+// Configure adds the provider configured client to the resource.
+func (r *projectGroupBindingResource) 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
+}
+
+// Schema defines the schema for the resource.
+func (r *projectGroupBindingResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ MarkdownDescription: "Project group binding assigns a group with a specific role to a project.",
+
+ Attributes: map[string]schema.Attribute{
+ "api_version": schema.StringAttribute{
+ MarkdownDescription: "Project group binding datatype version",
+ Computed: true,
+ Default: stringdefault.StaticString("v3"),
+ PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
+ },
+
+ "kind": schema.StringAttribute{
+ MarkdownDescription: "meshObject type, always `meshProjectGroupBinding`.",
+ Computed: true,
+ Default: stringdefault.StaticString("meshProjectGroupBinding"),
+ Validators: []validator.String{
+ stringvalidator.OneOf([]string{"meshProjectGroupBinding"}...),
+ },
+ PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
+ },
+
+ "metadata": schema.SingleNestedAttribute{
+ Required: true,
+ MarkdownDescription: "Project group binding metadata.",
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{
+ MarkdownDescription: "The name identifies the binding and must be unique across the meshStack.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
+ Validators: []validator.String{
+ stringvalidator.LengthBetween(1, 45),
+ },
+ },
+ },
+ },
+
+ "role_ref": schema.SingleNestedAttribute{
+ MarkdownDescription: "Selects the role to use for this project binding.",
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{
+ Required: true,
+ PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
+ },
+ },
+ },
+
+ "target_ref": schema.SingleNestedAttribute{
+ MarkdownDescription: "Selects the project to which this binding applies.",
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{
+ MarkdownDescription: "Project identifier.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
+ },
+ "owned_by_workspace": schema.StringAttribute{
+ MarkdownDescription: "Identifier of workspace containing the target project.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
+ },
+ },
+ },
+
+ "subject": schema.SingleNestedAttribute{
+ MarkdownDescription: "Selects the group for this binding.",
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{
+ MarkdownDescription: "Groupname.",
+ Required: true,
+ PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
+ },
+ },
+ },
+ },
+ }
+}
+
+// Create creates the resource and sets the initial Terraform state.
+func (r *projectGroupBindingResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+ var plan client.MeshProjectGroupBinding
+
+ diags := req.Plan.Get(ctx, &plan)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ binding, err := r.client.CreateProjectGroupBinding(&plan)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error creating project group binding",
+ "Could not create project group binding, unexpected error: "+err.Error(),
+ )
+ return
+ }
+
+ diags = resp.State.Set(ctx, binding)
+ resp.Diagnostics.Append(diags...)
+}
+
+// Read refreshes the Terraform state with the latest data.
+func (r *projectGroupBindingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+ var name string
+ resp.Diagnostics.Append(req.State.GetAttribute(ctx, path.Root("metadata").AtName("name"), &name)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ binding, err := r.client.ReadProjectGroupBinding(name)
+ if err != nil {
+ resp.Diagnostics.AddError("Unable to read project group binding", err.Error())
+ }
+
+ if binding == nil {
+ resp.State.RemoveResource(ctx)
+ return
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, binding)...)
+}
+
+// Update updates the resource and sets the updated Terraform state on success.
+func (r *projectGroupBindingResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+ resp.Diagnostics.AddError("Project group bindings can't be updated", "Unsupported operation: project group bindings can't be updated.")
+}
+
+// Delete deletes the resource and removes the Terraform state on success.
+func (r *projectGroupBindingResource) 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.DeleteProjecGroupBinding(name)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error deleting project group binding",
+ "Could not delete project group binding, unexpected error: "+err.Error(),
+ )
+ return
+ }
+}
+
+func (r *projectGroupBindingResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("metadata").AtName("name"), req.ID)...)
+}
diff --git a/internal/provider/project_user_binding_data_source.go b/internal/provider/project_user_binding_data_source.go
index 2afa059..321603b 100644
--- a/internal/provider/project_user_binding_data_source.go
+++ b/internal/provider/project_user_binding_data_source.go
@@ -14,24 +14,24 @@ import (
)
var (
- _ datasource.DataSource = &projectUserBindingsDataSource{}
- _ datasource.DataSourceWithConfigure = &projectUserBindingsDataSource{}
+ _ datasource.DataSource = &projectUserBindingDataSource{}
+ _ datasource.DataSourceWithConfigure = &projectUserBindingDataSource{}
)
-func NewProjectUserBindingsDataSource() datasource.DataSource {
- return &projectUserBindingsDataSource{}
+func NewProjectUserBindingDataSource() datasource.DataSource {
+ return &projectUserBindingDataSource{}
}
-type projectUserBindingsDataSource struct {
+type projectUserBindingDataSource struct {
client *client.MeshStackProviderClient
}
-func (d *projectUserBindingsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+func (d *projectUserBindingDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_project_user_binding"
}
-func (d *projectUserBindingsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+func (d *projectUserBindingDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "Single project user binding by name.",
@@ -80,7 +80,7 @@ func (d *projectUserBindingsDataSource) Schema(ctx context.Context, req datasour
},
},
"subject": schema.SingleNestedAttribute{
- MarkdownDescription: "Users assigned by this binding.",
+ MarkdownDescription: "User assigned by this binding.",
Computed: true,
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
@@ -93,7 +93,7 @@ func (d *projectUserBindingsDataSource) Schema(ctx context.Context, req datasour
}
}
-func (d *projectUserBindingsDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+func (d *projectUserBindingDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
@@ -112,8 +112,7 @@ func (d *projectUserBindingsDataSource) Configure(ctx context.Context, req datas
d.client = client
}
-func (d *projectUserBindingsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
- // get workspace and project to query for bindings
+func (d *projectUserBindingDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var name string
resp.Diagnostics.Append(req.Config.GetAttribute(ctx, path.Root("metadata").AtName("name"), &name)...)
if resp.Diagnostics.HasError() {
@@ -122,7 +121,7 @@ func (d *projectUserBindingsDataSource) Read(ctx context.Context, req datasource
binding, err := d.client.ReadProjectUserBinding(name)
if err != nil {
- resp.Diagnostics.AddError("Unable to read project", err.Error())
+ resp.Diagnostics.AddError("Unable to read project user binding", err.Error())
}
if binding == nil {
diff --git a/internal/provider/project_user_binding_resource.go b/internal/provider/project_user_binding_resource.go
index a79d37e..be4aa9e 100644
--- a/internal/provider/project_user_binding_resource.go
+++ b/internal/provider/project_user_binding_resource.go
@@ -199,7 +199,7 @@ func (r *projectUserBindingResource) Delete(ctx context.Context, req resource.De
err := r.client.DeleteProjecUserBinding(name)
if err != nil {
resp.Diagnostics.AddError(
- "Error deleting project",
+ "Error deleting project user binding",
"Could not delete project, unexpected error: "+err.Error(),
)
return
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index 4d48261..00715f4 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -79,6 +79,7 @@ func (p *MeshStackProvider) Resources(ctx context.Context) []func() resource.Res
NewProjectResource,
NewTenantResource,
NewProjectUserBindingResource,
+ NewProjectGroupBindingResource,
}
}
@@ -87,7 +88,8 @@ func (p *MeshStackProvider) DataSources(ctx context.Context) []func() datasource
NewBuildingBlockDataSource,
NewProjectDataSource,
NewProjectsDataSource,
- NewProjectUserBindingsDataSource,
+ NewProjectUserBindingDataSource,
+ NewProjectGroupBindingDataSource,
NewTenantDataSource,
}
}