mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-07-10 07:45:02 +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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
"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.
|
// ExpandPath expands a path relative to the client's base path.
|
||||||
func (c *Client) ExpandPath(path string) string {
|
func (c *Client) ExpandPath(_ string) string {
|
||||||
return c.Client.ExpandPath(
|
panic("ExpandPath of tasks.Client must not be used. Use BuildPath instead.")
|
||||||
fmt.Sprintf("tasks/%s", path),
|
}
|
||||||
)
|
|
||||||
|
// 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"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
||||||
@ -21,10 +20,15 @@ import (
|
|||||||
func (c *Client) GetTaskStatus(ctx context.Context, upid string) (*GetTaskStatusResponseData, error) {
|
func (c *Client) GetTaskStatus(ctx context.Context, upid string) (*GetTaskStatusResponseData, error) {
|
||||||
resBody := &GetTaskStatusResponseBody{}
|
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,
|
ctx,
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
c.ExpandPath(fmt.Sprintf("%s/status", url.PathEscape(upid))),
|
path,
|
||||||
nil,
|
nil,
|
||||||
resBody,
|
resBody,
|
||||||
)
|
)
|
||||||
|
@ -6,6 +6,13 @@
|
|||||||
|
|
||||||
package tasks
|
package tasks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
// GetTaskStatusResponseBody contains the body from a node get task status response.
|
// GetTaskStatusResponseBody contains the body from a node get task status response.
|
||||||
type GetTaskStatusResponseBody struct {
|
type GetTaskStatusResponseBody struct {
|
||||||
Data *GetTaskStatusResponseData `json:"data,omitempty"`
|
Data *GetTaskStatusResponseData `json:"data,omitempty"`
|
||||||
@ -17,3 +24,63 @@ type GetTaskStatusResponseData struct {
|
|||||||
Status string `json:"status,omitempty"`
|
Status string `json:"status,omitempty"`
|
||||||
ExitCode string `json:"exitstatus,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