0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-30 02:31:10 +00:00

feat(provider): configure temp directory (#607)

* feat(provider): configure temp directory

Resource 'proxmox_virtual_environment_file' often requires lot of disk
space in /tmp, which can be space-limited.

Instead of requiring to set TMPDIR environment variable before running
terraform, make it a provider configuration option.

Signed-off-by: Oto Petřík <oto.petrik@gmail.com>

* fix: lint error, align names in the `client` struct

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>

---------

Signed-off-by: Oto Petřík <oto.petrik@gmail.com>
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Oto Petřík 2023-10-08 03:00:34 +02:00 committed by GitHub
parent d36cf4eab8
commit 06ad00463c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 91 additions and 25 deletions

View File

@ -21,6 +21,7 @@ provider "proxmox" {
username = "root@pam" username = "root@pam"
password = "the-password-set-during-installation-of-proxmox-ve" password = "the-password-set-during-installation-of-proxmox-ve"
insecure = true insecure = true
tmp_dir = "/var/tmp"
} }
``` ```
@ -167,6 +168,15 @@ failed (changing feature flags for privileged container is only allowed for root
> when using API Token authentication, even when `Administrator` role or > when using API Token authentication, even when `Administrator` role or
> the `root@pam` user is used with the token. > the `root@pam` user is used with the token.
### Temporary directory
Using `proxmox_virtual_environment_file` with `.iso` files or disk images can require
large amount of space in the temporary directory of the computer running terraform.
Consider pointing `tmp_dir` to a directory with enough space, especially if the default
temporary directory is limited by the system memory (e.g. `tmpfs` mounted
on `/tmp`).
## Argument Reference ## Argument Reference
In addition In addition
@ -209,3 +219,4 @@ Proxmox `provider` block:
- `name` - (Required) The name of the node. - `name` - (Required) The name of the node.
- `address` - (Required) The IP address of the node. - `address` - (Required) The IP address of the node.
- `port` - (Optional) SSH port of the node. Defaults to 22. - `port` - (Optional) SSH port of the node. Defaults to 22.
- `tmp_dir` - (Optional) Use custom temporary directory. (can also be sourced from `PROXMOX_VE_TMPDIR`)

View File

@ -70,6 +70,7 @@ type proxmoxProviderModel struct {
Port types.Int64 `tfsdk:"port"` Port types.Int64 `tfsdk:"port"`
} `tfsdk:"node"` } `tfsdk:"node"`
} `tfsdk:"ssh"` } `tfsdk:"ssh"`
TmpDir types.String `tfsdk:"tmp_dir"`
} }
func (p *proxmoxProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) { func (p *proxmoxProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) {
@ -119,6 +120,10 @@ func (p *proxmoxProvider) Schema(_ context.Context, _ provider.SchemaRequest, re
Description: "The username for the Proxmox VE API.", Description: "The username for the Proxmox VE API.",
Optional: true, Optional: true,
}, },
"tmp_dir": schema.StringAttribute{
Description: "The alternative temporary directory.",
Optional: true,
},
}, },
Blocks: map[string]schema.Block{ Blocks: map[string]schema.Block{
// have to define it as a list due to backwards compatibility // have to define it as a list due to backwards compatibility
@ -355,7 +360,14 @@ func (p *proxmoxProvider) Configure(
return return
} }
client := proxmox.NewClient(apiClient, sshClient) // Intentionally use 'PROXMOX_VE_TMPDIR' with 'TMP' instead of 'TEMP', to match os.TempDir's use of $TMPDIR
tmpDirOverride := utils.GetAnyStringEnv("PROXMOX_VE_TMPDIR", "PM_VE_TMPDIR")
if !config.TmpDir.IsNull() {
tmpDirOverride = config.TmpDir.ValueString()
}
client := proxmox.NewClient(apiClient, sshClient, tmpDirOverride)
resp.ResourceData = client resp.ResourceData = client
resp.DataSourceData = client resp.DataSourceData = client

View File

@ -7,6 +7,8 @@
package proxmox package proxmox
import ( import (
"os"
"github.com/bpg/terraform-provider-proxmox/proxmox/access" "github.com/bpg/terraform-provider-proxmox/proxmox/access"
"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"
@ -42,54 +44,67 @@ type Client interface {
// SSH returns a lower-level SSH client. // SSH returns a lower-level SSH client.
SSH() ssh.Client SSH() ssh.Client
// TempDir returns (possibly overridden) os.TempDir().
TempDir() string
} }
type client struct { type client struct {
a api.Client apiClient api.Client
s ssh.Client sshClient ssh.Client
tmpDirOverride string
} }
// NewClient creates a new API client. // NewClient creates a new API client.
func NewClient(a api.Client, s ssh.Client) Client { func NewClient(apiClient api.Client, sshClient ssh.Client, tmpDirOverride string) Client {
return &client{a: a, s: s} return &client{apiClient: apiClient, sshClient: sshClient, tmpDirOverride: tmpDirOverride}
} }
// Access returns a client for managing access control. // Access returns a client for managing access control.
func (c *client) Access() *access.Client { func (c *client) Access() *access.Client {
return &access.Client{Client: c.a} return &access.Client{Client: c.apiClient}
} }
// Cluster returns a client for managing the cluster. // Cluster returns a client for managing the cluster.
func (c *client) Cluster() *cluster.Client { func (c *client) Cluster() *cluster.Client {
return &cluster.Client{Client: c.a} return &cluster.Client{Client: c.apiClient}
} }
// Node returns a client for managing resources on a specific node. // Node returns a client for managing resources on a specific node.
func (c *client) Node(nodeName string) *nodes.Client { func (c *client) Node(nodeName string) *nodes.Client {
return &nodes.Client{Client: c.a, NodeName: nodeName} return &nodes.Client{Client: c.apiClient, NodeName: nodeName}
} }
// Pool returns a client for managing resource pools. // Pool returns a client for managing resource pools.
func (c *client) Pool() *pools.Client { func (c *client) Pool() *pools.Client {
return &pools.Client{Client: c.a} return &pools.Client{Client: c.apiClient}
} }
// Storage returns a client for managing storage. // Storage returns a client for managing storage.
func (c *client) Storage() *storage.Client { func (c *client) Storage() *storage.Client {
return &storage.Client{Client: c.a} return &storage.Client{Client: c.apiClient}
} }
// Version returns a client for getting the version of the Proxmox Virtual Environment API. // Version returns a client for getting the version of the Proxmox Virtual Environment API.
func (c *client) Version() *version.Client { func (c *client) Version() *version.Client {
return &version.Client{Client: c.a} return &version.Client{Client: c.apiClient}
} }
// API returns a lower-lever REST API client. // API returns a lower-lever REST API client.
func (c *client) API() api.Client { func (c *client) API() api.Client {
return c.a return c.apiClient
} }
// SSH returns a lower-lever SSH client.s. // SSH returns a lower-lever SSH client.
func (c *client) SSH() ssh.Client { func (c *client) SSH() ssh.Client {
return c.s return c.sshClient
}
// TempDir returns (possibly overridden) os.TempDir().
func (c *client) TempDir() string {
if c.tmpDirOverride != "" {
return c.tmpDirOverride
}
return os.TempDir()
} }

View File

@ -145,6 +145,7 @@ func (c *Client) APIUpload(
datastoreID string, datastoreID string,
d *api.FileUploadRequest, d *api.FileUploadRequest,
uploadTimeout int, uploadTimeout int,
tempDir string,
) (*DatastoreUploadResponseBody, error) { ) (*DatastoreUploadResponseBody, error) {
tflog.Debug(ctx, "uploading file to datastore using PVE API", map[string]interface{}{ tflog.Debug(ctx, "uploading file to datastore using PVE API", map[string]interface{}{
"file_name": d.FileName, "file_name": d.FileName,
@ -205,7 +206,7 @@ func (c *Client) APIUpload(
// We need to store the multipart content in a temporary file to avoid using high amounts of memory. // We need to store the multipart content in a temporary file to avoid using high amounts of memory.
// This is necessary due to Proxmox VE not supporting chunked transfers in v6.1 and earlier versions. // This is necessary due to Proxmox VE not supporting chunked transfers in v6.1 and earlier versions.
tempMultipartFile, err := os.CreateTemp("", "multipart") tempMultipartFile, err := os.CreateTemp(tempDir, "multipart")
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create temporary file: %w", err) return nil, fmt.Errorf("failed to create temporary file: %w", err)
} }

View File

@ -8,6 +8,7 @@ package proxmoxtf
import ( import (
"errors" "errors"
"os"
"github.com/bpg/terraform-provider-proxmox/proxmox" "github.com/bpg/terraform-provider-proxmox/proxmox"
"github.com/bpg/terraform-provider-proxmox/proxmox/api" "github.com/bpg/terraform-provider-proxmox/proxmox/api"
@ -16,18 +17,21 @@ import (
// ProviderConfiguration is the configuration for the provider. // ProviderConfiguration is the configuration for the provider.
type ProviderConfiguration struct { type ProviderConfiguration struct {
apiClient api.Client apiClient api.Client
sshClient ssh.Client sshClient ssh.Client
tmpDirOverride string
} }
// NewProviderConfiguration creates a new provider configuration. // NewProviderConfiguration creates a new provider configuration.
func NewProviderConfiguration( func NewProviderConfiguration(
apiClient api.Client, apiClient api.Client,
sshClient ssh.Client, sshClient ssh.Client,
tmpDirOverride string,
) ProviderConfiguration { ) ProviderConfiguration {
return ProviderConfiguration{ return ProviderConfiguration{
apiClient: apiClient, apiClient: apiClient,
sshClient: sshClient, sshClient: sshClient,
tmpDirOverride: tmpDirOverride,
} }
} }
@ -45,5 +49,14 @@ func (c *ProviderConfiguration) GetClient() (proxmox.Client, error) {
) )
} }
return proxmox.NewClient(c.apiClient, c.sshClient), nil return proxmox.NewClient(c.apiClient, c.sshClient, c.tmpDirOverride), nil
}
// TempDir returns (possibly overridden) os.TempDir().
func (c *ProviderConfiguration) TempDir() string {
if c.tmpDirOverride != "" {
return c.tmpDirOverride
}
return os.TempDir()
} }

View File

@ -155,7 +155,14 @@ func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{},
return nil, diag.Errorf("error creating SSH client: %s", err) return nil, diag.Errorf("error creating SSH client: %s", err)
} }
config := proxmoxtf.NewProviderConfiguration(apiClient, sshClient) // Intentionally use 'PROXMOX_VE_TMPDIR' with 'TMP' instead of 'TEMP', to match os.TempDir's use of $TMPDIR
tmpDirOverride := utils.GetAnyStringEnv("PROXMOX_VE_TMPDIR", "PM_VE_TMPDIR")
if v, ok := d.GetOk(mkProviderTmpDir); ok {
tmpDirOverride = v.(string)
}
config := proxmoxtf.NewProviderConfiguration(apiClient, sshClient, tmpDirOverride)
return config, nil return config, nil
} }

View File

@ -22,6 +22,7 @@ const (
mkProviderPassword = "password" mkProviderPassword = "password"
mkProviderUsername = "username" mkProviderUsername = "username"
mkProviderAPIToken = "api_token" mkProviderAPIToken = "api_token"
mkProviderTmpDir = "tmp_dir"
mkProviderSSH = "ssh" mkProviderSSH = "ssh"
mkProviderSSHUsername = "username" mkProviderSSHUsername = "username"
mkProviderSSHPassword = "password" mkProviderSSHPassword = "password"
@ -169,5 +170,11 @@ func createSchema() map[string]*schema.Schema {
}, },
}, },
}, },
mkProviderTmpDir: {
Type: schema.TypeString,
Optional: true,
Description: "The alternative temporary directory.",
ValidateFunc: validation.StringIsNotEmpty,
},
} }
} }

View File

@ -395,7 +395,7 @@ func fileCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag
defer utils.CloseOrLogError(ctx)(res.Body) defer utils.CloseOrLogError(ctx)(res.Body)
tempDownloadedFile, err := os.CreateTemp("", "download") tempDownloadedFile, err := os.CreateTemp(config.TempDir(), "download")
if err != nil { if err != nil {
return diag.FromErr(err) return diag.FromErr(err)
} }
@ -471,8 +471,8 @@ func fileCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag
} }
} }
tempRawFile, err := os.CreateTemp("", "raw") tempRawFile, e := os.CreateTemp(config.TempDir(), "raw")
if err != nil { if e != nil {
return diag.FromErr(err) return diag.FromErr(err)
} }
@ -522,7 +522,7 @@ func fileCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag
switch *contentType { switch *contentType {
case "iso", "vztmpl": case "iso", "vztmpl":
uploadTimeout := d.Get(mkResourceVirtualEnvironmentFileTimeoutUpload).(int) uploadTimeout := d.Get(mkResourceVirtualEnvironmentFileTimeoutUpload).(int)
_, err = capi.Node(nodeName).APIUpload(ctx, datastoreID, request, uploadTimeout) _, err = capi.Node(nodeName).APIUpload(ctx, datastoreID, request, uploadTimeout, config.TempDir())
default: default:
// For all other content types, we need to upload the file to the node's // For all other content types, we need to upload the file to the node's
// datastore using SFTP. // datastore using SFTP.