mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-29 18:21:10 +00:00
fix(vm): improve cpu.architecture
handling (#1683)
* chore(tests): add option to select auth type (root user / token) for tests * fix(vm): throw an error when `cpu.architecture` can't be set --------- Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
parent
9d2118d762
commit
be6f220779
@ -257,7 +257,7 @@ func (p *proxmoxProvider) Configure(
|
|||||||
req provider.ConfigureRequest,
|
req provider.ConfigureRequest,
|
||||||
resp *provider.ConfigureResponse,
|
resp *provider.ConfigureResponse,
|
||||||
) {
|
) {
|
||||||
tflog.Info(ctx, "Configuring the Proxmox provider...")
|
tflog.Info(ctx, "Configuring the Framework Proxmox provider...")
|
||||||
|
|
||||||
// Retrieve provider data from configuration
|
// Retrieve provider data from configuration
|
||||||
var cfg proxmoxProviderModel
|
var cfg proxmoxProviderModel
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
//go:build acceptance || all
|
//go:build acceptance || all
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -9,10 +15,13 @@
|
|||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/rand"
|
||||||
"regexp"
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccResourceVM(t *testing.T) {
|
func TestAccResourceVM(t *testing.T) {
|
||||||
@ -142,6 +151,30 @@ func TestAccResourceVM(t *testing.T) {
|
|||||||
),
|
),
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"set cpu.architecture as non root is not supported", []resource.TestStep{{
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
resource "proxmox_virtual_environment_vm" "test_cpu_arch" {
|
||||||
|
node_name = "{{.NodeName}}"
|
||||||
|
started = false
|
||||||
|
cpu {
|
||||||
|
architecture = "x86_64"
|
||||||
|
}
|
||||||
|
}`, WithAPIToken()),
|
||||||
|
ExpectError: regexp.MustCompile(`the CPU architecture can only be set by the root account`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
resource "proxmox_virtual_environment_vm" "template" {
|
||||||
|
node_name = "{{.NodeName}}"
|
||||||
|
started = false
|
||||||
|
cpu {
|
||||||
|
architecture = "x86_64"
|
||||||
|
}
|
||||||
|
}`, WithRootUser()),
|
||||||
|
Destroy: false,
|
||||||
|
}},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"update memory block", []resource.TestStep{{
|
"update memory block", []resource.TestStep{{
|
||||||
Config: te.RenderConfig(`
|
Config: te.RenderConfig(`
|
||||||
@ -518,3 +551,49 @@ func TestAccResourceVMNetwork(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccResourceVMClone(t *testing.T) {
|
||||||
|
if utils.GetAnyStringEnv("TF_ACC") == "" {
|
||||||
|
t.Skip("Acceptance tests are disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
te := InitEnvironment(t)
|
||||||
|
te.AddTemplateVars(map[string]interface{}{
|
||||||
|
"TemplateVMID": 100000 + rand.Intn(99999),
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
step []resource.TestStep
|
||||||
|
}{
|
||||||
|
{"clone cpu.architecture as root", []resource.TestStep{{
|
||||||
|
Config: te.RenderConfig(`
|
||||||
|
resource "proxmox_virtual_environment_vm" "template" {
|
||||||
|
node_name = "{{.NodeName}}"
|
||||||
|
vm_id = {{.TemplateVMID}}
|
||||||
|
started = false
|
||||||
|
cpu {
|
||||||
|
architecture = "x86_64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resource "proxmox_virtual_environment_vm" "clone" {
|
||||||
|
node_name = "{{.NodeName}}"
|
||||||
|
started = false
|
||||||
|
clone {
|
||||||
|
vm_id = proxmox_virtual_environment_vm.template.vm_id
|
||||||
|
}
|
||||||
|
}`, WithRootUser()),
|
||||||
|
}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
ProtoV6ProviderFactories: te.AccProviders,
|
||||||
|
Steps: tt.step,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -25,22 +25,20 @@ import (
|
|||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/fwprovider"
|
"github.com/bpg/terraform-provider-proxmox/fwprovider"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/access"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/access"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster"
|
|
||||||
sdkV2provider "github.com/bpg/terraform-provider-proxmox/proxmoxtf/provider"
|
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/storage"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/nodes/storage"
|
||||||
|
sdkV2provider "github.com/bpg/terraform-provider-proxmox/proxmoxtf/provider"
|
||||||
"github.com/bpg/terraform-provider-proxmox/utils"
|
"github.com/bpg/terraform-provider-proxmox/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Environment is a test environment for acceptance tests.
|
// Environment is a test environment for acceptance tests.
|
||||||
type Environment struct {
|
type Environment struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
templateVars map[string]any
|
templateVars map[string]any
|
||||||
providerConfig string
|
NodeName string
|
||||||
NodeName string
|
DatastoreID string
|
||||||
DatastoreID string
|
|
||||||
|
|
||||||
AccProviders map[string]func() (tfprotov6.ProviderServer, error)
|
AccProviders map[string]func() (tfprotov6.ProviderServer, error)
|
||||||
once sync.Once
|
once sync.Once
|
||||||
@ -49,6 +47,92 @@ type Environment struct {
|
|||||||
ContainerImagesServer string
|
ContainerImagesServer string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RenderConfigOption is a configuration option for rendering the provider configuration.
|
||||||
|
type RenderConfigOption interface {
|
||||||
|
apply(rc *renderConfig) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type renderConfig struct {
|
||||||
|
providerConfig string
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the ssh configuration section of the provider config.
|
||||||
|
func (r *renderConfig) ssh() string {
|
||||||
|
nodeName := utils.GetAnyStringEnv("PROXMOX_VE_ACC_NODE_NAME")
|
||||||
|
if nodeName == "" {
|
||||||
|
nodeName = "pve"
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeAddress := utils.GetAnyStringEnv("PROXMOX_VE_ACC_NODE_SSH_ADDRESS")
|
||||||
|
if nodeAddress == "" {
|
||||||
|
endpoint := utils.GetAnyStringEnv("PROXMOX_VE_ENDPOINT")
|
||||||
|
|
||||||
|
u, err := url.Parse(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeAddress = u.Hostname()
|
||||||
|
}
|
||||||
|
|
||||||
|
nodePort := utils.GetAnyStringEnv("PROXMOX_VE_ACC_NODE_SSH_PORT")
|
||||||
|
if nodePort == "" {
|
||||||
|
nodePort = "22"
|
||||||
|
}
|
||||||
|
|
||||||
|
// one indent level
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
ssh {
|
||||||
|
node {
|
||||||
|
name = "%s"
|
||||||
|
address = "%s"
|
||||||
|
port = %s
|
||||||
|
}
|
||||||
|
}`, nodeName, nodeAddress, nodePort)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRootUser returns a configuration option that sets the root user in the provider configuration.
|
||||||
|
func WithRootUser() RenderConfigOption {
|
||||||
|
return &rootUserConfigOption{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type rootUserConfigOption struct{}
|
||||||
|
|
||||||
|
func (o *rootUserConfigOption) apply(rc *renderConfig) error {
|
||||||
|
if utils.GetAnyStringEnv("PROXMOX_VE_USERNAME") == "" || utils.GetAnyStringEnv("PROXMOX_VE_PASSWORD") == "" {
|
||||||
|
return fmt.Errorf("PROXMOX_VE_USERNAME and PROXMOX_VE_PASSWORD must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
rootUser := fmt.Sprintf("\tusername = \"%s\"\n\tpassword = \"%s\"\n\tapi_token = \"\"",
|
||||||
|
utils.GetAnyStringEnv("PROXMOX_VE_USERNAME"),
|
||||||
|
utils.GetAnyStringEnv("PROXMOX_VE_PASSWORD"),
|
||||||
|
)
|
||||||
|
|
||||||
|
rc.providerConfig = fmt.Sprintf("provider \"proxmox\" {\n%s\n%s\n}", rootUser, rc.ssh())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAPIToken returns a configuration option that sets the API token in the provider configuration.
|
||||||
|
func WithAPIToken() RenderConfigOption {
|
||||||
|
return &apiTokenConfigOption{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type apiTokenConfigOption struct{}
|
||||||
|
|
||||||
|
func (o *apiTokenConfigOption) apply(rc *renderConfig) error {
|
||||||
|
if utils.GetAnyStringEnv("PROXMOX_VE_API_TOKEN") == "" {
|
||||||
|
return fmt.Errorf("PROXMOX_VE_API_TOKEN must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
apiToken := fmt.Sprintf("\tapi_token = \"%s\"\n\tusername = \"\"\n\tpassword = \"\"",
|
||||||
|
utils.GetAnyStringEnv("PROXMOX_VE_API_TOKEN"))
|
||||||
|
|
||||||
|
rc.providerConfig = fmt.Sprintf("provider \"proxmox\" {\n%s\n%s\n}", apiToken, rc.ssh())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// InitEnvironment initializes a new test environment for acceptance tests.
|
// InitEnvironment initializes a new test environment for acceptance tests.
|
||||||
func InitEnvironment(t *testing.T) *Environment {
|
func InitEnvironment(t *testing.T) *Environment {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@ -58,33 +142,6 @@ func InitEnvironment(t *testing.T) *Environment {
|
|||||||
nodeName = "pve"
|
nodeName = "pve"
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeAddress := utils.GetAnyStringEnv("PROXMOX_VE_ACC_NODE_SSH_ADDRESS")
|
|
||||||
if nodeAddress == "" {
|
|
||||||
endpoint := utils.GetAnyStringEnv("PROXMOX_VE_ENDPOINT")
|
|
||||||
u, err := url.Parse(endpoint)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
nodeAddress = u.Hostname()
|
|
||||||
}
|
|
||||||
|
|
||||||
nodePort := utils.GetAnyStringEnv("PROXMOX_VE_ACC_NODE_SSH_PORT")
|
|
||||||
if nodePort == "" {
|
|
||||||
nodePort = "22"
|
|
||||||
}
|
|
||||||
|
|
||||||
pc := fmt.Sprintf(`
|
|
||||||
provider "proxmox" {
|
|
||||||
ssh {
|
|
||||||
node {
|
|
||||||
name = "%s"
|
|
||||||
address = "%s"
|
|
||||||
port = %s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//random_vm_ids = true
|
|
||||||
}
|
|
||||||
`, nodeName, nodeAddress, nodePort)
|
|
||||||
|
|
||||||
const datastoreID = "local"
|
const datastoreID = "local"
|
||||||
|
|
||||||
cloudImagesServer := utils.GetAnyStringEnv("PROXMOX_VE_ACC_CLOUD_IMAGES_SERVER")
|
cloudImagesServer := utils.GetAnyStringEnv("PROXMOX_VE_ACC_CLOUD_IMAGES_SERVER")
|
||||||
@ -100,18 +157,17 @@ provider "proxmox" {
|
|||||||
return &Environment{
|
return &Environment{
|
||||||
t: t,
|
t: t,
|
||||||
templateVars: map[string]any{
|
templateVars: map[string]any{
|
||||||
"ProviderConfig": pc,
|
|
||||||
"NodeName": nodeName,
|
"NodeName": nodeName,
|
||||||
"DatastoreID": datastoreID,
|
"DatastoreID": datastoreID,
|
||||||
"CloudImagesServer": cloudImagesServer,
|
"CloudImagesServer": cloudImagesServer,
|
||||||
"ContainerImagesServer": containerImagesServer,
|
"ContainerImagesServer": containerImagesServer,
|
||||||
},
|
},
|
||||||
providerConfig: pc,
|
|
||||||
NodeName: nodeName,
|
NodeName: nodeName,
|
||||||
DatastoreID: datastoreID,
|
DatastoreID: datastoreID,
|
||||||
AccProviders: muxProviders(t),
|
|
||||||
CloudImagesServer: cloudImagesServer,
|
CloudImagesServer: cloudImagesServer,
|
||||||
ContainerImagesServer: containerImagesServer,
|
ContainerImagesServer: containerImagesServer,
|
||||||
|
|
||||||
|
AccProviders: muxProviders(t),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,18 +181,32 @@ func (e *Environment) AddTemplateVars(vars map[string]any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RenderConfig renders the given configuration with for the current test environment using template engine.
|
// RenderConfig renders the given configuration with for the current test environment using template engine.
|
||||||
func (e *Environment) RenderConfig(cfg string) string {
|
func (e *Environment) RenderConfig(cfg string, opt ...RenderConfigOption) string {
|
||||||
tmpl, err := template.New("config").Parse("{{.ProviderConfig}}" + cfg)
|
if len(opt) == 0 {
|
||||||
|
opt = append(opt, WithAPIToken())
|
||||||
|
}
|
||||||
|
|
||||||
|
rc := &renderConfig{}
|
||||||
|
for _, o := range opt {
|
||||||
|
err := o.apply(rc)
|
||||||
|
require.NoError(e.t, err, "configuration error")
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.New("config").Parse(cfg)
|
||||||
require.NoError(e.t, err)
|
require.NoError(e.t, err)
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = tmpl.Execute(&buf, e.templateVars)
|
err = tmpl.Execute(&buf, e.templateVars)
|
||||||
require.NoError(e.t, err)
|
require.NoError(e.t, err)
|
||||||
|
|
||||||
return buf.String()
|
return rc.providerConfig + "\n" + buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client returns a new API client for the test environment.
|
// Client returns a new API client for the test environment.
|
||||||
|
// The client will be using the credentials from the environment variables, in precedence order:
|
||||||
|
// 1. API token
|
||||||
|
// 2. Ticket
|
||||||
|
// 3. User credentials.
|
||||||
func (e *Environment) Client() api.Client {
|
func (e *Environment) Client() api.Client {
|
||||||
if e.c == nil {
|
if e.c == nil {
|
||||||
e.once.Do(
|
e.once.Do(
|
||||||
@ -188,43 +258,29 @@ func (e *Environment) ClusterClient() *cluster.Client {
|
|||||||
return &cluster.Client{Client: e.Client()}
|
return &cluster.Client{Client: e.Client()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// testAccMuxProviders returns a map of mux servers for the acceptance tests.
|
// muxProviders returns a map of mux servers for the acceptance tests.
|
||||||
func muxProviders(t *testing.T) map[string]func() (tfprotov6.ProviderServer, error) {
|
func muxProviders(t *testing.T) map[string]func() (tfprotov6.ProviderServer, error) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
ctx := context.Background()
|
// Init mux servers
|
||||||
|
return map[string]func() (tfprotov6.ProviderServer, error){
|
||||||
|
"proxmox": func() (tfprotov6.ProviderServer, error) {
|
||||||
|
return tf6muxserver.NewMuxServer(context.Background(),
|
||||||
|
providerserver.NewProtocol6(fwprovider.New("test")()),
|
||||||
|
func() tfprotov6.ProviderServer {
|
||||||
|
sdkV2Provider, err := tf5to6server.UpgradeServer(
|
||||||
|
context.Background(),
|
||||||
|
func() tfprotov5.ProviderServer {
|
||||||
|
return schema.NewGRPCProviderServer(
|
||||||
|
sdkV2provider.ProxmoxVirtualEnvironment(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Init sdkV2 provider
|
return sdkV2Provider
|
||||||
sdkV2Provider, err := tf5to6server.UpgradeServer(
|
},
|
||||||
ctx,
|
|
||||||
func() tfprotov5.ProviderServer {
|
|
||||||
return schema.NewGRPCProviderServer(
|
|
||||||
sdkV2provider.ProxmoxVirtualEnvironment(),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Init framework provider
|
|
||||||
frameworkProvider := fwprovider.New("test")()
|
|
||||||
|
|
||||||
providers := []func() tfprotov6.ProviderServer{
|
|
||||||
providerserver.NewProtocol6(frameworkProvider),
|
|
||||||
func() tfprotov6.ProviderServer {
|
|
||||||
return sdkV2Provider
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init mux servers
|
|
||||||
muxServers := map[string]func() (tfprotov6.ProviderServer, error){
|
|
||||||
"proxmox": func() (tfprotov6.ProviderServer, error) {
|
|
||||||
muxServer, e := tf6muxserver.NewMuxServer(ctx, providers...)
|
|
||||||
if e != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create mux server: %w", e)
|
|
||||||
}
|
|
||||||
return muxServer, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return muxServers
|
|
||||||
}
|
}
|
||||||
|
@ -16,11 +16,11 @@ import (
|
|||||||
// is configured to use the root user.
|
// is configured to use the root user.
|
||||||
type Authenticator interface {
|
type Authenticator interface {
|
||||||
// IsRoot returns true if the authenticator is configured to use the root
|
// IsRoot returns true if the authenticator is configured to use the root
|
||||||
IsRoot() bool
|
IsRoot(ctx context.Context) bool
|
||||||
|
|
||||||
// IsRootTicket returns true if the authenticator is configured to use the root directly using a login ticket.
|
// IsRootTicket returns true if the authenticator is configured to use the root directly using a login ticket.
|
||||||
// (root using token is weaker, cannot change VM arch)
|
// (root using token is weaker, cannot change VM arch)
|
||||||
IsRootTicket() bool
|
IsRootTicket(ctx context.Context) bool
|
||||||
|
|
||||||
// AuthenticateRequest adds authentication data to a new request.
|
// AuthenticateRequest adds authentication data to a new request.
|
||||||
AuthenticateRequest(ctx context.Context, req *http.Request) error
|
AuthenticateRequest(ctx context.Context, req *http.Request) error
|
||||||
|
@ -48,11 +48,11 @@ type Client interface {
|
|||||||
ExpandPath(path string) string
|
ExpandPath(path string) string
|
||||||
|
|
||||||
// IsRoot returns true if the client is configured with the root user.
|
// IsRoot returns true if the client is configured with the root user.
|
||||||
IsRoot() bool
|
IsRoot(ctx context.Context) bool
|
||||||
|
|
||||||
// IsRootTicket returns true if the authenticator is configured to use the root directly using a login ticket.
|
// IsRootTicket returns true if the authenticator is configured to use the root directly using a login ticket.
|
||||||
// (root using token is weaker, cannot change VM arch)
|
// (root using token is weaker, cannot change VM arch)
|
||||||
IsRootTicket() bool
|
IsRootTicket(ctx context.Context) bool
|
||||||
|
|
||||||
// HTTP returns a lower-level HTTP client.
|
// HTTP returns a lower-level HTTP client.
|
||||||
HTTP() *http.Client
|
HTTP() *http.Client
|
||||||
@ -313,12 +313,12 @@ func (c *client) ExpandPath(path string) string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) IsRoot() bool {
|
func (c *client) IsRoot(ctx context.Context) bool {
|
||||||
return c.auth.IsRoot()
|
return c.auth.IsRoot(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) IsRootTicket() bool {
|
func (c *client) IsRootTicket(ctx context.Context) bool {
|
||||||
return c.auth.IsRootTicket()
|
return c.auth.IsRootTicket(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) HTTP() *http.Client {
|
func (c *client) HTTP() *http.Client {
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* 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 api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -29,11 +35,11 @@ func newTestClient(fn RoundTripFunc) *http.Client {
|
|||||||
|
|
||||||
type dummyAuthenticator struct{}
|
type dummyAuthenticator struct{}
|
||||||
|
|
||||||
func (dummyAuthenticator) IsRoot() bool {
|
func (dummyAuthenticator) IsRoot(_ context.Context) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dummyAuthenticator) IsRootTicket() bool {
|
func (dummyAuthenticator) IsRootTicket(context.Context) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,12 +40,12 @@ func NewTicketAuthenticator(creds TicketCredentials) (Authenticator, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ticketAuthenticator) IsRoot() bool {
|
func (t *ticketAuthenticator) IsRoot(_ context.Context) bool {
|
||||||
return t.authData != nil && t.authData.Username == rootUsername
|
return t.authData != nil && t.authData.Username == rootUsername
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *ticketAuthenticator) IsRootTicket() bool {
|
func (t *ticketAuthenticator) IsRootTicket(ctx context.Context) bool {
|
||||||
return t.IsRoot()
|
return t.IsRoot(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthenticateRequest adds authentication data to a new request.
|
// AuthenticateRequest adds authentication data to a new request.
|
||||||
|
@ -26,11 +26,11 @@ func NewTokenAuthenticator(toc TokenCredentials) (Authenticator, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tokenAuthenticator) IsRoot() bool {
|
func (t *tokenAuthenticator) IsRoot(_ context.Context) bool {
|
||||||
return t.username == rootUsername
|
return t.username == rootUsername
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tokenAuthenticator) IsRootTicket() bool {
|
func (t *tokenAuthenticator) IsRootTicket(_ context.Context) bool {
|
||||||
// Logged using a token, therefore not a ticket login
|
// Logged using a token, therefore not a ticket login
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -121,12 +121,22 @@ func (t *userAuthenticator) authenticate(ctx context.Context) (*AuthenticationRe
|
|||||||
return resBody.Data, nil
|
return resBody.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *userAuthenticator) IsRoot() bool {
|
func (t *userAuthenticator) IsRoot(ctx context.Context) bool {
|
||||||
|
if t.authData == nil {
|
||||||
|
if _, err := t.authenticate(ctx); err != nil {
|
||||||
|
tflog.Warn(ctx, "Failed to authenticate while checking root status", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return t.authData != nil && t.authData.Username == rootUsername
|
return t.authData != nil && t.authData.Username == rootUsername
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *userAuthenticator) IsRootTicket() bool {
|
func (t *userAuthenticator) IsRootTicket(ctx context.Context) bool {
|
||||||
return t.IsRoot()
|
return t.IsRoot(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthenticateRequest adds authentication data to a new request.
|
// AuthenticateRequest adds authentication data to a new request.
|
||||||
|
@ -34,7 +34,7 @@ func ProxmoxVirtualEnvironment() *schema.Provider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
|
func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
var diags diag.Diagnostics
|
var diags diag.Diagnostics
|
||||||
@ -47,6 +47,8 @@ func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{},
|
|||||||
|
|
||||||
var conn *api.Connection
|
var conn *api.Connection
|
||||||
|
|
||||||
|
tflog.Info(ctx, "Configuring the SDK Proxmox provider...")
|
||||||
|
|
||||||
// Check environment variables
|
// Check environment variables
|
||||||
endpoint := utils.GetAnyStringEnv("PROXMOX_VE_ENDPOINT", "PM_VE_ENDPOINT")
|
endpoint := utils.GetAnyStringEnv("PROXMOX_VE_ENDPOINT", "PM_VE_ENDPOINT")
|
||||||
insecure := utils.GetAnyBoolEnv("PROXMOX_VE_INSECURE", "PM_VE_INSECURE")
|
insecure := utils.GetAnyBoolEnv("PROXMOX_VE_INSECURE", "PM_VE_INSECURE")
|
||||||
@ -78,7 +80,8 @@ func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{},
|
|||||||
csrfPreventionToken = v.(string)
|
csrfPreventionToken = v.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, ok := d.GetOk(mkProviderAPIToken); ok {
|
//nolint:staticcheck //using GetOkExists to check if the value is set, as it can be an empty string in tests
|
||||||
|
if v, ok := d.GetOkExists(mkProviderAPIToken); ok {
|
||||||
apiToken = v.(string)
|
apiToken = v.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,11 +89,13 @@ func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{},
|
|||||||
otp = v.(string)
|
otp = v.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, ok := d.GetOk(mkProviderUsername); ok {
|
///nolint:staticcheck //using GetOkExists to check if the value is set, as it can be an empty string in tests
|
||||||
|
if v, ok := d.GetOkExists(mkProviderUsername); ok {
|
||||||
username = v.(string)
|
username = v.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, ok := d.GetOk(mkProviderPassword); ok {
|
//nolint:staticcheck //using GetOkExists to check if the value is set, as it can be an empty string in tests
|
||||||
|
if v, ok := d.GetOkExists(mkProviderPassword); ok {
|
||||||
password = v.(string)
|
password = v.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,11 +77,11 @@ func createSchema() map[string]*schema.Schema {
|
|||||||
ValidateFunc: validation.StringIsNotEmpty,
|
ValidateFunc: validation.StringIsNotEmpty,
|
||||||
},
|
},
|
||||||
mkProviderAPIToken: {
|
mkProviderAPIToken: {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Sensitive: true,
|
Sensitive: true,
|
||||||
Description: "The API token for the Proxmox VE API.",
|
Description: "The API token for the Proxmox VE API.",
|
||||||
ValidateFunc: validation.StringIsNotEmpty,
|
// note: we allow empty string as a valid value, as it is used to unset the token in tests
|
||||||
},
|
},
|
||||||
mkProviderOTP: {
|
mkProviderOTP: {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
@ -91,17 +91,17 @@ func createSchema() map[string]*schema.Schema {
|
|||||||
"Please use the `api_token` attribute instead.",
|
"Please use the `api_token` attribute instead.",
|
||||||
},
|
},
|
||||||
mkProviderUsername: {
|
mkProviderUsername: {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Description: "The username for the Proxmox VE API.",
|
Description: "The username for the Proxmox VE API.",
|
||||||
ValidateFunc: validation.StringIsNotEmpty,
|
// note: we allow empty string as a valid value, as it is used to unset the username in tests
|
||||||
},
|
},
|
||||||
mkProviderPassword: {
|
mkProviderPassword: {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Sensitive: true,
|
Sensitive: true,
|
||||||
Description: "The password for the Proxmox VE API.",
|
Description: "The password for the Proxmox VE API.",
|
||||||
ValidateFunc: validation.StringIsNotEmpty,
|
// note: we allow empty string as a valid value, as it is used to unset the password in tests
|
||||||
},
|
},
|
||||||
mkProviderSSH: {
|
mkProviderSSH: {
|
||||||
Type: schema.TypeList,
|
Type: schema.TypeList,
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
|
||||||
|
|
||||||
|
"github.com/bpg/terraform-provider-proxmox/proxmox"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/cluster"
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/helpers/ptr"
|
||||||
@ -1531,6 +1532,10 @@ func VM() *schema.Resource {
|
|||||||
customdiff.ForceNewIf(
|
customdiff.ForceNewIf(
|
||||||
mkVMID,
|
mkVMID,
|
||||||
func(_ context.Context, d *schema.ResourceDiff, _ interface{}) bool {
|
func(_ context.Context, d *schema.ResourceDiff, _ interface{}) bool {
|
||||||
|
if !d.HasChange(mkVMID) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
newValue := d.Get(mkVMID)
|
newValue := d.Get(mkVMID)
|
||||||
|
|
||||||
// 'vm_id' is ForceNew, except when changing 'vm_id' to existing correct id
|
// 'vm_id' is ForceNew, except when changing 'vm_id' to existing correct id
|
||||||
@ -1541,6 +1546,10 @@ func VM() *schema.Resource {
|
|||||||
customdiff.ForceNewIf(
|
customdiff.ForceNewIf(
|
||||||
mkNodeName,
|
mkNodeName,
|
||||||
func(_ context.Context, d *schema.ResourceDiff, _ interface{}) bool {
|
func(_ context.Context, d *schema.ResourceDiff, _ interface{}) bool {
|
||||||
|
if !d.HasChange(mkNodeName) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return !d.Get(mkMigrate).(bool)
|
return !d.Get(mkMigrate).(bool)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -1945,9 +1954,8 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
|
|||||||
cpuFlagsConverted[fi] = flag.(string)
|
cpuFlagsConverted[fi] = flag.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only the root account is allowed to change the CPU architecture, which makes this check necessary.
|
if err := setCPUArchitecture(ctx, cpuArchitecture, client, updateBody); err != nil {
|
||||||
if client.API().IsRootTicket() && cpuArchitecture != "" {
|
return diag.FromErr(err)
|
||||||
updateBody.CPUArchitecture = &cpuArchitecture
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBody.CPUCores = ptr.Ptr(int64(cpuCores))
|
updateBody.CPUCores = ptr.Ptr(int64(cpuCores))
|
||||||
@ -2295,6 +2303,25 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
|
|||||||
return vmCreateStart(ctx, d, m)
|
return vmCreateStart(ctx, d, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setCPUArchitecture(
|
||||||
|
ctx context.Context,
|
||||||
|
cpuArchitecture string,
|
||||||
|
client proxmox.Client,
|
||||||
|
updateBody *vms.UpdateRequestBody,
|
||||||
|
) error {
|
||||||
|
// Only the root account is allowed to change the CPU architecture.
|
||||||
|
if cpuArchitecture != "" {
|
||||||
|
if client.API().IsRootTicket(ctx) {
|
||||||
|
updateBody.CPUArchitecture = &cpuArchitecture
|
||||||
|
} else {
|
||||||
|
return errors.New("the `cpu.architecture` can only be set by the root account. " +
|
||||||
|
"Please switch to the root account or remove the `cpu.architecture` from the VM configuration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
|
func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
|
||||||
createTimeoutSec := d.Get(mkTimeoutCreate).(int)
|
createTimeoutSec := d.Get(mkTimeoutCreate).(int)
|
||||||
|
|
||||||
@ -2672,9 +2699,8 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
|
|||||||
CustomStorageDevices: diskDeviceObjects,
|
CustomStorageDevices: diskDeviceObjects,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only the root account is allowed to change the CPU architecture, which makes this check necessary.
|
if err = setCPUArchitecture(ctx, cpuArchitecture, client, createBody); err != nil {
|
||||||
if client.API().IsRootTicket() && cpuArchitecture != "" {
|
return diag.FromErr(err)
|
||||||
createBody.CPUArchitecture = &cpuArchitecture
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cpuHotplugged > 0 {
|
if cpuHotplugged > 0 {
|
||||||
@ -3570,12 +3596,7 @@ func vmReadCustom(
|
|||||||
cpu[mkCPUArchitecture] = *vmConfig.CPUArchitecture
|
cpu[mkCPUArchitecture] = *vmConfig.CPUArchitecture
|
||||||
} else {
|
} else {
|
||||||
// Default value of "arch" is "" according to the API documentation.
|
// Default value of "arch" is "" according to the API documentation.
|
||||||
// However, assume the provider's default value as a workaround when the root account is not being used.
|
cpu[mkCPUArchitecture] = ""
|
||||||
if !client.API().IsRootTicket() {
|
|
||||||
cpu[mkCPUArchitecture] = dvCPUArchitecture
|
|
||||||
} else {
|
|
||||||
cpu[mkCPUArchitecture] = ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if vmConfig.CPUCores != nil {
|
if vmConfig.CPUCores != nil {
|
||||||
@ -4987,9 +5008,8 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
|
|||||||
cpuUnits := cpuBlock[mkCPUUnits].(int)
|
cpuUnits := cpuBlock[mkCPUUnits].(int)
|
||||||
cpuAffinity := cpuBlock[mkCPUAffinity].(string)
|
cpuAffinity := cpuBlock[mkCPUAffinity].(string)
|
||||||
|
|
||||||
// Only the root account is allowed to change the CPU architecture, which makes this check necessary.
|
if err = setCPUArchitecture(ctx, cpuArchitecture, client, updateBody); err != nil {
|
||||||
if client.API().IsRootTicket() && cpuArchitecture != "" {
|
return diag.FromErr(err)
|
||||||
updateBody.CPUArchitecture = &cpuArchitecture
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBody.CPUCores = ptr.Ptr(int64(cpuCores))
|
updateBody.CPUCores = ptr.Ptr(int64(cpuCores))
|
||||||
|
2
testacc
2
testacc
@ -7,4 +7,4 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
# shellcheck disable=SC2046
|
# shellcheck disable=SC2046
|
||||||
TF_ACC=1 env $(xargs < testacc.env) go test -v -count 1 --tags=acceptance -timeout 360s -run "$1" github.com/bpg/terraform-provider-proxmox/fwprovider/... $2
|
TF_ACC=1 env $(xargs < testacc.env) go test -count 1 --tags=acceptance -timeout 360s -run "$1" github.com/bpg/terraform-provider-proxmox/fwprovider/... $2
|
||||||
|
Loading…
Reference in New Issue
Block a user