From c5d8a34730c40f751281dc9abce07f72dbf09526 Mon Sep 17 00:00:00 2001 From: Dan Petersen Date: Wed, 11 Dec 2019 00:24:36 +0100 Subject: [PATCH] Initial work on ACL support --- README.md | 16 ++ data_source_virtual_environment_group.go | 74 +++++++- data_source_virtual_environment_user.go | 60 +++++++ .../data_source_virtual_environment_group.tf | 4 + .../data_source_virtual_environment_user.tf | 4 + example/resource_virtual_environment_group.tf | 9 + example/resource_virtual_environment_role.tf | 1 - example/resource_virtual_environment_user.tf | 10 ++ proxmox/virtual_environment_acl.go | 59 +++++++ resource_virtual_environment_group.go | 163 +++++++++++++++++- resource_virtual_environment_role.go | 6 +- resource_virtual_environment_user.go | 151 +++++++++++++++- 12 files changed, 538 insertions(+), 19 deletions(-) create mode 100644 proxmox/virtual_environment_acl.go diff --git a/README.md b/README.md index db9bd0d0..38217564 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,10 @@ If you're building the provider, follow the instructions to [install it as a plu * `group_id` - (Required) The group id ###### Attributes +* `acl` - The access control list + * `path` - The path + * `propagate` - Whether to propagate to child paths + * `role_id` - The role id * `comment` - The group comment * `members` - The group members as a list with `username@realm` entries @@ -101,6 +105,10 @@ This data source doesn't accept arguments. * `user_id` - (Required) The user id. ###### Attributes +* `acl` - The access control list + * `path` - The path + * `propagate` - Whether to propagate to child paths + * `role_id` - The role id * `comment` - The user comment * `email` - The user's email address * `enabled` - Whether the user account is enabled @@ -144,6 +152,10 @@ This data source doesn't accept arguments. ##### Group (proxmox_virtual_environment_group) ###### Arguments +* `acl` - (Optional) The access control list (multiple blocks supported) + * `path` - The path + * `propagate` - Whether to propagate to child paths + * `role_id` - The role id * `comment` - (Optional) The group comment * `group_id` - (Required) The group id @@ -176,6 +188,10 @@ This resource doesn't expose any additional attributes. ##### User (proxmox_virtual_environment_user) ###### Arguments +* `acl` - (Optional) The access control list (multiple blocks supported) + * `path` - The path + * `propagate` - Whether to propagate to child paths + * `role_id` - The role id * `comment` - (Optional) The user comment * `email` - (Optional) The user's email address * `enabled` - (Optional) Whether the user account is enabled diff --git a/data_source_virtual_environment_group.go b/data_source_virtual_environment_group.go index 42268b17..05110b6b 100644 --- a/data_source_virtual_environment_group.go +++ b/data_source_virtual_environment_group.go @@ -9,14 +9,46 @@ import ( ) const ( - mkDataSourceVirtualEnvironmentGroupComment = "comment" - mkDataSourceVirtualEnvironmentGroupID = "group_id" - mkDataSourceVirtualEnvironmentGroupMembers = "members" + mkDataSourceVirtualEnvironmentGroupACL = "acl" + mkDataSourceVirtualEnvironmentGroupACLPath = "path" + mkDataSourceVirtualEnvironmentGroupACLPropagate = "propagate" + mkDataSourceVirtualEnvironmentGroupACLRoleID = "role_id" + mkDataSourceVirtualEnvironmentGroupComment = "comment" + mkDataSourceVirtualEnvironmentGroupID = "group_id" + mkDataSourceVirtualEnvironmentGroupMembers = "members" ) func dataSourceVirtualEnvironmentGroup() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ + mkDataSourceVirtualEnvironmentGroupACL: &schema.Schema{ + Type: schema.TypeSet, + Description: "The access control list", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return make([]interface{}, 0), nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkDataSourceVirtualEnvironmentGroupACLPath: { + Type: schema.TypeString, + Required: true, + Description: "The path", + }, + mkDataSourceVirtualEnvironmentGroupACLPropagate: { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to propagate to child paths", + Default: false, + }, + mkDataSourceVirtualEnvironmentGroupACLRoleID: { + Type: schema.TypeString, + Required: true, + Description: "The role id", + }, + }, + }, + }, mkDataSourceVirtualEnvironmentGroupComment: &schema.Schema{ Type: schema.TypeString, Description: "The group comment", @@ -47,7 +79,13 @@ func dataSourceVirtualEnvironmentGroupRead(d *schema.ResourceData, m interface{} } groupID := d.Get(mkDataSourceVirtualEnvironmentGroupID).(string) - accessGroup, err := veClient.GetGroup(groupID) + group, err := veClient.GetGroup(groupID) + + if err != nil { + return err + } + + acl, err := veClient.GetACL() if err != nil { return err @@ -55,13 +93,35 @@ func dataSourceVirtualEnvironmentGroupRead(d *schema.ResourceData, m interface{} d.SetId(groupID) - if accessGroup.Comment != nil { - d.Set(mkDataSourceVirtualEnvironmentGroupComment, accessGroup.Comment) + aclParsed := make([]interface{}, 0) + + for _, v := range acl { + if v.Type == "group" && v.UserOrGroupID == groupID { + aclEntry := make(map[string]interface{}) + + aclEntry[mkDataSourceVirtualEnvironmentGroupACLPath] = v.Path + + if v.Propagate != nil { + aclEntry[mkDataSourceVirtualEnvironmentGroupACLPropagate] = bool(*v.Propagate) + } else { + aclEntry[mkDataSourceVirtualEnvironmentGroupACLPropagate] = false + } + + aclEntry[mkDataSourceVirtualEnvironmentGroupACLRoleID] = v.RoleID + + aclParsed = append(aclParsed, aclEntry) + } + } + + d.Set(mkDataSourceVirtualEnvironmentGroupACL, aclParsed) + + if group.Comment != nil { + d.Set(mkDataSourceVirtualEnvironmentGroupComment, group.Comment) } else { d.Set(mkDataSourceVirtualEnvironmentGroupComment, "") } - d.Set(mkDataSourceVirtualEnvironmentGroupMembers, accessGroup.Members) + d.Set(mkDataSourceVirtualEnvironmentGroupMembers, group.Members) return nil } diff --git a/data_source_virtual_environment_user.go b/data_source_virtual_environment_user.go index 67a048cc..126c1937 100644 --- a/data_source_virtual_environment_user.go +++ b/data_source_virtual_environment_user.go @@ -11,6 +11,10 @@ import ( ) const ( + mkDataSourceVirtualEnvironmentUserACL = "acl" + mkDataSourceVirtualEnvironmentUserACLPath = "path" + mkDataSourceVirtualEnvironmentUserACLPropagate = "propagate" + mkDataSourceVirtualEnvironmentUserACLRoleID = "role_id" mkDataSourceVirtualEnvironmentUserComment = "comment" mkDataSourceVirtualEnvironmentUserEmail = "email" mkDataSourceVirtualEnvironmentUserEnabled = "enabled" @@ -25,6 +29,34 @@ const ( func dataSourceVirtualEnvironmentUser() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ + mkDataSourceVirtualEnvironmentUserACL: &schema.Schema{ + Type: schema.TypeSet, + Description: "The access control list", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return make([]interface{}, 0), nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkDataSourceVirtualEnvironmentUserACLPath: { + Type: schema.TypeString, + Required: true, + Description: "The path", + }, + mkDataSourceVirtualEnvironmentUserACLPropagate: { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to propagate to child paths", + Default: false, + }, + mkDataSourceVirtualEnvironmentUserACLRoleID: { + Type: schema.TypeString, + Required: true, + Description: "The role id", + }, + }, + }, + }, mkDataSourceVirtualEnvironmentUserComment: &schema.Schema{ Type: schema.TypeString, Description: "The user comment", @@ -91,8 +123,36 @@ func dataSourceVirtualEnvironmentUserRead(d *schema.ResourceData, m interface{}) return err } + acl, err := veClient.GetACL() + + if err != nil { + return err + } + d.SetId(userID) + aclParsed := make([]interface{}, 0) + + for _, v := range acl { + if v.Type == "user" && v.UserOrGroupID == userID { + aclEntry := make(map[string]interface{}) + + aclEntry[mkDataSourceVirtualEnvironmentUserACLPath] = v.Path + + if v.Propagate != nil { + aclEntry[mkDataSourceVirtualEnvironmentUserACLPropagate] = bool(*v.Propagate) + } else { + aclEntry[mkDataSourceVirtualEnvironmentUserACLPropagate] = false + } + + aclEntry[mkDataSourceVirtualEnvironmentUserACLRoleID] = v.RoleID + + aclParsed = append(aclParsed, aclEntry) + } + } + + d.Set(mkDataSourceVirtualEnvironmentUserACL, aclParsed) + if v.Comment != nil { d.Set(mkDataSourceVirtualEnvironmentUserComment, v.Comment) } else { diff --git a/example/data_source_virtual_environment_group.tf b/example/data_source_virtual_environment_group.tf index 344a1438..0aa96c08 100644 --- a/example/data_source_virtual_environment_group.tf +++ b/example/data_source_virtual_environment_group.tf @@ -2,6 +2,10 @@ data "proxmox_virtual_environment_group" "example" { group_id = "${proxmox_virtual_environment_group.example.id}" } +output "data_proxmox_virtual_environment_group_example_acl" { + value = "${data.proxmox_virtual_environment_group.example.acl}" +} + output "data_proxmox_virtual_environment_group_example_comment" { value = "${data.proxmox_virtual_environment_group.example.comment}" } diff --git a/example/data_source_virtual_environment_user.tf b/example/data_source_virtual_environment_user.tf index 0c3e6849..fd916697 100644 --- a/example/data_source_virtual_environment_user.tf +++ b/example/data_source_virtual_environment_user.tf @@ -2,6 +2,10 @@ data "proxmox_virtual_environment_user" "example" { user_id = "${proxmox_virtual_environment_user.example.id}" } +output "data_proxmox_virtual_environment_user_example_acl" { + value = "${data.proxmox_virtual_environment_user.example.acl}" +} + output "data_proxmox_virtual_environment_user_example_comment" { value = "${data.proxmox_virtual_environment_user.example.comment}" } diff --git a/example/resource_virtual_environment_group.tf b/example/resource_virtual_environment_group.tf index 5084c2f3..b0cc383c 100644 --- a/example/resource_virtual_environment_group.tf +++ b/example/resource_virtual_environment_group.tf @@ -1,8 +1,17 @@ resource "proxmox_virtual_environment_group" "example" { + acl { + path = "/vms/1" + role_id = "${proxmox_virtual_environment_role.example.id}" + } + comment = "Managed by Terraform" group_id = "terraform-provider-proxmox-example" } +output "resource_proxmox_virtual_environment_group_example_acl" { + value = "${proxmox_virtual_environment_group.example.acl}" +} + output "resource_proxmox_virtual_environment_group_example_comment" { value = "${proxmox_virtual_environment_group.example.comment}" } diff --git a/example/resource_virtual_environment_role.tf b/example/resource_virtual_environment_role.tf index 9f23da7b..6b9832dd 100644 --- a/example/resource_virtual_environment_role.tf +++ b/example/resource_virtual_environment_role.tf @@ -1,6 +1,5 @@ resource "proxmox_virtual_environment_role" "example" { privileges = [ - "VM.Console", "VM.Monitor", ] role_id = "terraform-provider-proxmox-example" diff --git a/example/resource_virtual_environment_user.tf b/example/resource_virtual_environment_user.tf index 6129580a..bd0b1ecf 100644 --- a/example/resource_virtual_environment_user.tf +++ b/example/resource_virtual_environment_user.tf @@ -1,9 +1,19 @@ resource "proxmox_virtual_environment_user" "example" { + acl { + path = "/" + propagate = true + role_id = "PVEVMAdmin" + } + comment = "Managed by Terraform" password = "Test1234!" user_id = "terraform-provider-proxmox-example@pve" } +output "resource_proxmox_virtual_environment_user_example_acl" { + value = "${proxmox_virtual_environment_user.example.acl}" +} + output "resource_proxmox_virtual_environment_user_example_comment" { value = "${proxmox_virtual_environment_user.example.comment}" } diff --git a/proxmox/virtual_environment_acl.go b/proxmox/virtual_environment_acl.go new file mode 100644 index 00000000..97efa097 --- /dev/null +++ b/proxmox/virtual_environment_acl.go @@ -0,0 +1,59 @@ +/* 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 proxmox + +import ( + "errors" + "sort" +) + +// VirtualEnvironmentACLGetResponseBody contains the body from an access control list response. +type VirtualEnvironmentACLGetResponseBody struct { + Data []*VirtualEnvironmentACLGetResponseData `json:"data,omitempty"` +} + +// VirtualEnvironmentACLGetResponseData contains the data from an access control list response. +type VirtualEnvironmentACLGetResponseData struct { + Path string `json:"path"` + Propagate *CustomBool `json:"propagate,omitempty"` + RoleID string `json:"roleid"` + Type string `json:"type"` + UserOrGroupID string `json:"ugid"` +} + +// VirtualEnvironmentACLUpdateRequestBody contains the data for an access control list update request. +type VirtualEnvironmentACLUpdateRequestBody struct { + Delete *CustomBool `json:"delete,omitempty" url:"delete,omitempty,int"` + Groups []string `json:"groups,omitempty" url:"groups,omitempty,comma"` + Path string `json:"path" url:"path"` + Propagate *CustomBool `json:"propagate,omitempty" url:"propagate,omitempty,int"` + Roles []string `json:"roles" url:"roles,comma"` + Users []string `json:"users,omitempty" url:"users,omitempty,comma"` +} + +// GetACL retrieves the access control list. +func (c *VirtualEnvironmentClient) GetACL() ([]*VirtualEnvironmentACLGetResponseData, error) { + resBody := &VirtualEnvironmentACLGetResponseBody{} + err := c.DoRequest(hmGET, "access/acl", nil, resBody) + + if err != nil { + return nil, err + } + + if resBody.Data == nil { + return nil, errors.New("The server did not include a data object in the response") + } + + sort.Slice(resBody.Data, func(i, j int) bool { + return resBody.Data[i].Path < resBody.Data[j].Path + }) + + return resBody.Data, nil +} + +// UpdateACL updates the access control list. +func (c *VirtualEnvironmentClient) UpdateACL(d *VirtualEnvironmentACLUpdateRequestBody) error { + return c.DoRequest(hmPUT, "access/acl", d, nil) +} diff --git a/resource_virtual_environment_group.go b/resource_virtual_environment_group.go index d5630e00..cc10e75f 100644 --- a/resource_virtual_environment_group.go +++ b/resource_virtual_environment_group.go @@ -12,14 +12,46 @@ import ( ) const ( - mkResourceVirtualEnvironmentGroupComment = "comment" - mkResourceVirtualEnvironmentGroupID = "group_id" - mkResourceVirtualEnvironmentGroupMembers = "members" + mkResourceVirtualEnvironmentGroupACL = "acl" + mkResourceVirtualEnvironmentGroupACLPath = "path" + mkResourceVirtualEnvironmentGroupACLPropagate = "propagate" + mkResourceVirtualEnvironmentGroupACLRoleID = "role_id" + mkResourceVirtualEnvironmentGroupComment = "comment" + mkResourceVirtualEnvironmentGroupID = "group_id" + mkResourceVirtualEnvironmentGroupMembers = "members" ) func resourceVirtualEnvironmentGroup() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentGroupACL: &schema.Schema{ + Type: schema.TypeSet, + Description: "The access control list", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return make([]interface{}, 0), nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentGroupACLPath: { + Type: schema.TypeString, + Required: true, + Description: "The path", + }, + mkResourceVirtualEnvironmentGroupACLPropagate: { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to propagate to child paths", + Default: false, + }, + mkResourceVirtualEnvironmentGroupACLRoleID: { + Type: schema.TypeString, + Required: true, + Description: "The role id", + }, + }, + }, + }, mkResourceVirtualEnvironmentGroupComment: &schema.Schema{ Type: schema.TypeString, Description: "The group comment", @@ -70,6 +102,28 @@ func resourceVirtualEnvironmentGroupCreate(d *schema.ResourceData, m interface{} d.SetId(groupID) + aclParsed := d.Get(mkResourceVirtualEnvironmentGroupACL).(*schema.Set).List() + + for _, v := range aclParsed { + aclDelete := proxmox.CustomBool(false) + aclEntry := v.(map[string]interface{}) + aclPropagate := proxmox.CustomBool(aclEntry[mkResourceVirtualEnvironmentGroupACLPropagate].(bool)) + + aclBody := &proxmox.VirtualEnvironmentACLUpdateRequestBody{ + Delete: &aclDelete, + Groups: []string{groupID}, + Path: aclEntry[mkResourceVirtualEnvironmentGroupACLPath].(string), + Propagate: &aclPropagate, + Roles: []string{aclEntry[mkResourceVirtualEnvironmentGroupACLRoleID].(string)}, + } + + err := veClient.UpdateACL(aclBody) + + if err != nil { + return err + } + } + return resourceVirtualEnvironmentGroupRead(d, m) } @@ -82,7 +136,7 @@ func resourceVirtualEnvironmentGroupRead(d *schema.ResourceData, m interface{}) } groupID := d.Id() - accessGroup, err := veClient.GetGroup(groupID) + group, err := veClient.GetGroup(groupID) if err != nil { if strings.Contains(err.Error(), "HTTP 404") { @@ -94,15 +148,43 @@ func resourceVirtualEnvironmentGroupRead(d *schema.ResourceData, m interface{}) return err } + acl, err := veClient.GetACL() + + if err != nil { + return err + } + d.SetId(groupID) - if accessGroup.Comment != nil { - d.Set(mkResourceVirtualEnvironmentGroupComment, accessGroup.Comment) + aclParsed := make([]interface{}, 0) + + for _, v := range acl { + if v.Type == "group" && v.UserOrGroupID == groupID { + aclEntry := make(map[string]interface{}) + + aclEntry[mkResourceVirtualEnvironmentGroupACLPath] = v.Path + + if v.Propagate != nil { + aclEntry[mkResourceVirtualEnvironmentGroupACLPropagate] = bool(*v.Propagate) + } else { + aclEntry[mkResourceVirtualEnvironmentGroupACLPropagate] = false + } + + aclEntry[mkResourceVirtualEnvironmentGroupACLRoleID] = v.RoleID + + aclParsed = append(aclParsed, aclEntry) + } + } + + d.Set(mkResourceVirtualEnvironmentGroupACL, aclParsed) + + if group.Comment != nil { + d.Set(mkResourceVirtualEnvironmentGroupComment, group.Comment) } else { d.Set(mkResourceVirtualEnvironmentGroupComment, "") } - d.Set(mkResourceVirtualEnvironmentGroupMembers, accessGroup.Members) + d.Set(mkResourceVirtualEnvironmentGroupMembers, group.Members) return nil } @@ -128,6 +210,51 @@ func resourceVirtualEnvironmentGroupUpdate(d *schema.ResourceData, m interface{} return err } + aclArgOld, aclArg := d.GetChange(mkResourceVirtualEnvironmentGroupACL) + aclParsedOld := aclArgOld.(*schema.Set).List() + + for _, v := range aclParsedOld { + aclDelete := proxmox.CustomBool(true) + aclEntry := v.(map[string]interface{}) + aclPropagate := proxmox.CustomBool(aclEntry[mkResourceVirtualEnvironmentGroupACLPropagate].(bool)) + + aclBody := &proxmox.VirtualEnvironmentACLUpdateRequestBody{ + Delete: &aclDelete, + Groups: []string{groupID}, + Path: aclEntry[mkResourceVirtualEnvironmentGroupACLPath].(string), + Propagate: &aclPropagate, + Roles: []string{aclEntry[mkResourceVirtualEnvironmentGroupACLRoleID].(string)}, + } + + err := veClient.UpdateACL(aclBody) + + if err != nil { + return err + } + } + + aclParsed := aclArg.(*schema.Set).List() + + for _, v := range aclParsed { + aclDelete := proxmox.CustomBool(false) + aclEntry := v.(map[string]interface{}) + aclPropagate := proxmox.CustomBool(aclEntry[mkResourceVirtualEnvironmentGroupACLPropagate].(bool)) + + aclBody := &proxmox.VirtualEnvironmentACLUpdateRequestBody{ + Delete: &aclDelete, + Groups: []string{groupID}, + Path: aclEntry[mkResourceVirtualEnvironmentGroupACLPath].(string), + Propagate: &aclPropagate, + Roles: []string{aclEntry[mkResourceVirtualEnvironmentGroupACLRoleID].(string)}, + } + + err := veClient.UpdateACL(aclBody) + + if err != nil { + return err + } + } + return resourceVirtualEnvironmentGroupRead(d, m) } @@ -139,7 +266,29 @@ func resourceVirtualEnvironmentGroupDelete(d *schema.ResourceData, m interface{} return err } + aclParsed := d.Get(mkResourceVirtualEnvironmentGroupACL).(*schema.Set).List() groupID := d.Id() + + for _, v := range aclParsed { + aclDelete := proxmox.CustomBool(true) + aclEntry := v.(map[string]interface{}) + aclPropagate := proxmox.CustomBool(aclEntry[mkResourceVirtualEnvironmentGroupACLPropagate].(bool)) + + aclBody := &proxmox.VirtualEnvironmentACLUpdateRequestBody{ + Delete: &aclDelete, + Groups: []string{groupID}, + Path: aclEntry[mkResourceVirtualEnvironmentGroupACLPath].(string), + Propagate: &aclPropagate, + Roles: []string{aclEntry[mkResourceVirtualEnvironmentGroupACLRoleID].(string)}, + } + + err := veClient.UpdateACL(aclBody) + + if err != nil { + return err + } + } + err = veClient.DeleteGroup(groupID) if err != nil { diff --git a/resource_virtual_environment_role.go b/resource_virtual_environment_role.go index 5054486b..47135a79 100644 --- a/resource_virtual_environment_role.go +++ b/resource_virtual_environment_role.go @@ -80,7 +80,7 @@ func resourceVirtualEnvironmentRoleRead(d *schema.ResourceData, m interface{}) e } roleID := d.Id() - accessRole, err := veClient.GetRole(roleID) + role, err := veClient.GetRole(roleID) if err != nil { if strings.Contains(err.Error(), "HTTP 404") { @@ -94,8 +94,8 @@ func resourceVirtualEnvironmentRoleRead(d *schema.ResourceData, m interface{}) e privileges := schema.NewSet(schema.HashString, make([]interface{}, 0)) - if *accessRole != nil { - for _, v := range *accessRole { + if *role != nil { + for _, v := range *role { privileges.Add(v) } } diff --git a/resource_virtual_environment_user.go b/resource_virtual_environment_user.go index f342da41..9beedccf 100644 --- a/resource_virtual_environment_user.go +++ b/resource_virtual_environment_user.go @@ -14,6 +14,10 @@ import ( ) const ( + mkResourceVirtualEnvironmentUserACL = "acl" + mkResourceVirtualEnvironmentUserACLPath = "path" + mkResourceVirtualEnvironmentUserACLPropagate = "propagate" + mkResourceVirtualEnvironmentUserACLRoleID = "role_id" mkResourceVirtualEnvironmentUserComment = "comment" mkResourceVirtualEnvironmentUserEmail = "email" mkResourceVirtualEnvironmentUserEnabled = "enabled" @@ -29,6 +33,34 @@ const ( func resourceVirtualEnvironmentUser() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentUserACL: &schema.Schema{ + Type: schema.TypeSet, + Description: "The access control list", + Optional: true, + DefaultFunc: func() (interface{}, error) { + return make([]interface{}, 0), nil + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + mkResourceVirtualEnvironmentUserACLPath: { + Type: schema.TypeString, + Required: true, + Description: "The path", + }, + mkResourceVirtualEnvironmentUserACLPropagate: { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to propagate to child paths", + Default: false, + }, + mkResourceVirtualEnvironmentUserACLRoleID: { + Type: schema.TypeString, + Required: true, + Description: "The role id", + }, + }, + }, + }, mkResourceVirtualEnvironmentUserComment: &schema.Schema{ Type: schema.TypeString, Description: "The user comment", @@ -64,10 +96,10 @@ func resourceVirtualEnvironmentUser() *schema.Resource { Type: schema.TypeSet, Description: "The user's groups", Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, DefaultFunc: func() (interface{}, error) { return []string{}, nil }, + Elem: &schema.Schema{Type: schema.TypeString}, }, mkResourceVirtualEnvironmentUserKeys: &schema.Schema{ Type: schema.TypeString, @@ -152,6 +184,28 @@ func resourceVirtualEnvironmentUserCreate(d *schema.ResourceData, m interface{}) d.SetId(userID) + aclParsed := d.Get(mkResourceVirtualEnvironmentUserACL).(*schema.Set).List() + + for _, v := range aclParsed { + aclDelete := proxmox.CustomBool(false) + aclEntry := v.(map[string]interface{}) + aclPropagate := proxmox.CustomBool(aclEntry[mkResourceVirtualEnvironmentUserACLPropagate].(bool)) + + aclBody := &proxmox.VirtualEnvironmentACLUpdateRequestBody{ + Delete: &aclDelete, + Path: aclEntry[mkResourceVirtualEnvironmentUserACLPath].(string), + Propagate: &aclPropagate, + Roles: []string{aclEntry[mkResourceVirtualEnvironmentUserACLRoleID].(string)}, + Users: []string{userID}, + } + + err := veClient.UpdateACL(aclBody) + + if err != nil { + return err + } + } + return resourceVirtualEnvironmentUserRead(d, m) } @@ -176,8 +230,36 @@ func resourceVirtualEnvironmentUserRead(d *schema.ResourceData, m interface{}) e return err } + acl, err := veClient.GetACL() + + if err != nil { + return err + } + d.SetId(userID) + aclParsed := make([]interface{}, 0) + + for _, v := range acl { + if v.Type == "user" && v.UserOrGroupID == userID { + aclEntry := make(map[string]interface{}) + + aclEntry[mkResourceVirtualEnvironmentUserACLPath] = v.Path + + if v.Propagate != nil { + aclEntry[mkResourceVirtualEnvironmentUserACLPropagate] = bool(*v.Propagate) + } else { + aclEntry[mkResourceVirtualEnvironmentUserACLPropagate] = false + } + + aclEntry[mkResourceVirtualEnvironmentUserACLRoleID] = v.RoleID + + aclParsed = append(aclParsed, aclEntry) + } + } + + d.Set(mkResourceVirtualEnvironmentUserACL, aclParsed) + if user.Comment != nil { d.Set(mkResourceVirtualEnvironmentUserComment, user.Comment) } else { @@ -289,6 +371,51 @@ func resourceVirtualEnvironmentUserUpdate(d *schema.ResourceData, m interface{}) } } + aclArgOld, aclArg := d.GetChange(mkResourceVirtualEnvironmentUserACL) + aclParsedOld := aclArgOld.(*schema.Set).List() + + for _, v := range aclParsedOld { + aclDelete := proxmox.CustomBool(true) + aclEntry := v.(map[string]interface{}) + aclPropagate := proxmox.CustomBool(aclEntry[mkResourceVirtualEnvironmentUserACLPropagate].(bool)) + + aclBody := &proxmox.VirtualEnvironmentACLUpdateRequestBody{ + Delete: &aclDelete, + Path: aclEntry[mkResourceVirtualEnvironmentUserACLPath].(string), + Propagate: &aclPropagate, + Roles: []string{aclEntry[mkResourceVirtualEnvironmentUserACLRoleID].(string)}, + Users: []string{userID}, + } + + err := veClient.UpdateACL(aclBody) + + if err != nil { + return err + } + } + + aclParsed := aclArg.(*schema.Set).List() + + for _, v := range aclParsed { + aclDelete := proxmox.CustomBool(false) + aclEntry := v.(map[string]interface{}) + aclPropagate := proxmox.CustomBool(aclEntry[mkResourceVirtualEnvironmentUserACLPropagate].(bool)) + + aclBody := &proxmox.VirtualEnvironmentACLUpdateRequestBody{ + Delete: &aclDelete, + Path: aclEntry[mkResourceVirtualEnvironmentUserACLPath].(string), + Propagate: &aclPropagate, + Roles: []string{aclEntry[mkResourceVirtualEnvironmentUserACLRoleID].(string)}, + Users: []string{userID}, + } + + err := veClient.UpdateACL(aclBody) + + if err != nil { + return err + } + } + return resourceVirtualEnvironmentUserRead(d, m) } @@ -300,7 +427,29 @@ func resourceVirtualEnvironmentUserDelete(d *schema.ResourceData, m interface{}) return err } + aclParsed := d.Get(mkResourceVirtualEnvironmentUserACL).(*schema.Set).List() userID := d.Id() + + for _, v := range aclParsed { + aclDelete := proxmox.CustomBool(true) + aclEntry := v.(map[string]interface{}) + aclPropagate := proxmox.CustomBool(aclEntry[mkResourceVirtualEnvironmentUserACLPropagate].(bool)) + + aclBody := &proxmox.VirtualEnvironmentACLUpdateRequestBody{ + Delete: &aclDelete, + Path: aclEntry[mkResourceVirtualEnvironmentUserACLPath].(string), + Propagate: &aclPropagate, + Roles: []string{aclEntry[mkResourceVirtualEnvironmentUserACLRoleID].(string)}, + Users: []string{userID}, + } + + err := veClient.UpdateACL(aclBody) + + if err != nil { + return err + } + } + err = veClient.DeleteUser(userID) if err != nil {