0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-29 18:21:10 +00:00
terraform-provider-proxmox/proxmox/nodes/tasks/tasks.go
Pavel Boldyrev 4f70459c26
chore(code): fix code quality warnings (#1409)
* chore(code): fix code quality warnings

+ re-enable Qodana on PRs

---------

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
2024-06-25 00:47:50 -04:00

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
}