diff --git a/GNUmakefile b/GNUmakefile index a51e850..d06fd26 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -9,7 +9,6 @@ WARN_COLOR=\033[33;01m generate-framework: @printf "$(OK_COLOR)==> Generate provider schema$(NO_COLOR)\n" tfplugingen-framework generate provider --input ./internal/schema/provider_windows.json --output ./internal/generate - @printf "$(OK_COLOR)==> Generate local schema$(NO_COLOR)\n" tfplugingen-framework generate data-sources --input ./internal/schema/local_datasources.json --output ./internal/generate tfplugingen-framework generate resources --input ./internal/schema/local_resources.json --output ./internal/generate diff --git a/docs/data-sources/local_users.md b/docs/data-sources/local_users.md index 412d2e3..a619fb2 100644 --- a/docs/data-sources/local_users.md +++ b/docs/data-sources/local_users.md @@ -8,6 +8,12 @@ description: |- Retrieve a list of all local users. + +## Example Usage + +```terraform +data "windows_local_users" "all" {} +``` ## Schema diff --git a/docs/resources/local_group_member.md b/docs/resources/local_group_member.md new file mode 100644 index 0000000..89f792b --- /dev/null +++ b/docs/resources/local_group_member.md @@ -0,0 +1,39 @@ +--- +page_title: "windows_local_group_member Resource - terraform-provider-windows" +subcategory: "Local" +description: |- + Manage group member for local security groups +--- +# windows_local_group_member (Resource) + + +Manage group member for local security groups. + +## Example Usage + +```terraform +resource "windows_local_user" "this" { + name = "test-user" +} + +resource "windows_local_group" "this" { + name = "test-group" +} + +resource "windows_local_group_member" "this" { + group_id = windows_local_group.this.id + member_id = windows_local_user.this.id +} +``` + + +## Schema + +### Required + +- `group_id` (String) The ID of the local security group you want to add the member to. Changing this forces a new resource to be created. +- `member_id` (String) The ID of the principal you want to add as a member to the group. Supported object types are local users or groups. Changing this forces a new resource to be created. + +### Read-Only + +- `id` (String) The ID of this resource. \ No newline at end of file diff --git a/examples/data-sources/windows_local_users/data-source.tf b/examples/data-sources/windows_local_users/data-source.tf new file mode 100644 index 0000000..2481536 --- /dev/null +++ b/examples/data-sources/windows_local_users/data-source.tf @@ -0,0 +1 @@ +data "windows_local_users" "all" {} \ No newline at end of file diff --git a/examples/resources/windows_local_group_member/resource.tf b/examples/resources/windows_local_group_member/resource.tf new file mode 100644 index 0000000..45aedcd --- /dev/null +++ b/examples/resources/windows_local_group_member/resource.tf @@ -0,0 +1,12 @@ +resource "windows_local_user" "this" { + name = "test-user" +} + +resource "windows_local_group" "this" { + name = "test-group" +} + +resource "windows_local_group_member" "this" { + group_id = windows_local_group.this.id + member_id = windows_local_user.this.id +} \ No newline at end of file diff --git a/go.mod b/go.mod index 643e152..21c24d2 100644 --- a/go.mod +++ b/go.mod @@ -75,11 +75,11 @@ require ( github.com/yuin/goldmark v1.6.0 // indirect github.com/yuin/goldmark-meta v1.1.0 // indirect github.com/zclconf/go-cty v1.14.1 // indirect - golang.org/x/crypto v0.17.0 // indirect + golang.org/x/crypto v0.19.0 // indirect golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.18.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect google.golang.org/grpc v1.60.0 // indirect diff --git a/go.sum b/go.sum index d3762bd..058b521 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,8 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 h1:EDuYyU/MkFXllv9QF9819VlI9a4tzGuCbhG0ExK9o1U= golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -260,15 +260,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= diff --git a/internal/generate/resource_local_group_member/local_group_member_resource_gen.go b/internal/generate/resource_local_group_member/local_group_member_resource_gen.go new file mode 100644 index 0000000..59cc524 --- /dev/null +++ b/internal/generate/resource_local_group_member/local_group_member_resource_gen.go @@ -0,0 +1,49 @@ +// Code generated by terraform-plugin-framework-generator DO NOT EDIT. + +package resource_local_group_member + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema" +) + +func LocalGroupMemberResourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "group_id": schema.StringAttribute{ + Required: true, + Description: "The ID of the local security group you want to add the member to. Changing this forces a new resource to be created.", + MarkdownDescription: "The ID of the local security group you want to add the member to. Changing this forces a new resource to be created.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "id": schema.StringAttribute{ + Computed: true, + Description: "The ID of this resource.", + MarkdownDescription: "The ID of this resource.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "member_id": schema.StringAttribute{ + Required: true, + Description: "The ID of the principal you want to add as a member to the group. Supported object types are local users or groups. Changing this forces a new resource to be created.", + MarkdownDescription: "The ID of the principal you want to add as a member to the group. Supported object types are local users or groups. Changing this forces a new resource to be created.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + } +} + +type LocalGroupMemberModel struct { + GroupId types.String `tfsdk:"group_id"` + Id types.String `tfsdk:"id"` + MemberId types.String `tfsdk:"member_id"` +} diff --git a/internal/provider/local/local_group_member_resource.go b/internal/provider/local/local_group_member_resource.go new file mode 100644 index 0000000..f53817f --- /dev/null +++ b/internal/provider/local/local_group_member_resource.go @@ -0,0 +1,127 @@ +package local + +import ( + "context" + "fmt" + "terraform-provider-windows/internal/generate/resource_local_group_member" + + "github.com/d-strobel/gowindows" + "github.com/d-strobel/gowindows/windows/local" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.Resource = (*localGroupMemberResource)(nil) + +func NewLocalGroupMemberResource() resource.Resource { + return &localGroupMemberResource{} +} + +type localGroupMemberResource struct { + client *gowindows.Client +} + +func (r *localGroupMemberResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_local_group_member" +} + +func (r *localGroupMemberResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = resource_local_group_member.LocalGroupMemberResourceSchema(ctx) + resp.Schema.Description = `Manage group member for local security groups.` +} + +func (r *localGroupMemberResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*gowindows.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *gowindows.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + r.client = client +} + +func (r *localGroupMemberResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data resource_local_group_member.LocalGroupMemberModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create API call logic + params := local.GroupMemberCreateParams{ + SID: data.GroupId.ValueString(), + Member: data.MemberId.ValueString(), + } + + if err := r.client.Local.GroupMemberCreate(ctx, params); err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create local group member, got error: %s", err)) + return + } + + // Create the ID for the resource + data.Id = types.StringValue(fmt.Sprintf("%s/member/%s", data.GroupId.ValueString(), data.MemberId.ValueString())) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *localGroupMemberResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data resource_local_group_member.LocalGroupMemberModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Read API call logic + params := local.GroupMemberReadParams{ + SID: data.GroupId.ValueString(), + Member: data.MemberId.ValueString(), + } + + if _, err := r.client.Local.GroupMemberRead(ctx, params); err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete local group member, got error: %s", err)) + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *localGroupMemberResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Update is not needed in this resource +} + +func (r *localGroupMemberResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data resource_local_group_member.LocalGroupMemberModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Delete API call logic + params := local.GroupMemberDeleteParams{ + SID: data.GroupId.ValueString(), + Member: data.MemberId.ValueString(), + } + + if err := r.client.Local.GroupMemberDelete(ctx, params); err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete local group member, got error: %s", err)) + return + } +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 2cbb642..7e814cc 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -255,6 +255,7 @@ func (p *WindowsProvider) Resources(ctx context.Context) []func() resource.Resou return []func() resource.Resource{ local.NewLocalGroupResource, local.NewLocalUserResource, + local.NewLocalGroupMemberResource, } } diff --git a/internal/schema/local_resources.json b/internal/schema/local_resources.json index b937075..52d43ad 100644 --- a/internal/schema/local_resources.json +++ b/internal/schema/local_resources.json @@ -352,6 +352,70 @@ } ] } + }, + { + "name": "local_group_member", + "schema": { + "attributes": [ + { + "name": "group_id", + "string": { + "computed_optional_required": "required", + "description": "The ID of the local security group you want to add the member to. Changing this forces a new resource to be created.", + "plan_modifiers": [ + { + "custom": { + "imports": [ + { + "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + } + ], + "schema_definition": "stringplanmodifier.RequiresReplace()" + } + } + ] + } + }, + { + "name": "member_id", + "string": { + "computed_optional_required": "required", + "description": "The ID of the principal you want to add as a member to the group. Supported object types are local users or groups. Changing this forces a new resource to be created.", + "plan_modifiers": [ + { + "custom": { + "imports": [ + { + "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + } + ], + "schema_definition": "stringplanmodifier.RequiresReplace()" + } + } + ] + } + }, + { + "name": "id", + "string": { + "computed_optional_required": "computed", + "description": "The ID of this resource.", + "plan_modifiers": [ + { + "custom": { + "imports": [ + { + "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + } + ], + "schema_definition": "stringplanmodifier.UseStateForUnknown()" + } + } + ] + } + } + ] + } } ] } \ No newline at end of file