0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-30 02:31:10 +00:00
terraform-provider-proxmox/proxmox/nodes/storage.go
Oto Petřík 06ad00463c
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>
2023-10-07 21:00:34 -04:00

292 lines
6.3 KiB
Go

/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package nodes
import (
"context"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"os"
"sort"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
)
// DeleteDatastoreFile deletes a file in a datastore.
func (c *Client) DeleteDatastoreFile(
ctx context.Context,
datastoreID, volumeID string,
) error {
err := c.DoRequest(
ctx,
http.MethodDelete,
c.ExpandPath(
fmt.Sprintf(
"storage/%s/content/%s",
url.PathEscape(datastoreID),
url.PathEscape(volumeID),
),
),
nil,
nil,
)
if err != nil {
return fmt.Errorf("error deleting file %s from datastore %s: %w", volumeID, datastoreID, err)
}
return nil
}
// GetDatastoreStatus gets status information for a given datastore.
func (c *Client) GetDatastoreStatus(
ctx context.Context,
datastoreID string,
) (*DatastoreGetStatusResponseData, error) {
resBody := &DatastoreGetStatusResponseBody{}
err := c.DoRequest(
ctx,
http.MethodGet,
c.ExpandPath(
fmt.Sprintf(
"storage/%s/status",
url.PathEscape(datastoreID),
),
),
nil,
resBody,
)
if err != nil {
return nil, fmt.Errorf("error retrieving status for datastore %s: %w", datastoreID, err)
}
if resBody.Data == nil {
return nil, api.ErrNoDataObjectInResponse
}
return resBody.Data, nil
}
// ListDatastoreFiles retrieves a list of the files in a datastore.
func (c *Client) ListDatastoreFiles(
ctx context.Context,
datastoreID string,
) ([]*DatastoreFileListResponseData, error) {
resBody := &DatastoreFileListResponseBody{}
err := c.DoRequest(
ctx,
http.MethodGet,
c.ExpandPath(
fmt.Sprintf(
"storage/%s/content",
url.PathEscape(datastoreID),
),
),
nil,
resBody,
)
if err != nil {
return nil, fmt.Errorf("error retrieving files from datastore %s: %w", datastoreID, err)
}
if resBody.Data == nil {
return nil, api.ErrNoDataObjectInResponse
}
sort.Slice(resBody.Data, func(i, j int) bool {
return resBody.Data[i].VolumeID < resBody.Data[j].VolumeID
})
return resBody.Data, nil
}
// ListDatastores retrieves a list of nodes.
func (c *Client) ListDatastores(
ctx context.Context,
d *DatastoreListRequestBody,
) ([]*DatastoreListResponseData, error) {
resBody := &DatastoreListResponseBody{}
err := c.DoRequest(
ctx,
http.MethodGet,
c.ExpandPath("storage"),
d,
resBody,
)
if err != nil {
return nil, fmt.Errorf("error retrieving datastores: %w", err)
}
if resBody.Data == nil {
return nil, api.ErrNoDataObjectInResponse
}
sort.Slice(resBody.Data, func(i, j int) bool {
return resBody.Data[i].ID < resBody.Data[j].ID
})
return resBody.Data, nil
}
// APIUpload uploads a file to a datastore using the Proxmox API.
func (c *Client) APIUpload(
ctx context.Context,
datastoreID string,
d *api.FileUploadRequest,
uploadTimeout int,
tempDir string,
) (*DatastoreUploadResponseBody, error) {
tflog.Debug(ctx, "uploading file to datastore using PVE API", map[string]interface{}{
"file_name": d.FileName,
"content_type": d.ContentType,
})
r, w := io.Pipe()
defer func(r *io.PipeReader) {
err := r.Close()
if err != nil {
tflog.Error(ctx, "failed to close pipe reader", map[string]interface{}{
"error": err,
})
}
}(r)
m := multipart.NewWriter(w)
go func() {
defer func(w *io.PipeWriter) {
err := w.Close()
if err != nil {
tflog.Error(ctx, "failed to close pipe writer", map[string]interface{}{
"error": err,
})
}
}(w)
defer func(m *multipart.Writer) {
err := m.Close()
if err != nil {
tflog.Error(ctx, "failed to close multipart writer", map[string]interface{}{
"error": err,
})
}
}(m)
err := m.WriteField("content", d.ContentType)
if err != nil {
tflog.Error(ctx, "failed to write 'content' field", map[string]interface{}{
"error": err,
})
return
}
part, err := m.CreateFormFile("filename", d.FileName)
if err != nil {
return
}
_, err = io.Copy(part, d.File)
if err != nil {
return
}
}()
// 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.
tempMultipartFile, err := os.CreateTemp(tempDir, "multipart")
if err != nil {
return nil, fmt.Errorf("failed to create temporary file: %w", err)
}
tempMultipartFileName := tempMultipartFile.Name()
_, err = io.Copy(tempMultipartFile, r)
if err != nil {
return nil, fmt.Errorf("failed to copy multipart data to temporary file: %w", err)
}
err = tempMultipartFile.Close()
if err != nil {
return nil, fmt.Errorf("failed to close temporary file: %w", err)
}
defer func(name string) {
e := os.Remove(name)
if e != nil {
tflog.Error(ctx, "failed to remove temporary file", map[string]interface{}{
"error": e,
})
}
}(tempMultipartFileName)
// Now that the multipart data is stored in a file, we can go ahead and do an HTTP POST request.
fileReader, err := os.Open(tempMultipartFileName)
if err != nil {
return nil, fmt.Errorf("failed to open temporary file: %w", err)
}
defer func(fileReader *os.File) {
e := fileReader.Close()
if e != nil {
tflog.Error(ctx, "failed to close file reader", map[string]interface{}{
"error": e,
})
}
}(fileReader)
fileInfo, err := fileReader.Stat()
if err != nil {
return nil, fmt.Errorf("failed to get file info: %w", err)
}
fileSize := fileInfo.Size()
reqBody := &api.MultiPartData{
Boundary: m.Boundary(),
Reader: fileReader,
Size: &fileSize,
}
resBody := &DatastoreUploadResponseBody{}
err = c.DoRequest(
ctx,
http.MethodPost,
c.ExpandPath(
fmt.Sprintf(
"storage/%s/upload",
url.PathEscape(datastoreID),
),
),
reqBody,
resBody,
)
if err != nil {
return nil, fmt.Errorf("error uploading file to datastore %s: %w", datastoreID, err)
}
if resBody.UploadID == nil {
return nil, fmt.Errorf("error uploading file to datastore %s: no uploadID", datastoreID)
}
err = c.Tasks().WaitForTask(ctx, *resBody.UploadID, uploadTimeout, 5)
if err != nil {
return nil, fmt.Errorf("error uploading file to datastore %s: failed waiting for upload - %w", datastoreID, err)
}
return resBody, nil
}