mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-30 02:31:10 +00:00
feat(access): add proxmox user token (#1159)
This commit is contained in:
parent
41ec34e04b
commit
8220271eee
61
docs/resources/virtual_environment_user_token.md
Normal file
61
docs/resources/virtual_environment_user_token.md
Normal file
@ -0,0 +1,61 @@
|
||||
---
|
||||
layout: page
|
||||
title: proxmox_virtual_environment_user_token
|
||||
parent: Resources
|
||||
subcategory: Virtual Environment
|
||||
description: |-
|
||||
User API tokens.
|
||||
---
|
||||
|
||||
# Resource: proxmox_virtual_environment_user_token
|
||||
|
||||
User API tokens.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
# if creating a user token, the user must be created first
|
||||
resource "proxmox_virtual_environment_user" "user" {
|
||||
comment = "Managed by Terraform"
|
||||
email = "user@pve"
|
||||
enabled = true
|
||||
expiration_date = "2034-01-01T22:00:00Z"
|
||||
user_id = "user@pve"
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_user_token" "user_token" {
|
||||
comment = "Managed by Terraform"
|
||||
expiration_date = "2033-01-01T22:00:00Z"
|
||||
token_name = "tk1"
|
||||
user_id = proxmox_virtual_environment_user.user.user_id
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `token_name` (String) User-specific token identifier.
|
||||
- `user_id` (String) User identifier.
|
||||
|
||||
### Optional
|
||||
|
||||
- `comment` (String) Comment for the token.
|
||||
- `expiration_date` (String) Expiration date for the token.
|
||||
- `privileges_separation` (Boolean) Restrict API token privileges with separate ACLs (default), or give full privileges of corresponding user.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `id` (String) Unique token identifier with format `<user_id>!<token_name>`.
|
||||
- `value` (String, Sensitive) API token value used for authentication. It is populated only when creating a new token, and can't be retrieved at import.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
#!/usr/bin/env sh
|
||||
#Tokens can be imported using they identifiers in format `user_id!token_name` format, e.g.:
|
||||
terraform import proxmox_virtual_environment_user_token.token1 user@pve!token1
|
||||
```
|
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env sh
|
||||
#Tokens can be imported using they identifiers in format `user_id!token_name` format, e.g.:
|
||||
terraform import proxmox_virtual_environment_user_token.token1 user@pve!token1
|
@ -0,0 +1,15 @@
|
||||
# if creating a user token, the user must be created first
|
||||
resource "proxmox_virtual_environment_user" "user" {
|
||||
comment = "Managed by Terraform"
|
||||
email = "user@pve"
|
||||
enabled = true
|
||||
expiration_date = "2034-01-01T22:00:00Z"
|
||||
user_id = "user@pve"
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_user_token" "user_token" {
|
||||
comment = "Managed by Terraform"
|
||||
expiration_date = "2033-01-01T22:00:00Z"
|
||||
token_name = "tk1"
|
||||
user_id = proxmox_virtual_environment_user.user.user_id
|
||||
}
|
306
fwprovider/access/resource_user_token.go
Normal file
306
fwprovider/access/resource_user_token.go
Normal file
@ -0,0 +1,306 @@
|
||||
package access
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"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/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/structure"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/validators"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox"
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/access"
|
||||
proxmoxtypes "github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
)
|
||||
|
||||
var (
|
||||
_ resource.Resource = &userTokenResource{}
|
||||
_ resource.ResourceWithConfigure = &userTokenResource{}
|
||||
_ resource.ResourceWithImportState = &userTokenResource{}
|
||||
)
|
||||
|
||||
type userTokenResource struct {
|
||||
client proxmox.Client
|
||||
}
|
||||
|
||||
type userTokenModel struct {
|
||||
Comment types.String `tfsdk:"comment"`
|
||||
ExpirationDate types.String `tfsdk:"expiration_date"`
|
||||
ID types.String `tfsdk:"id"`
|
||||
PrivSeparation types.Bool `tfsdk:"privileges_separation"`
|
||||
UserID types.String `tfsdk:"user_id"`
|
||||
TokenName types.String `tfsdk:"token_name"`
|
||||
Value types.String `tfsdk:"value"`
|
||||
}
|
||||
|
||||
// NewUserTokenResource creates a new user token resource.
|
||||
func NewUserTokenResource() resource.Resource {
|
||||
return &userTokenResource{}
|
||||
}
|
||||
|
||||
func (r *userTokenResource) Schema(
|
||||
_ context.Context,
|
||||
_ resource.SchemaRequest,
|
||||
resp *resource.SchemaResponse,
|
||||
) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "User API tokens.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"comment": schema.StringAttribute{
|
||||
Description: "Comment for the token.",
|
||||
Optional: true,
|
||||
},
|
||||
"expiration_date": schema.StringAttribute{
|
||||
Description: "Expiration date for the token.",
|
||||
Optional: true,
|
||||
Validators: []validator.String{
|
||||
validators.NewParseValidator(func(s string) (time.Time, error) {
|
||||
return time.Parse(time.RFC3339, s)
|
||||
}, "must be a valid RFC3339 date"),
|
||||
},
|
||||
},
|
||||
"id": structure.IDAttribute("Unique token identifier with format `<user_id>!<token_name>`."),
|
||||
"privileges_separation": schema.BoolAttribute{
|
||||
Description: "Restrict API token privileges with separate ACLs (default)",
|
||||
MarkdownDescription: "Restrict API token privileges with separate ACLs (default), " +
|
||||
"or give full privileges of corresponding user.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Default: booldefault.StaticBool(true),
|
||||
},
|
||||
"token_name": schema.StringAttribute{
|
||||
Description: "User-specific token identifier.",
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.RegexMatches(regexp.MustCompile(`[A-Za-z][A-Za-z0-9.\-_]+`), "must be a valid token identifier"),
|
||||
},
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"user_id": schema.StringAttribute{
|
||||
Description: "User identifier.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"value": schema.StringAttribute{
|
||||
Description: "API token value used for authentication.",
|
||||
MarkdownDescription: "API token value used for authentication. It is populated only when creating a new token, " +
|
||||
"and can't be retrieved at import.",
|
||||
Computed: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *userTokenResource) 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", req.ProviderData),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
r.client = client
|
||||
}
|
||||
|
||||
func (r *userTokenResource) Metadata(
|
||||
_ context.Context,
|
||||
req resource.MetadataRequest,
|
||||
resp *resource.MetadataResponse,
|
||||
) {
|
||||
resp.TypeName = req.ProviderTypeName + "_user_token"
|
||||
}
|
||||
|
||||
func (r *userTokenResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan userTokenModel
|
||||
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
|
||||
body := access.UserTokenCreateRequestBody{
|
||||
Comment: plan.Comment.ValueStringPointer(),
|
||||
PrivSeparate: proxmoxtypes.CustomBoolPtr(plan.PrivSeparation.ValueBoolPointer()),
|
||||
}
|
||||
|
||||
if !plan.ExpirationDate.IsNull() && plan.ExpirationDate.ValueString() != "" {
|
||||
expirationDate, err := time.Parse(
|
||||
time.RFC3339,
|
||||
plan.ExpirationDate.ValueString(),
|
||||
)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error parsing expiration date", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
v := expirationDate.Unix()
|
||||
body.ExpirationDate = &v
|
||||
}
|
||||
|
||||
value, err := r.client.Access().CreateUserToken(ctx, plan.UserID.ValueString(), plan.TokenName.ValueString(), &body)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error creating user token", err.Error())
|
||||
}
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
plan.ID = types.StringValue(plan.UserID.ValueString() + "!" + plan.TokenName.ValueString())
|
||||
plan.Value = types.StringValue(value)
|
||||
resp.State.Set(ctx, plan)
|
||||
}
|
||||
|
||||
func (r *userTokenResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state userTokenModel
|
||||
diags := req.State.Get(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := r.client.Access().GetUserToken(ctx, state.UserID.ValueString(), state.TokenName.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error reading user token", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
state.Comment = types.StringPointerValue(data.Comment)
|
||||
|
||||
if data.ExpirationDate != nil {
|
||||
dt := time.Unix(int64(*data.ExpirationDate), 0).UTC().Format(time.RFC3339)
|
||||
state.ExpirationDate = types.StringValue(dt)
|
||||
}
|
||||
|
||||
state.PrivSeparation = types.BoolPointerValue(data.PrivSeparate.PointerBool())
|
||||
|
||||
diags = resp.State.Set(ctx, state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
}
|
||||
|
||||
func (r *userTokenResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan, state userTokenModel
|
||||
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
body := access.UserTokenUpdateRequestBody{
|
||||
Comment: plan.Comment.ValueStringPointer(),
|
||||
PrivSeparate: proxmoxtypes.CustomBoolPtr(plan.PrivSeparation.ValueBoolPointer()),
|
||||
}
|
||||
|
||||
if !plan.ExpirationDate.IsNull() && plan.ExpirationDate.ValueString() != "" {
|
||||
expirationDate, err := time.Parse(
|
||||
time.RFC3339,
|
||||
plan.ExpirationDate.ValueString(),
|
||||
)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error parsing expiration date", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
v := expirationDate.Unix()
|
||||
body.ExpirationDate = &v
|
||||
}
|
||||
|
||||
err := r.client.Access().UpdateUserToken(ctx, plan.UserID.ValueString(), plan.TokenName.ValueString(), &body)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error creating user token", err.Error())
|
||||
}
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
plan.Value = types.StringNull()
|
||||
|
||||
resp.State.Set(ctx, plan)
|
||||
}
|
||||
|
||||
func (r *userTokenResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var state userTokenModel
|
||||
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
err := r.client.Access().DeleteUserToken(ctx, state.UserID.ValueString(), state.TokenName.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error deleting user token", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
resp.State.RemoveResource(ctx)
|
||||
}
|
||||
|
||||
func (r *userTokenResource) ImportState(
|
||||
ctx context.Context,
|
||||
req resource.ImportStateRequest,
|
||||
resp *resource.ImportStateResponse,
|
||||
) {
|
||||
idParts := strings.Split(req.ID, "!")
|
||||
if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" {
|
||||
resp.Diagnostics.AddError(
|
||||
"Unexpected Import Identifier",
|
||||
fmt.Sprintf("Expected import identifier with format: 'user_id!token_name'. Got: %q", req.ID),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
userID := idParts[0]
|
||||
tokenName := idParts[1]
|
||||
|
||||
data, err := r.client.Access().GetUserToken(ctx, userID, tokenName)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Error reading user token", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
state := userTokenModel{
|
||||
Comment: types.StringPointerValue(data.Comment),
|
||||
ID: types.StringValue(req.ID),
|
||||
PrivSeparation: types.BoolPointerValue(data.PrivSeparate.PointerBool()),
|
||||
UserID: types.StringValue(userID),
|
||||
TokenName: types.StringValue(tokenName),
|
||||
Value: types.StringNull(),
|
||||
}
|
||||
|
||||
if data.ExpirationDate != nil {
|
||||
state.ExpirationDate = types.StringValue(time.Unix(int64(*data.ExpirationDate), 0).UTC().Format(time.RFC3339))
|
||||
}
|
||||
|
||||
diags := resp.State.Set(ctx, state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
}
|
@ -25,6 +25,7 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/access"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/ha"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/hardwaremapping"
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider/network"
|
||||
@ -446,6 +447,7 @@ func (p *proxmoxProvider) Resources(_ context.Context) []func() resource.Resourc
|
||||
hardwaremapping.NewResourceUSB,
|
||||
network.NewLinuxBridgeResource,
|
||||
network.NewLinuxVLANResource,
|
||||
access.NewUserTokenResource,
|
||||
vm.NewVMResource,
|
||||
NewClusterOptionsResource,
|
||||
NewDownloadFileResource,
|
||||
|
@ -7,9 +7,15 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v7"
|
||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/access"
|
||||
)
|
||||
|
||||
func TestAccResourceUser(t *testing.T) {
|
||||
@ -17,45 +23,55 @@ func TestAccResourceUser(t *testing.T) {
|
||||
|
||||
te := initTestEnvironment(t)
|
||||
|
||||
username := fmt.Sprintf("%s@pve", gofakeit.Username())
|
||||
te.addTemplateVars(map[string]any{
|
||||
"Username": username,
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
steps []resource.TestStep
|
||||
}{
|
||||
{"create and update user", []resource.TestStep{
|
||||
{
|
||||
Config: `resource "proxmox_virtual_environment_user" "user1" {
|
||||
Config: te.renderConfig(`resource "proxmox_virtual_environment_user" "user" {
|
||||
comment = "Managed by Terraform"
|
||||
email = "user1@pve"
|
||||
email = "{{.Username}}"
|
||||
enabled = true
|
||||
expiration_date = "2034-01-01T22:00:00Z"
|
||||
first_name = "First"
|
||||
last_name = "Last"
|
||||
user_id = "user1@pve"
|
||||
}`,
|
||||
Check: testResourceAttributes("proxmox_virtual_environment_user.user1", map[string]string{
|
||||
user_id = "{{.Username}}"
|
||||
}`),
|
||||
Check: testResourceAttributes("proxmox_virtual_environment_user.user", map[string]string{
|
||||
"comment": "Managed by Terraform",
|
||||
"email": "user1@pve",
|
||||
"email": username,
|
||||
"enabled": "true",
|
||||
"expiration_date": "2034-01-01T22:00:00Z",
|
||||
"first_name": "First",
|
||||
"last_name": "Last",
|
||||
"user_id": "user1@pve",
|
||||
"user_id": username,
|
||||
}),
|
||||
},
|
||||
{
|
||||
Config: `resource "proxmox_virtual_environment_user" "user1" {
|
||||
Config: te.renderConfig(`resource "proxmox_virtual_environment_user" "user" {
|
||||
enabled = false
|
||||
expiration_date = "2035-01-01T22:00:00Z"
|
||||
user_id = "user1@pve"
|
||||
user_id = "{{.Username}}"
|
||||
first_name = "First One"
|
||||
}`,
|
||||
Check: testResourceAttributes("proxmox_virtual_environment_user.user1", map[string]string{
|
||||
}`),
|
||||
Check: testResourceAttributes("proxmox_virtual_environment_user.user", map[string]string{
|
||||
"enabled": "false",
|
||||
"expiration_date": "2035-01-01T22:00:00Z",
|
||||
"first_name": "First One",
|
||||
"user_id": "user1@pve",
|
||||
"user_id": username,
|
||||
}),
|
||||
},
|
||||
{
|
||||
ResourceName: "proxmox_virtual_environment_user.user",
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
@ -68,3 +84,91 @@ func TestAccResourceUser(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccResourceUserToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
te := initTestEnvironment(t)
|
||||
username := fmt.Sprintf("%s@pve", gofakeit.Username())
|
||||
tokenName := gofakeit.Word()
|
||||
|
||||
te.addTemplateVars(map[string]any{
|
||||
"Username": username,
|
||||
"TokenName": tokenName,
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
preCheck func()
|
||||
steps []resource.TestStep
|
||||
}{
|
||||
{
|
||||
"create and update user token",
|
||||
func() {
|
||||
err := te.accessClient().CreateUser(context.Background(), &access.UserCreateRequestBody{
|
||||
ID: username,
|
||||
Password: gofakeit.Password(true, true, true, true, false, 8),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
err := te.accessClient().DeleteUser(context.Background(), username)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
},
|
||||
[]resource.TestStep{
|
||||
{
|
||||
Config: te.renderConfig(`resource "proxmox_virtual_environment_user_token" "user_token" {
|
||||
comment = "Managed by Terraform"
|
||||
expiration_date = "2034-01-01T22:00:00Z"
|
||||
token_name = "{{.TokenName}}"
|
||||
user_id = "{{.Username}}"
|
||||
}`),
|
||||
Check: testResourceAttributes("proxmox_virtual_environment_user_token.user_token", map[string]string{
|
||||
"comment": "Managed by Terraform",
|
||||
"expiration_date": "2034-01-01T22:00:00Z",
|
||||
"id": fmt.Sprintf("%s!%s", username, tokenName),
|
||||
"user_id": username,
|
||||
"value": fmt.Sprintf("%s!%s=.*", username, tokenName),
|
||||
}),
|
||||
},
|
||||
{
|
||||
Config: te.renderConfig(`resource "proxmox_virtual_environment_user_token" "user_token" {
|
||||
comment = "Managed by Terraform 2"
|
||||
expiration_date = "2033-01-01T01:01:01Z"
|
||||
privileges_separation = false
|
||||
token_name = "{{.TokenName}}"
|
||||
user_id = "{{.Username}}"
|
||||
}`),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testResourceAttributes("proxmox_virtual_environment_user_token.user_token", map[string]string{
|
||||
"comment": "Managed by Terraform 2",
|
||||
"expiration_date": "2033-01-01T01:01:01Z",
|
||||
"privileges_separation": "false",
|
||||
"token_name": tokenName,
|
||||
"user_id": username,
|
||||
}),
|
||||
testNoResourceAttributesSet("proxmox_virtual_environment_user_token.user_token", []string{
|
||||
"value",
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
ResourceName: "proxmox_virtual_environment_user_token.user_token",
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resource.Test(t, resource.TestCase{
|
||||
ProtoV6ProviderFactories: te.accProviders,
|
||||
PreCheck: tt.preCheck,
|
||||
Steps: tt.steps,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/access"
|
||||
sdkV2provider "github.com/bpg/terraform-provider-proxmox/proxmoxtf/provider"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/fwprovider"
|
||||
@ -35,7 +36,7 @@ type testEnvironment struct {
|
||||
|
||||
accProviders map[string]func() (tfprotov6.ProviderServer, error)
|
||||
once sync.Once
|
||||
nc *nodes.Client
|
||||
c api.Client
|
||||
}
|
||||
|
||||
func initTestEnvironment(t *testing.T) *testEnvironment {
|
||||
@ -109,8 +110,8 @@ func (e *testEnvironment) renderConfig(cfg string) string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (e *testEnvironment) nodeClient() *nodes.Client {
|
||||
if e.nc == nil {
|
||||
func (e *testEnvironment) client() api.Client {
|
||||
if e.c == nil {
|
||||
e.once.Do(
|
||||
func() {
|
||||
username := utils.GetAnyStringEnv("PROXMOX_VE_USERNAME")
|
||||
@ -128,21 +129,26 @@ func (e *testEnvironment) nodeClient() *nodes.Client {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
client, err := api.NewClient(creds, conn)
|
||||
e.c, err = api.NewClient(creds, conn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
e.nc = &nodes.Client{Client: client, NodeName: e.nodeName}
|
||||
})
|
||||
}
|
||||
|
||||
return e.nc
|
||||
return e.c
|
||||
}
|
||||
|
||||
func (e *testEnvironment) accessClient() *access.Client {
|
||||
return &access.Client{Client: e.client()}
|
||||
}
|
||||
|
||||
func (e *testEnvironment) nodeClient() *nodes.Client {
|
||||
return &nodes.Client{Client: e.client(), NodeName: e.nodeName}
|
||||
}
|
||||
|
||||
func (e *testEnvironment) nodeStorageClient() *storage.Client {
|
||||
nodesClient := e.nodeClient()
|
||||
return &storage.Client{Client: nodesClient, StorageName: e.datastoreID}
|
||||
return &storage.Client{Client: e.nodeClient(), StorageName: e.datastoreID}
|
||||
}
|
||||
|
||||
// testAccMuxProviders returns a map of mux servers for the acceptance tests.
|
||||
|
93
proxmox/access/user_token.go
Normal file
93
proxmox/access/user_token.go
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||
)
|
||||
|
||||
func (c *Client) userTokensPath(id string) string {
|
||||
return fmt.Sprintf("%s/%s/token", c.usersPath(), url.PathEscape(id))
|
||||
}
|
||||
|
||||
func (c *Client) userTokenPath(userid, id string) string {
|
||||
return fmt.Sprintf("%s/%s", c.userTokensPath(userid), url.PathEscape(id))
|
||||
}
|
||||
|
||||
// CreateUserToken creates a user token.
|
||||
func (c *Client) CreateUserToken(
|
||||
ctx context.Context,
|
||||
userid string,
|
||||
id string,
|
||||
d *UserTokenCreateRequestBody,
|
||||
) (string, error) {
|
||||
resBody := &UserTokenCreateResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodPost, c.userTokenPath(userid, id), d, resBody)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error creating user token: %w", err)
|
||||
}
|
||||
|
||||
return resBody.Data.FullTokenID + "=" + resBody.Data.Value, nil
|
||||
}
|
||||
|
||||
// DeleteUserToken deletes an user token.
|
||||
func (c *Client) DeleteUserToken(ctx context.Context, userid string, id string) error {
|
||||
err := c.DoRequest(ctx, http.MethodDelete, c.userTokenPath(userid, id), nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting user token: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserToken retrieves a user token.
|
||||
func (c *Client) GetUserToken(ctx context.Context, userid string, id string) (*UserTokenGetResponseData, error) {
|
||||
resBody := &UserTokenGetResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.userTokenPath(userid, id), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving user token: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// ListUserTokens retrieves a list of user tokens.
|
||||
func (c *Client) ListUserTokens(ctx context.Context, userid string) ([]*UserTokenListResponseData, error) {
|
||||
resBody := &UserTokenListResponseBody{}
|
||||
|
||||
err := c.DoRequest(ctx, http.MethodGet, c.userTokensPath(userid), nil, resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing user tokens: %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, api.ErrNoDataObjectInResponse
|
||||
}
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
// UpdateUserToken updates the user token.
|
||||
func (c *Client) UpdateUserToken(ctx context.Context, userid string, id string, d *UserTokenUpdateRequestBody) error {
|
||||
err := c.DoRequest(ctx, http.MethodPut, c.userTokenPath(userid, id), d, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating user token: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
58
proxmox/access/user_token_types.go
Normal file
58
proxmox/access/user_token_types.go
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 (
|
||||
"github.com/bpg/terraform-provider-proxmox/proxmox/types"
|
||||
)
|
||||
|
||||
// UserTokenCreateRequestBody contains the data for a user token create request.
|
||||
type UserTokenCreateRequestBody struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
ExpirationDate *int64 `json:"expire,omitempty" url:"expire,omitempty"`
|
||||
PrivSeparate *types.CustomBool `json:"privsep,omitempty" url:"privsep,omitempty,int"`
|
||||
}
|
||||
|
||||
// UserTokenUpdateRequestBody contains the data for a user token update request.
|
||||
type UserTokenUpdateRequestBody UserTokenCreateRequestBody
|
||||
|
||||
// UserTokenCreateResponseBody contains the body from a user token create response.
|
||||
type UserTokenCreateResponseBody struct {
|
||||
Data *UserTokenCreateResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// UserTokenCreateResponseData contains the data from a user token create response.
|
||||
type UserTokenCreateResponseData struct {
|
||||
// The full token id, format "<userid>!<tokenid>"
|
||||
FullTokenID string `json:"full-tokenid"`
|
||||
Info UserTokenGetResponseData `json:"info"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// UserTokenGetResponseBody contains the body from a user token get response.
|
||||
type UserTokenGetResponseBody struct {
|
||||
Data *UserTokenGetResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// UserTokenGetResponseData contains the data from a user token get response.
|
||||
type UserTokenGetResponseData struct {
|
||||
Comment *string `json:"comment,omitempty" url:"comment,omitempty"`
|
||||
PrivSeparate *types.CustomBool `json:"privsep,omitempty" url:"privsep,omitempty,int"`
|
||||
ExpirationDate *types.CustomInt64 `json:"expire,omitempty" url:"expire,omitempty"`
|
||||
}
|
||||
|
||||
// UserTokenListResponseBody contains the body from a user token list response.
|
||||
type UserTokenListResponseBody struct {
|
||||
Data []*UserTokenListResponseData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// UserTokenListResponseData contains the data from a user token list response.
|
||||
type UserTokenListResponseData struct {
|
||||
UserTokenGetResponseData
|
||||
|
||||
TokenID string `json:"tokenid"`
|
||||
}
|
@ -45,4 +45,5 @@ import (
|
||||
//go:generate cp ../build/docs-gen/resources/virtual_environment_haresource.md ../docs/resources/
|
||||
//go:generate cp ../build/docs-gen/resources/virtual_environment_cluster_options.md ../docs/resources/
|
||||
//go:generate cp ../build/docs-gen/resources/virtual_environment_download_file.md ../docs/resources/
|
||||
//go:generate cp ../build/docs-gen/resources/virtual_environment_user_token.md ../docs/resources/
|
||||
//go:generate cp ../build/docs-gen/resources/virtual_environment_vm2.md ../docs/resources/
|
||||
|
Loading…
Reference in New Issue
Block a user