mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-07-03 03:52:58 +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,
|
"git.alwaysSignOff": true,
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"capi",
|
||||||
"CLRF",
|
"CLRF",
|
||||||
"iothread",
|
"iothread",
|
||||||
"keyctl",
|
"keyctl",
|
||||||
|
"nolint",
|
||||||
"proxmoxtf",
|
"proxmoxtf",
|
||||||
"qcow",
|
"qcow",
|
||||||
"rootfs",
|
"rootfs",
|
||||||
@ -13,7 +15,8 @@
|
|||||||
"virtio",
|
"virtio",
|
||||||
"VLANID",
|
"VLANID",
|
||||||
"vmbr",
|
"vmbr",
|
||||||
"VMID"
|
"VMID",
|
||||||
|
"vztmpl"
|
||||||
],
|
],
|
||||||
"go.lintTool": "golangci-lint",
|
"go.lintTool": "golangci-lint",
|
||||||
"go.lintFlags": [
|
"go.lintFlags": [
|
||||||
|
@ -160,12 +160,12 @@ You can configure the `sudo` privilege for the user via the command line on the
|
|||||||
sudo visudo
|
sudo visudo
|
||||||
```
|
```
|
||||||
|
|
||||||
Add the following line to the end of the file:
|
Add the following lines to the end of the file:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
terraform ALL=(root) NOPASSWD: /sbin/pvesm
|
terraform ALL=(root) NOPASSWD: /sbin/pvesm
|
||||||
terraform ALL=(root) NOPASSWD: /sbin/qm
|
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.
|
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`:
|
- Test the SSH connection and password-less `sudo`:
|
||||||
|
|
||||||
```sh
|
```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
|
### Node IP address used for SSH connection
|
||||||
|
|
||||||
|
@ -17,10 +17,12 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/hashicorp/go-cty/cty"
|
"github.com/hashicorp/go-cty/cty"
|
||||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
"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 {
|
switch *contentType {
|
||||||
case "iso", "vztmpl":
|
case "iso", "vztmpl":
|
||||||
uploadTimeout := d.Get(mkResourceVirtualEnvironmentFileTimeoutUpload).(int)
|
uploadTimeout := d.Get(mkResourceVirtualEnvironmentFileTimeoutUpload).(int)
|
||||||
|
|
||||||
_, err = capi.Node(nodeName).Storage(datastoreID).APIUpload(ctx, request, uploadTimeout, config.TempDir())
|
_, err = capi.Node(nodeName).Storage(datastoreID).APIUpload(ctx, request, uploadTimeout, config.TempDir())
|
||||||
|
if err != nil {
|
||||||
|
diags = append(diags, diag.FromErr(err)...)
|
||||||
|
return diags
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// For all other content types, we need to upload the file to the node's
|
// For all other content types, we need to upload the file to the node's
|
||||||
// datastore using SFTP.
|
// 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 {
|
// handle the case where the file is uploaded to a subdirectory of the datastore
|
||||||
diags = append(diags, diag.FromErr(err)...)
|
srcDir := tempFileDir
|
||||||
return diags
|
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)
|
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"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -2974,7 +2975,7 @@ func vmCreateCustomDisks(ctx context.Context, d *schema.ResourceData, m interfac
|
|||||||
commands = append(
|
commands = append(
|
||||||
commands,
|
commands,
|
||||||
`set -e`,
|
`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_id="%s"`, fileID),
|
||||||
fmt.Sprintf(`file_format="%s"`, fileFormat),
|
fmt.Sprintf(`file_format="%s"`, fileFormat),
|
||||||
fmt.Sprintf(`datastore_id_target="%s"`, datastoreID),
|
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)
|
out, err := api.SSH().ExecuteNodeCommands(ctx, nodeName, commands)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "pvesm: not found") {
|
if matches, e := regexp.Match(`pvesm: .* not found`, out); e == nil && matches {
|
||||||
return diag.Errorf("The configured SSH user '%s' does not have the required permissions to import disks. "+
|
return diag.FromErr(newErrSSHUserNoPermission(api.SSH().Username()))
|
||||||
"Make sure `sudo` is installated and the user is a member of sudoers.", api.SSH().Username())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return diag.FromErr(err)
|
return diag.FromErr(err)
|
||||||
|
Loading…
Reference in New Issue
Block a user