mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-06-29 18:21:10 +00:00
* chore(code): fix code quality warnings + re-enable Qodana on PRs --------- Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
191 lines
4.7 KiB
Go
191 lines
4.7 KiB
Go
/*
|
|
* 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 (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/avast/retry-go/v4"
|
|
|
|
"github.com/bpg/terraform-provider-proxmox/proxmox/api"
|
|
)
|
|
|
|
// GetTaskStatus retrieves the status of a task.
|
|
func (c *Client) GetTaskStatus(ctx context.Context, upid string) (*GetTaskStatusResponseData, error) {
|
|
resBody := &GetTaskStatusResponseBody{}
|
|
|
|
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, path, nil, resBody)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error retrieving task status: %w", err)
|
|
}
|
|
|
|
if resBody.Data == nil {
|
|
return nil, api.ErrNoDataObjectInResponse
|
|
}
|
|
|
|
return resBody.Data, nil
|
|
}
|
|
|
|
// GetTaskLog retrieves the log of a task. The log is returned as an array of
|
|
// lines. Each line is an object with a line number and the text of the line.
|
|
// Reads first 50 lines by default.
|
|
func (c *Client) GetTaskLog(ctx context.Context, upid string) ([]string, error) {
|
|
resBody := &GetTaskLogResponseBody{}
|
|
|
|
var lines []string //nolint: prealloc
|
|
|
|
path, err := c.BuildPath(upid, "log")
|
|
if err != nil {
|
|
return lines, fmt.Errorf("error building path for task status: %w", err)
|
|
}
|
|
|
|
err = c.DoRequest(ctx, http.MethodGet, path, nil, resBody)
|
|
if err != nil {
|
|
return lines, fmt.Errorf("error retrieving task status: %w", err)
|
|
}
|
|
|
|
if resBody.Data == nil {
|
|
return lines, api.ErrNoDataObjectInResponse
|
|
}
|
|
|
|
for _, line := range resBody.Data {
|
|
lines = append(lines, line.LineText)
|
|
}
|
|
|
|
return lines, nil
|
|
}
|
|
|
|
// DeleteTask deletes specific task.
|
|
func (c *Client) DeleteTask(ctx context.Context, upid string) error {
|
|
path, err := c.baseTaskPath(upid)
|
|
if err != nil {
|
|
return fmt.Errorf("error creating task path: %w", err)
|
|
}
|
|
|
|
err = c.DoRequest(ctx, http.MethodDelete, path, nil, nil)
|
|
if err != nil {
|
|
if errors.Is(err, api.ErrResourceDoesNotExist) {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("error deleting task: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type taskWaitOptions struct {
|
|
ignoreWarnings bool
|
|
ignoreStatusCode int
|
|
}
|
|
|
|
// TaskWaitOption is an option for waiting for a task to complete.
|
|
type TaskWaitOption interface {
|
|
apply(opts *taskWaitOptions)
|
|
}
|
|
|
|
type withIgnoreWarnings struct{}
|
|
|
|
// WithIgnoreWarnings is an option to ignore warnings when waiting for a task to complete.
|
|
func WithIgnoreWarnings() TaskWaitOption {
|
|
return withIgnoreWarnings{}
|
|
}
|
|
|
|
func (w withIgnoreWarnings) apply(opts *taskWaitOptions) {
|
|
opts.ignoreWarnings = true
|
|
}
|
|
|
|
type withIgnoreStatus struct {
|
|
statusCode int
|
|
}
|
|
|
|
// WithIgnoreStatus is an option to ignore particular status code from PVE API when waiting for a task to complete.
|
|
func WithIgnoreStatus(statusCode int) TaskWaitOption {
|
|
return withIgnoreStatus{statusCode: statusCode}
|
|
}
|
|
|
|
func (w withIgnoreStatus) apply(opts *taskWaitOptions) {
|
|
opts.ignoreStatusCode = w.statusCode
|
|
}
|
|
|
|
// WaitForTask waits for a specific task to complete.
|
|
func (c *Client) WaitForTask(ctx context.Context, upid string, opts ...TaskWaitOption) error {
|
|
errStillRunning := errors.New("still running")
|
|
|
|
options := &taskWaitOptions{}
|
|
|
|
for _, opt := range opts {
|
|
opt.apply(options)
|
|
}
|
|
|
|
status, err := retry.DoWithData(
|
|
func() (*GetTaskStatusResponseData, error) {
|
|
status, err := c.GetTaskStatus(ctx, upid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if status.Status == "running" {
|
|
return nil, errStillRunning
|
|
}
|
|
|
|
return status, err
|
|
},
|
|
retry.Context(ctx),
|
|
retry.RetryIf(func(err error) bool {
|
|
var target *api.HTTPError
|
|
if errors.As(err, &target) {
|
|
if target.Code == http.StatusBadRequest {
|
|
// this is a special case to account for eventual consistency
|
|
// when creating a task -- the task may not be available via status API
|
|
// immediately after creation
|
|
return true
|
|
}
|
|
|
|
if options.ignoreStatusCode != 0 && target.Code == options.ignoreStatusCode {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return errors.Is(err, errStillRunning)
|
|
}),
|
|
retry.LastErrorOnly(true),
|
|
retry.UntilSucceeded(),
|
|
retry.DelayType(retry.FixedDelay),
|
|
retry.Delay(time.Second),
|
|
)
|
|
|
|
if errors.Is(err, context.DeadlineExceeded) {
|
|
return fmt.Errorf("timeout while waiting for task %q to complete", upid)
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("error while waiting for task %q to complete: %w", upid, err)
|
|
}
|
|
|
|
if status.ExitCode != "OK" {
|
|
if options.ignoreWarnings &&
|
|
strings.HasPrefix(status.ExitCode, "WARNINGS: ") && !strings.Contains(status.ExitCode, "ERROR") {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("task %q failed to complete with exit code: %s", upid, status.ExitCode)
|
|
}
|
|
|
|
return nil
|
|
}
|