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:
parent
65bdf24d58
commit
ef2f2c1159
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
|
92
proxmox/nodes/tasks/tasks_types_test.go
Normal file
92
proxmox/nodes/tasks/tasks_types_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user