0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-07-09 15:25:01 +00:00

fix(node): file upload in multi-node PVE cluster (#533)

* fix(node): fix file upload in multi-node PVE cluster

* fix: timezone 🤦
This commit is contained in:
Pavel Boldyrev 2023-08-31 21:45:20 -04:00 committed by GitHub
parent 65bdf24d58
commit ef2f2c1159
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 180 additions and 7 deletions

View File

@ -8,6 +8,7 @@ package tasks
import (
"fmt"
"net/url"
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
)
@ -18,8 +19,17 @@ type Client struct {
}
// ExpandPath expands a path relative to the client's base path.
func (c *Client) ExpandPath(path string) string {
return c.Client.ExpandPath(
fmt.Sprintf("tasks/%s", path),
)
func (c *Client) ExpandPath(_ string) string {
panic("ExpandPath of tasks.Client must not be used. Use BuildPath instead.")
}
// BuildPath builds a path using information from Task ID.
func (c *Client) BuildPath(taskID string, path string) (string, error) {
tid, err := ParseTaskID(taskID)
if err != nil {
return "", err
}
return fmt.Sprintf("nodes/%s/tasks/%s/%s",
url.PathEscape(tid.NodeName), url.PathEscape(taskID), url.PathEscape(path)), nil
}

View File

@ -11,7 +11,6 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"time"
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
@ -21,10 +20,15 @@ import (
func (c *Client) GetTaskStatus(ctx context.Context, upid string) (*GetTaskStatusResponseData, error) {
resBody := &GetTaskStatusResponseBody{}
err := c.DoRequest(
path, err := c.BuildPath(upid, "status")
if err != nil {
return nil, fmt.Errorf("error building path for task status: %w", err)
}
err = c.DoRequest(
ctx,
http.MethodGet,
c.ExpandPath(fmt.Sprintf("%s/status", url.PathEscape(upid))),
path,
nil,
resBody,
)

View File

@ -6,6 +6,13 @@
package tasks
import (
"fmt"
"strconv"
"strings"
"time"
)
// GetTaskStatusResponseBody contains the body from a node get task status response.
type GetTaskStatusResponseBody struct {
Data *GetTaskStatusResponseData `json:"data,omitempty"`
@ -17,3 +24,63 @@ type GetTaskStatusResponseData struct {
Status string `json:"status,omitempty"`
ExitCode string `json:"exitstatus,omitempty"`
}
// TaskID contains the components of a PVE task ID.
type TaskID struct {
NodeName string
PID int
PStart int
StartTime time.Time
Type string
ID string
User string
}
// ParseTaskID parses a task ID into its component parts.
// The task ID is expected to be in the format of:
//
// UPID:<node_name>:<pid_in_hex>:<pstart_in_hex>:<starttime_in_hex>:<type>:<id (optional)>:<user>@<realm>:
func ParseTaskID(taskID string) (TaskID, error) {
parts := strings.SplitN(taskID, ":", 9)
if parts[0] != "UPID" || len(parts) < 8 {
return TaskID{}, fmt.Errorf("invalid task ID format: %s", taskID)
}
if parts[1] == "" {
return TaskID{}, fmt.Errorf("missing node name in task ID: %s", taskID)
}
pid, err := strconv.ParseInt(parts[2], 16, 32)
if err != nil {
return TaskID{}, fmt.Errorf("error parsing task ID: %w", err)
}
pstart, err := strconv.ParseInt(parts[3], 16, 32)
if err != nil {
return TaskID{}, fmt.Errorf("error parsing pstart in task ID: %q: %w", taskID, err)
}
stime, err := strconv.ParseInt(parts[4], 16, 32)
if err != nil {
return TaskID{}, fmt.Errorf("error parsing start time in task ID: %q: %w", taskID, err)
}
if parts[5] == "" {
return TaskID{}, fmt.Errorf("missing task type in task ID: %q", taskID)
}
if parts[7] == "" {
return TaskID{}, fmt.Errorf("missing user in task ID: %q", taskID)
}
return TaskID{
NodeName: parts[1],
PID: int(pid),
PStart: int(pstart),
StartTime: time.Unix(stime, 0).UTC(),
Type: parts[5],
ID: parts[6],
User: parts[7],
}, nil
}

View File

@ -0,0 +1,92 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package tasks
import (
"reflect"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestParseTaskID(t *testing.T) {
t.Parallel()
stime, err := time.Parse(time.RFC3339, "2023-08-30T21:28:16-04:00")
require.NoError(t, err)
stime = stime.UTC()
tests := []struct {
name string
taskID string
want TaskID
wantErr bool
}{
{
name: "imgcopy task",
taskID: "UPID:pve:00061CB3:010BA69C:64EFECB0:imgcopy::root@pam:",
want: TaskID{
NodeName: "pve",
PID: 400563,
PStart: 17540764,
StartTime: stime,
Type: "imgcopy",
ID: "",
User: "root@pam",
},
},
{
name: "qmcreate task",
taskID: "UPID:pve:00061CB3:010BA69C:64EFECB0:qmcreate:101:root@pam:",
want: TaskID{
NodeName: "pve",
PID: 400563,
PStart: 17540764,
StartTime: stime,
Type: "qmcreate",
ID: "101",
User: "root@pam",
},
},
{
name: "missing node",
taskID: "UPID::00061CB3:010BA69C:64EFECB0:qmcreate:101:root@pam:",
wantErr: true,
},
{
name: "wrong ID format",
taskID: "blah",
wantErr: true,
},
{
name: "missing pid",
taskID: "UPID:pve::010BA69C:64EFECB0:qmcreate:101:root@pam:",
wantErr: true,
},
{
name: "missing parts",
taskID: "UPID:pve:00061CB3:010BA69C:64EFECB0::root@pam:",
wantErr: true,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := ParseTaskID(tt.taskID)
if (err != nil) != tt.wantErr {
t.Errorf("ParseTaskID() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ParseTaskID() got = %v, want %v", got, tt.want)
}
})
}
}