mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-30 10:33:46 +00:00
fix(provider): ignore `ErrClosed` for fileReader Signed-off-by: Keith King <KingKeithC@gmail.com>
164 lines
3.7 KiB
Go
164 lines
3.7 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"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,
|
|
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 {
|
|
if errors.Is(e, os.ErrClosed) {
|
|
// We can ignore the error in the case that the file was already closed.
|
|
return
|
|
}
|
|
|
|
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)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error uploading file to datastore %s: failed waiting for upload - %w", c.StorageName, err)
|
|
}
|
|
|
|
return resBody, nil
|
|
}
|