mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-30 10:33:46 +00:00
271 lines
7.4 KiB
Go
271 lines
7.4 KiB
Go
/*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
package access
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator"
|
|
"github.com/hashicorp/terraform-plugin-framework/path"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
|
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
|
|
|
"github.com/bpg/terraform-provider-proxmox/fwprovider/structure"
|
|
"github.com/bpg/terraform-provider-proxmox/proxmox"
|
|
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
|
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
|
)
|
|
|
|
var (
|
|
_ resource.Resource = (*aclResource)(nil)
|
|
_ resource.ResourceWithConfigure = (*aclResource)(nil)
|
|
_ resource.ResourceWithImportState = (*aclResource)(nil)
|
|
_ resource.ResourceWithConfigValidators = (*aclResource)(nil)
|
|
)
|
|
|
|
type aclResource struct {
|
|
client proxmox.Client
|
|
}
|
|
|
|
// NewACLResource creates a new ACL resource.
|
|
func NewACLResource() resource.Resource {
|
|
return &aclResource{}
|
|
}
|
|
|
|
func (r *aclResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
|
resp.Schema = schema.Schema{
|
|
Description: "Manages ACLs on the Proxmox cluster",
|
|
MarkdownDescription: "Manages ACLs on the Proxmox cluster.\n\n" +
|
|
"ACLs are used to control access to resources in the Proxmox cluster.\n" +
|
|
"Each ACL consists of a path, a user, group or token, a role, and a flag to allow propagation of permissions.",
|
|
Attributes: map[string]schema.Attribute{
|
|
"group_id": schema.StringAttribute{
|
|
Description: "The group the ACL should apply to (mutually exclusive with `token_id` and `user_id`)",
|
|
Optional: true,
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.RequiresReplace(),
|
|
},
|
|
},
|
|
"id": structure.IDAttribute(),
|
|
"path": schema.StringAttribute{
|
|
Description: "Access control path",
|
|
Required: true,
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.RequiresReplace(),
|
|
},
|
|
},
|
|
"propagate": schema.BoolAttribute{
|
|
Description: "Allow to propagate (inherit) permissions.",
|
|
Optional: true,
|
|
Computed: true,
|
|
Default: booldefault.StaticBool(true),
|
|
},
|
|
"role_id": schema.StringAttribute{
|
|
Description: "The role to apply",
|
|
Required: true,
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.RequiresReplace(),
|
|
},
|
|
},
|
|
"token_id": schema.StringAttribute{
|
|
Description: "The token the ACL should apply to (mutually exclusive with `group_id` and `user_id`)",
|
|
Optional: true,
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.RequiresReplace(),
|
|
},
|
|
},
|
|
"user_id": schema.StringAttribute{
|
|
Description: "The user the ACL should apply to (mutually exclusive with `group_id` and `token_id`)",
|
|
Optional: true,
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.RequiresReplace(),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (r *aclResource) ConfigValidators(_ context.Context) []resource.ConfigValidator {
|
|
return []resource.ConfigValidator{
|
|
resourcevalidator.Conflicting(
|
|
path.MatchRoot("group_id"),
|
|
path.MatchRoot("token_id"),
|
|
path.MatchRoot("user_id"),
|
|
),
|
|
}
|
|
}
|
|
|
|
func (r *aclResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
|
if req.ProviderData == nil {
|
|
return
|
|
}
|
|
|
|
client, ok := req.ProviderData.(proxmox.Client)
|
|
|
|
if !ok {
|
|
resp.Diagnostics.AddError(
|
|
"Unexpected Resource Configure Type",
|
|
fmt.Sprintf("Expected *proxmox.Client, got: %T. Please report this issue to the provider developers.",
|
|
req.ProviderData),
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
r.client = client
|
|
}
|
|
|
|
func (r *aclResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
|
resp.TypeName = req.ProviderTypeName + "_acl"
|
|
}
|
|
|
|
func (r *aclResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
|
var plan aclResourceModel
|
|
|
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
|
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
body := plan.intoUpdateBody()
|
|
|
|
err := r.client.Access().UpdateACL(ctx, body)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Unable to create ACL", apiCallFailed+err.Error())
|
|
return
|
|
}
|
|
|
|
plan.ID = plan.generateID()
|
|
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
|
|
}
|
|
|
|
func (r *aclResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
|
var state aclResourceModel
|
|
|
|
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
|
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
acls, err := r.client.Access().GetACL(ctx)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Unable read ACL", apiCallFailed+err.Error())
|
|
return
|
|
}
|
|
|
|
for _, acl := range acls {
|
|
switch acl.Type {
|
|
case "group":
|
|
if acl.UserOrGroupID != state.GroupID.ValueString() {
|
|
continue
|
|
}
|
|
case "token":
|
|
if acl.UserOrGroupID != state.TokenID.ValueString() {
|
|
continue
|
|
}
|
|
case "user":
|
|
if acl.UserOrGroupID != state.UserID.ValueString() {
|
|
continue
|
|
}
|
|
default:
|
|
// ignore unknown values
|
|
continue
|
|
}
|
|
|
|
if acl.Path != state.Path {
|
|
continue
|
|
}
|
|
|
|
if acl.RoleID != state.RoleID {
|
|
continue
|
|
}
|
|
|
|
state.Propagate = ptr.Or(acl.Propagate.PointerBool(), true)
|
|
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
|
|
|
|
return
|
|
}
|
|
|
|
resp.State.RemoveResource(ctx)
|
|
}
|
|
|
|
func (r *aclResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
|
var (
|
|
state aclResourceModel
|
|
plan aclResourceModel
|
|
)
|
|
|
|
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
|
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
stateBody := state.intoUpdateBody()
|
|
stateBody.Delete = proxmoxtypes.CustomBool(true).Pointer()
|
|
|
|
err := r.client.Access().UpdateACL(ctx, stateBody)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Unable to delete old ACL", apiCallFailed+err.Error())
|
|
return
|
|
}
|
|
|
|
planBody := plan.intoUpdateBody()
|
|
|
|
err = r.client.Access().UpdateACL(ctx, planBody)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Unable to create ACL", apiCallFailed+err.Error())
|
|
return
|
|
}
|
|
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
|
|
}
|
|
|
|
func (r *aclResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
|
var state aclResourceModel
|
|
|
|
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
|
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
stateBody := state.intoUpdateBody()
|
|
stateBody.Delete = proxmoxtypes.CustomBool(true).Pointer()
|
|
|
|
err := r.client.Access().UpdateACL(ctx, stateBody)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Unable to delete old ACL", apiCallFailed+err.Error())
|
|
return
|
|
}
|
|
}
|
|
|
|
func (r *aclResource) ImportState(
|
|
ctx context.Context,
|
|
req resource.ImportStateRequest,
|
|
resp *resource.ImportStateResponse,
|
|
) {
|
|
model, err := parseACLResourceModelFromID(req.ID)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("Unable to import ACL", "failed to parse ID: "+err.Error())
|
|
return
|
|
}
|
|
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, model)...)
|
|
}
|
|
|
|
const apiCallFailed = "API call failed: "
|