mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-30 02:31:10 +00:00
fix(file): use sudo
for snippets upload (#1004)
* fix(file): use `sudo` for snippets upload Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> * fix: linter Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> * fix: no more rm -rf Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> --------- Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
parent
78fa7b1a4f
commit
60fb679e9f
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -1,9 +1,11 @@
|
||||
{
|
||||
"git.alwaysSignOff": true,
|
||||
"cSpell.words": [
|
||||
"capi",
|
||||
"CLRF",
|
||||
"iothread",
|
||||
"keyctl",
|
||||
"nolint",
|
||||
"proxmoxtf",
|
||||
"qcow",
|
||||
"rootfs",
|
||||
@ -13,7 +15,8 @@
|
||||
"virtio",
|
||||
"VLANID",
|
||||
"vmbr",
|
||||
"VMID"
|
||||
"VMID",
|
||||
"vztmpl"
|
||||
],
|
||||
"go.lintTool": "golangci-lint",
|
||||
"go.lintFlags": [
|
||||
|
@ -160,12 +160,12 @@ You can configure the `sudo` privilege for the user via the command line on the
|
||||
sudo visudo
|
||||
```
|
||||
|
||||
Add the following line to the end of the file:
|
||||
Add the following lines to the end of the file:
|
||||
|
||||
```sh
|
||||
terraform ALL=(root) NOPASSWD: /sbin/pvesm
|
||||
terraform ALL=(root) NOPASSWD: /sbin/qm
|
||||
terraform ALL=(root) NOPASSWD: /usr/bin/echo tfpve
|
||||
terraform ALL=(root) NOPASSWD: /usr/bin/mv /tmp/tfpve/* /var/lib/vz/*
|
||||
```
|
||||
|
||||
Save the file and exit.
|
||||
@ -179,10 +179,10 @@ You can configure the `sudo` privilege for the user via the command line on the
|
||||
- Test the SSH connection and password-less `sudo`:
|
||||
|
||||
```sh
|
||||
ssh terraform@<target-node> sudo echo tfpve
|
||||
ssh terraform@<target-node> sudo pvesm apiinfo
|
||||
```
|
||||
|
||||
You should be able to connect to the target node and see the output `tfpve` on the screen without being prompted for your password.
|
||||
You should be able to connect to the target node and see the output containing `APIVER <number>` on the screen without being prompted for your password.
|
||||
|
||||
### Node IP address used for SSH connection
|
||||
|
||||
|
@ -17,10 +17,12 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/hashicorp/go-cty/cty"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
@ -538,7 +540,12 @@ func fileCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag
|
||||
switch *contentType {
|
||||
case "iso", "vztmpl":
|
||||
uploadTimeout := d.Get(mkResourceVirtualEnvironmentFileTimeoutUpload).(int)
|
||||
|
||||
_, err = capi.Node(nodeName).Storage(datastoreID).APIUpload(ctx, request, uploadTimeout, config.TempDir())
|
||||
if err != nil {
|
||||
diags = append(diags, diag.FromErr(err)...)
|
||||
return diags
|
||||
}
|
||||
default:
|
||||
// For all other content types, we need to upload the file to the node's
|
||||
// datastore using SFTP.
|
||||
@ -565,14 +572,44 @@ func fileCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag
|
||||
}...)
|
||||
}
|
||||
|
||||
remoteFileDir := *datastore.Path
|
||||
// the temp directory is used to store the file on the node before moving it to the datastore
|
||||
// will be created if it does not exist
|
||||
tempFileDir := fmt.Sprintf("/tmp/tfpve/%s", uuid.NewString())
|
||||
|
||||
err = capi.SSH().NodeUpload(ctx, nodeName, remoteFileDir, request)
|
||||
}
|
||||
err = capi.SSH().NodeUpload(ctx, nodeName, tempFileDir, request)
|
||||
if err != nil {
|
||||
diags = append(diags, diag.FromErr(err)...)
|
||||
return diags
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
diags = append(diags, diag.FromErr(err)...)
|
||||
return diags
|
||||
// handle the case where the file is uploaded to a subdirectory of the datastore
|
||||
srcDir := tempFileDir
|
||||
dstDir := *datastore.Path
|
||||
|
||||
if request.ContentType != "" {
|
||||
srcDir = tempFileDir + "/" + request.ContentType
|
||||
dstDir = *datastore.Path + "/" + request.ContentType
|
||||
}
|
||||
|
||||
_, err := capi.SSH().ExecuteNodeCommands(ctx, nodeName, []string{
|
||||
// the `mv` command should be scoped to the specific directories in sudoers!
|
||||
fmt.Sprintf(`%s; try_sudo "mv %s/%s %s/%s" && rm %s/%s && rmdir -p %s`,
|
||||
trySudo,
|
||||
srcDir, *fileName,
|
||||
dstDir, *fileName,
|
||||
srcDir, *fileName,
|
||||
srcDir,
|
||||
),
|
||||
})
|
||||
if err != nil {
|
||||
if matches, e := regexp.MatchString(`cannot move .* Permission denied`, err.Error()); e == nil && matches {
|
||||
return diag.FromErr(newErrSSHUserNoPermission(capi.SSH().Username()))
|
||||
}
|
||||
|
||||
diags = append(diags, diag.Errorf("error moving file: %s", err.Error())...)
|
||||
|
||||
return diags
|
||||
}
|
||||
}
|
||||
|
||||
volID, di := fileGetVolumeID(d)
|
||||
|
15
proxmoxtf/resource/sudo.go
Normal file
15
proxmoxtf/resource/sudo.go
Normal file
@ -0,0 +1,15 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
trySudo = `try_sudo(){ if [ $(sudo -n pvesm apiinfo 2>&1 | grep "APIVER" | wc -l) -gt 0 ]; then sudo $1; else $1; fi }`
|
||||
)
|
||||
|
||||
func newErrSSHUserNoPermission(username string) error {
|
||||
return fmt.Errorf("the SSH user '%s' does not have required permissions. "+
|
||||
"Make sure 'sudo' is installed and the user is configured in sudoers file. "+
|
||||
"Refer to the documentation for more details", username)
|
||||
}
|
@ -11,6 +11,7 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -2974,7 +2975,7 @@ func vmCreateCustomDisks(ctx context.Context, d *schema.ResourceData, m interfac
|
||||
commands = append(
|
||||
commands,
|
||||
`set -e`,
|
||||
`try_sudo(){ if [ $(sudo -n echo tfpve 2>&1 | grep "tfpve" | wc -l) -gt 0 ]; then sudo $1; else $1; fi }`,
|
||||
trySudo,
|
||||
fmt.Sprintf(`file_id="%s"`, fileID),
|
||||
fmt.Sprintf(`file_format="%s"`, fileFormat),
|
||||
fmt.Sprintf(`datastore_id_target="%s"`, datastoreID),
|
||||
@ -3007,9 +3008,8 @@ func vmCreateCustomDisks(ctx context.Context, d *schema.ResourceData, m interfac
|
||||
|
||||
out, err := api.SSH().ExecuteNodeCommands(ctx, nodeName, commands)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "pvesm: not found") {
|
||||
return diag.Errorf("The configured SSH user '%s' does not have the required permissions to import disks. "+
|
||||
"Make sure `sudo` is installated and the user is a member of sudoers.", api.SSH().Username())
|
||||
if matches, e := regexp.Match(`pvesm: .* not found`, out); e == nil && matches {
|
||||
return diag.FromErr(newErrSSHUserNoPermission(api.SSH().Username()))
|
||||
}
|
||||
|
||||
return diag.FromErr(err)
|
||||
|
Loading…
Reference in New Issue
Block a user