0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-07-04 12:32:59 +00:00

fix(file): SSH file upload on Windows (#308)

* fix(file): SSH file upload on Windows

* update bug report template
This commit is contained in:
Pavel Boldyrev 2023-04-19 22:14:48 -04:00 committed by GitHub
parent 20aacc2547
commit 7c9505d11f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 70 additions and 16 deletions

View File

@ -12,10 +12,14 @@ A clear and concise description of what the bug is.
**To Reproduce** **To Reproduce**
Steps to reproduce the behavior: Steps to reproduce the behavior:
1. Go to '...' 1. Create a resource '....'
2. Click on '....' 2. Run '....'
3. Scroll down to '....' 3. See error
4. See error 4. Modify the resource '....'
5. Run '....'
6. See error
Please also provide a minimal Terraform configuration that reproduces the issue.
**Expected behavior** **Expected behavior**
A clear and concise description of what you expected to happen. A clear and concise description of what you expected to happen.
@ -25,3 +29,7 @@ If applicable, add screenshots to help explain your problem.
**Additional context** **Additional context**
Add any other context about the problem here. Add any other context about the problem here.
- Provider version (ideally it should be the latest version):
- Terraform version:
- OS (where you run Terraform from)):
- Debug logs (`TF_LOG=DEBUG terraform apply`):

View File

@ -17,6 +17,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"strings"
"github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-log/tflog"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
@ -183,6 +184,13 @@ func (c *VirtualEnvironmentClient) UploadFileToDatastore(
) (*DatastoreUploadResponseBody, error) { ) (*DatastoreUploadResponseBody, error) {
switch d.ContentType { switch d.ContentType {
case "iso", "vztmpl": case "iso", "vztmpl":
tflog.Debug(ctx, "uploading file to datastore using PVE API", map[string]interface{}{
"node_name": d.NodeName,
"datastore_id": d.DatastoreID,
"file_name": d.FileName,
"content_type": d.ContentType,
})
r, w := io.Pipe() r, w := io.Pipe()
defer func(r *io.PipeReader) { defer func(r *io.PipeReader) {
@ -227,7 +235,7 @@ func (c *VirtualEnvironmentClient) UploadFileToDatastore(
return return
} }
_, err = io.Copy(part, d.FileReader) _, err = io.Copy(part, d.File)
if err != nil { if err != nil {
return return
@ -311,6 +319,19 @@ func (c *VirtualEnvironmentClient) UploadFileToDatastore(
default: default:
// We need to upload all other files using SFTP due to API limitations. // We need to upload all other files using SFTP due to API limitations.
// Hopefully, this will not be required in future releases of Proxmox VE. // Hopefully, this will not be required in future releases of Proxmox VE.
tflog.Debug(ctx, "uploading file to datastore using SFTP", map[string]interface{}{
"node_name": d.NodeName,
"datastore_id": d.DatastoreID,
"file_name": d.FileName,
"content_type": d.ContentType,
})
fileInfo, err := d.File.Stat()
if err != nil {
return nil, fmt.Errorf("failed to get file info: %w", err)
}
fileSize := fileInfo.Size()
sshClient, err := c.OpenNodeShell(ctx, d.NodeName) sshClient, err := c.OpenNodeShell(ctx, d.NodeName)
if err != nil { if err != nil {
return nil, err return nil, err
@ -337,8 +358,8 @@ func (c *VirtualEnvironmentClient) UploadFileToDatastore(
if d.ContentType != "" { if d.ContentType != "" {
remoteFileDir = filepath.Join(remoteFileDir, d.ContentType) remoteFileDir = filepath.Join(remoteFileDir, d.ContentType)
} }
remoteFilePath := strings.ReplaceAll(filepath.Join(remoteFileDir, d.FileName), `\`, `/`)
remoteFilePath := filepath.Join(remoteFileDir, d.FileName)
sftpClient, err := sftp.NewClient(sshClient) sftpClient, err := sftp.NewClient(sshClient)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create SFTP client: %w", err) return nil, fmt.Errorf("failed to create SFTP client: %w", err)
@ -372,11 +393,18 @@ func (c *VirtualEnvironmentClient) UploadFileToDatastore(
} }
}(remoteFile) }(remoteFile)
_, err = remoteFile.ReadFrom(d.FileReader) bytesUploaded, err := remoteFile.ReadFrom(d.File)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to upload file %s: %w", remoteFilePath, err) return nil, fmt.Errorf("failed to upload file %s: %w", remoteFilePath, err)
} }
if bytesUploaded != fileSize {
return nil, fmt.Errorf("failed to upload file %s: uploaded %d bytes, expected %d bytes",
remoteFilePath, bytesUploaded, fileSize)
}
tflog.Debug(ctx, "uploaded file to datastore", map[string]interface{}{
"remote_file_path": remoteFilePath,
"size": bytesUploaded,
})
return &DatastoreUploadResponseBody{}, nil return &DatastoreUploadResponseBody{}, nil
} }
} }

View File

@ -5,7 +5,7 @@
package proxmox package proxmox
import ( import (
"io" "os"
"github.com/bpg/terraform-provider-proxmox/proxmox/types" "github.com/bpg/terraform-provider-proxmox/proxmox/types"
) )
@ -88,11 +88,11 @@ type DatastoreListResponseData struct {
// DatastoreUploadRequestBody contains the body for a datastore upload request. // DatastoreUploadRequestBody contains the body for a datastore upload request.
type DatastoreUploadRequestBody struct { type DatastoreUploadRequestBody struct {
ContentType string `json:"content,omitempty"` ContentType string `json:"content,omitempty"`
DatastoreID string `json:"storage,omitempty"` DatastoreID string `json:"storage,omitempty"`
FileName string `json:"filename,omitempty"` FileName string `json:"filename,omitempty"`
FileReader io.Reader `json:"-"` NodeName string `json:"node,omitempty"`
NodeName string `json:"node,omitempty"` File *os.File `json:"-"`
} }
// DatastoreUploadResponseBody contains the body from a datastore upload response. // DatastoreUploadResponseBody contains the body from a datastore upload response.

View File

@ -12,6 +12,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path"
"sort" "sort"
"strings" "strings"
"time" "time"
@ -199,7 +200,20 @@ func (c *VirtualEnvironmentClient) OpenNodeShell(
return nil, fmt.Errorf("failed to determine the home directory: %w", err) return nil, fmt.Errorf("failed to determine the home directory: %w", err)
} }
sshHost := fmt.Sprintf("%s:22", *nodeAddress) sshHost := fmt.Sprintf("%s:22", *nodeAddress)
khPath := fmt.Sprintf("%s/.ssh/known_hosts", homeDir) sshPath := path.Join(homeDir, ".ssh")
if _, err = os.Stat(sshPath); os.IsNotExist(err) {
e := os.Mkdir(sshPath, 0o700)
if e != nil {
return nil, fmt.Errorf("failed to create %s: %w", sshPath, e)
}
}
khPath := path.Join(sshPath, "known_hosts")
if _, err = os.Stat(khPath); os.IsNotExist(err) {
e := os.WriteFile(khPath, []byte{}, 0o600)
if e != nil {
return nil, fmt.Errorf("failed to create %s: %w", khPath, e)
}
}
kh, err := knownhosts.New(khPath) kh, err := knownhosts.New(khPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read %s: %w", khPath, err) return nil, fmt.Errorf("failed to read %s: %w", khPath, err)
@ -243,6 +257,10 @@ func (c *VirtualEnvironmentClient) OpenNodeShell(
return nil, fmt.Errorf("failed to dial %s: %w", sshHost, err) return nil, fmt.Errorf("failed to dial %s: %w", sshHost, err)
} }
tflog.Debug(ctx, "SSH connection established", map[string]interface{}{
"host": sshHost,
"user": ur[0],
})
return sshClient, nil return sshClient, nil
} }

View File

@ -382,7 +382,7 @@ func fileCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag
ContentType: *contentType, ContentType: *contentType,
DatastoreID: datastoreID, DatastoreID: datastoreID,
FileName: *fileName, FileName: *fileName,
FileReader: file, File: file,
NodeName: nodeName, NodeName: nodeName,
} }