From 43d9e516f39ad268dd0f1003d654bd04ddac5ab9 Mon Sep 17 00:00:00 2001 From: ManInDark <61268856+ManInDark@users.noreply.github.com> Date: Sat, 31 Jan 2026 15:35:39 +0100 Subject: [PATCH] feat: working update --- proxmox.go | 121 +++++++++++++++++++++++++++++++++++++++++++----- proxmox_test.go | 45 ++++++++++++++++++ types.go | 8 ++++ 3 files changed, 162 insertions(+), 12 deletions(-) diff --git a/proxmox.go b/proxmox.go index 158e562..9ca0e6f 100644 --- a/proxmox.go +++ b/proxmox.go @@ -9,6 +9,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "io" "log" "net/http" @@ -123,6 +124,9 @@ func (p *Plugin) Create(ctx context.Context, req *resource.CreateRequest) (*reso client := &http.Client{} arguments := "vmid=" + props.VMID + "&ostemplate=" + props.OSTemplate + "&hostname=" + props.Hostname + if props.Description != "" { + arguments += "&description=" + props.Description + } request, err := http.NewRequest("POST", config.URL+"/api2/json/nodes/"+config.NODE+"/lxc", bytes.NewBuffer([]byte(arguments))) request.Header.Set("Authorization", "PVEAPIToken="+username+"="+token) @@ -223,22 +227,115 @@ func (p *Plugin) Read(ctx context.Context, req *resource.ReadRequest) (*resource // Update modifies an existing resource. func (p *Plugin) Update(ctx context.Context, req *resource.UpdateRequest) (*resource.UpdateResult, error) { - // TODO: Implement resource update - // - // 1. Use req.NativeID to identify the resource - // 2. Use req.PatchDocument for changes (JSON Patch format) - // Or compare req.PriorProperties with req.DesiredProperties - // 3. Call your provider's API to apply changes - // 4. Return ProgressResult with status + + prior, err := parseLXCProperties(req.PriorProperties) + if err != nil { + return &resource.UpdateResult{ + ProgressResult: &resource.ProgressResult{ + Operation: resource.OperationUpdate, + OperationStatus: resource.OperationStatusFailure, + ErrorCode: resource.OperationErrorCodeInvalidRequest, + StatusMessage: err.Error(), + }, + }, err + } + + desir, err := parseLXCProperties(req.DesiredProperties) + if err != nil { + return &resource.UpdateResult{ + ProgressResult: &resource.ProgressResult{ + Operation: resource.OperationUpdate, + OperationStatus: resource.OperationStatusFailure, + ErrorCode: resource.OperationErrorCodeInvalidRequest, + StatusMessage: err.Error(), + }, + }, err + } + + if prior == nil { + p.Create(ctx, &resource.CreateRequest{ + ResourceType: req.ResourceType, + Label: req.Label, + Properties: req.DesiredProperties, + TargetConfig: req.TargetConfig, + }) + } + + if prior.VMID != desir.VMID { + return &resource.UpdateResult{ + ProgressResult: &resource.ProgressResult{ + Operation: resource.OperationUpdate, + OperationStatus: resource.OperationStatusFailure, + ErrorCode: resource.OperationErrorCodeInvalidRequest, + StatusMessage: "can't change vmid", + }, + }, fmt.Errorf("can't change vmid") + } + + if prior.Hostname != desir.Hostname || prior.Description != desir.Description { + config, err := parseTargetConfig(req.TargetConfig) + if err != nil { + log.Println(err.Error()) + return &resource.UpdateResult{ + ProgressResult: &resource.ProgressResult{ + Operation: resource.OperationCreate, + OperationStatus: resource.OperationStatusFailure, + ErrorCode: resource.OperationErrorCodeInternalFailure, + StatusMessage: err.Error(), + }, + }, err + } + + username, token, err := getCredentials() + if err != nil { + return &resource.UpdateResult{ + ProgressResult: &resource.ProgressResult{ + Operation: resource.OperationUpdate, + OperationStatus: resource.OperationStatusFailure, + ErrorCode: resource.OperationErrorCodeAccessDenied, + StatusMessage: err.Error(), + }, + }, err + } + + client := &http.Client{} + + url := config.URL + "/api2/json/nodes/" + config.NODE + "/lxc/" + desir.VMID + "/config" + arguments := "vmid=" + desir.VMID + "&hostname=" + desir.Hostname + "&description=" + desir.Description + + argumentBuffer := bytes.NewBuffer([]byte(arguments)) + request, err := http.NewRequest("PUT", url, argumentBuffer) + request.Header.Set("Authorization", "PVEAPIToken="+username+"="+token) + + resp, err := client.Do(request) + if err != nil { + return &resource.UpdateResult{ + ProgressResult: &resource.ProgressResult{ + Operation: resource.OperationCreate, + OperationStatus: resource.OperationStatusFailure, + ErrorCode: resource.OperationErrorCodeInternalFailure, + StatusMessage: err.Error(), + }, + }, err + } + + log.Println("Response StatusCode: ", resp.Status) + } + + result, err := p.Read(ctx, &resource.ReadRequest{ + NativeID: req.NativeID, + ResourceType: req.ResourceType, + TargetConfig: req.TargetConfig, + }) return &resource.UpdateResult{ ProgressResult: &resource.ProgressResult{ - Operation: resource.OperationUpdate, - OperationStatus: resource.OperationStatusFailure, - ErrorCode: resource.OperationErrorCodeInternalFailure, - StatusMessage: "Update not implemented", + Operation: resource.OperationUpdate, + OperationStatus: resource.OperationStatusSuccess, + NativeID: req.NativeID, + ResourceProperties: json.RawMessage(result.Properties), }, - }, ErrNotImplemented + }, nil } // Delete removes a resource. diff --git a/proxmox_test.go b/proxmox_test.go index 20c8cb3..928a182 100644 --- a/proxmox_test.go +++ b/proxmox_test.go @@ -107,3 +107,48 @@ func TestRead(t *testing.T) { require.Equal(t, "ntfy", props["hostname"], "hostname should match") require.Equal(t, strconv.Itoa(120), props["vmid"], "vmid should match") } + +func TestUpdate(t *testing.T) { + ctx := context.Background() + plugin := &Plugin{} + + priorProperties, _ := json.Marshal(map[string]any{ + "vmid": "200", + "hostname": "testlxc", + "description": "none", + "ostemplate": "local:vztmpl/alpine-3.22-default_20250617_amd64.tar.xz", + }) + + desiredProperties, _ := json.Marshal(map[string]any{ + "vmid": "200", + "hostname": "testlxc-updated", + "description": "none", + "ostemplate": "local:vztmpl/alpine-3.22-default_20250617_amd64.tar.xz", + }) + + req := &resource.UpdateRequest{ + NativeID: "200", + ResourceType: "PROXMOX::Service::LXC", + PriorProperties: priorProperties, + DesiredProperties: desiredProperties, + TargetConfig: testTargetConfig(), + } + + result, err := plugin.Update(ctx, req) + require.NoError(t, err, "Update should not return error") + require.NotNil(t, result.ProgressResult, "Update should return ProgressResult") + require.Equal(t, resource.OperationStatusSuccess, result.ProgressResult.OperationStatus, "Update should return Success status") + + readReq := &resource.ReadRequest{ + NativeID: strconv.Itoa(200), + ResourceType: "PROXMOX::Service::LXC", + TargetConfig: testTargetConfig(), + } + + readResult, err := plugin.Read(ctx, readReq) + var props map[string]any + + err = json.Unmarshal([]byte(readResult.Properties), &props) + require.Equal(t, "testlxc-updated", props["hostname"], "hostname should have changed") + // test if update has happened +} diff --git a/types.go b/types.go index e8017ea..5b64f57 100644 --- a/types.go +++ b/types.go @@ -20,6 +20,14 @@ type ReadRequest struct { TargetConfig json.RawMessage } +type UpdateRequest struct { + NativeID string + ResourceType string + PriorProperties json.RawMessage + DesiredProperties json.RawMessage + TargetConfig json.RawMessage +} + type StatusLXCGeneral struct { Status string `json:"status"` NetIn int `json:"netin"`