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:
parent
20aacc2547
commit
7c9505d11f
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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`):
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user