0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-07-02 03:22:59 +00:00
terraform-provider-proxmox/proxmox/nodes/storage/upload.go
Rafał Safin 58347c09fe
feat(storage): add new resource proxmox_virtual_environment_download_file (#837)
* feat(core): add query url metadata endpoint to nodes client api

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* feat(core): move storage api code to nodestorage folder

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* feat(core): add download url api

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* feat(storage): add  resource_download_file

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* feat(storage): finish new resource_download_file

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* feat(storage): generate docs for new download file resource, update other docs and examples

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* feat(storage): add basic acc tests for resource download_file

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* fix(test): lint new test file

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* feat(storage): warning instead of error when file already exists on resource download file

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* feat(storage): better validation in resource download file, delete upload task on error

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* feat(storage): better err message in resource download file

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* fix(core): removed unnecessary toint in custom type bool

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* fix(storage): typo in resource download file error

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* feat(storage): download file resource review - 1

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* feat(storage): finish resource download file after review

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* fix(storage): error instead of warnings in parse int errors in sizeRequiresReplaceModifier

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* fix(docs): remove unwanted changes in virtual_environment_file.md

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* fix(test): fix download file base acceptance tests

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* fix(test): fix resource download file acc tests

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* fix(test): last fix resource download file acc test

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>

* fix: use PVE-compatible jammy LXC image, fix few typos

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

---------

Signed-off-by: Rafał Safin <rafal.safin@rafsaf.pl>
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
Co-authored-by: Rafał Safin <rafal.safin@rafsaf.pl>
Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
2024-01-02 21:29:13 -05:00

160 lines
3.6 KiB
Go

package storage
import (
"context"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
)
// APIUpload uploads a file to a datastore using the Proxmox API.
func (c *Client) APIUpload(
ctx context.Context,
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("upload"),
reqBody,
resBody,
)
if err != nil {
return nil, fmt.Errorf("error uploading file to datastore %s: %w", c.StorageName, err)
}
if resBody.UploadID == nil {
return nil, fmt.Errorf("error uploading file to datastore %s: no uploadID", c.StorageName)
}
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", c.StorageName, err)
}
return resBody, nil
}