Ctrl K
ComunidadGuías…

iclient/incus_instance.go

Rama develop · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package iclient

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"slices"
	"strings"

	"github.com/lxc/incus/v7/shared/api"
)

// incusInstancesPath is the collection every instance call hangs off.
const incusInstancesPath = "/instances"

// incusInstancesQuery turns the listing arguments into query parameters.
func incusInstancesQuery(args GetInstancesArgs, recursion string) url.Values {
	v := url.Values{}

	if args.Type != api.InstanceTypeAny {
		v.Set("instance-type", string(args.Type))
	}

	if recursion != "" {
		v.Set("recursion", recursion)
	}

	filter := incusFilter(args.Filters)
	if filter != "" {
		v.Set("filter", filter)
	}

	return v
}

// incusFilter renders "key=value" selectors the way the API reads them.
func incusFilter(filters []string) string {
	clauses := make([]string, 0, len(filters))

	for _, filter := range filters {
		key, value, found := strings.Cut(filter, "=")
		if !found {
			continue
		}

		clauses = append(clauses, key+" eq "+value)
	}

	return strings.Join(clauses, " and ")
}

// incusRecursion is the depth a Full listing needs.
func incusRecursion(full bool) string {
	if full {
		return "2"
	}

	return "1"
}

// incusInstancePath escapes a name into a path under the collection.
func incusInstancePath(name string, suffix ...string) string {
	return incusInstancesPath + "/" + url.PathEscape(name) + strings.Join(suffix, "")
}

// GetInstance returns one instance and its ETag. Without args.Full the state,
// snapshots and backups are zero.
func (c *Connection) GetInstance(ctx context.Context, project string, name string, args *GetInstanceArgs) (*api.InstanceFull, string, error) {
	if args == nil {
		args = &GetInstanceArgs{}
	}

	instance := api.InstanceFull{}

	var query url.Values

	if args.Full {
		query = url.Values{}
		query.Set("recursion", "1")
	}

	etag, err := c.getStruct(ctx, project, incusInstancePath(name), query, &instance)
	if err != nil {
		return nil, "", err
	}

	return &instance, etag, nil
}

// GetInstances returns the instances of project the arguments select.
func (c *Connection) GetInstances(ctx context.Context, project string, args *GetInstancesArgs) ([]api.InstanceFull, error) {
	if args == nil {
		args = &GetInstancesArgs{}
	}

	instances := []api.InstanceFull{}

	_, err := c.getStruct(ctx, project, incusInstancesPath, incusInstancesQuery(*args, incusRecursion(args.Full)), &instances)
	if err != nil {
		return nil, err
	}

	return instances, nil
}

// GetInstancesAllProjects returns the instances of every project the
// certificate may see.
func (c *Connection) GetInstancesAllProjects(ctx context.Context, args *GetInstancesArgs) ([]api.InstanceFull, error) {
	if args == nil {
		args = &GetInstancesArgs{}
	}

	query := incusInstancesQuery(*args, incusRecursion(args.Full))
	query.Set("all-projects", "true")

	instances := []api.InstanceFull{}

	// No project: incusd refuses a request carrying both.
	_, err := c.getStruct(ctx, "", incusInstancesPath, query, &instances)
	if err != nil {
		return nil, err
	}

	return instances, nil
}

// GetInstanceNames returns the names of the instances of project the arguments
// select. There is no all-projects form: a bare name is not unique across them.
func (c *Connection) GetInstanceNames(ctx context.Context, project string, args *GetInstancesArgs) ([]string, error) {
	if args == nil {
		args = &GetInstancesArgs{}
	}

	// Without recursion the collection is a list of resource URLs.
	uris := []string{}

	_, err := c.getStruct(ctx, project, incusInstancesPath, incusInstancesQuery(*args, ""), &uris)
	if err != nil {
		return nil, err
	}

	return resourceNames(incusInstancesPath, uris)
}

// CreateInstance creates an instance and follows the operation.
func (c *Connection) CreateInstance(ctx context.Context, project string, instance api.InstancesPost, args *CreateInstanceArgs) (<-chan api.Operation, error) {
	var query url.Values

	if args != nil && args.Target != "" {
		query = url.Values{}
		query.Set("target", args.Target)
	}

	return c.asyncOperation(ctx, project, http.MethodPost, incusInstancesPath, query, instance, "")
}

// DeleteInstance removes an instance and follows the operation.
func (c *Connection) DeleteInstance(ctx context.Context, project string, name string) (<-chan api.Operation, error) {
	return c.asyncOperation(ctx, project, http.MethodDelete, incusInstancePath(name), nil, nil, "")
}

// UpdateInstanceState starts, stops or restarts an instance and follows the
// operation.
func (c *Connection) UpdateInstanceState(ctx context.Context, project string, name string, state api.InstanceStatePut, etag string) (<-chan api.Operation, error) {
	return c.asyncOperation(ctx, project, http.MethodPut, incusInstancePath(name, "/state"), nil, state, etag)
}

// GetInstanceState returns the runtime state of one instance.
func (c *Connection) GetInstanceState(ctx context.Context, project string, name string) (*api.InstanceState, string, error) {
	state := api.InstanceState{}

	etag, err := c.getStruct(ctx, project, incusInstancePath(name, "/state"), nil, &state)
	if err != nil {
		return nil, "", err
	}

	return &state, etag, nil
}

// WaitInstanceBusy blocks until no queryable operation holds the instance's
// operation lock.
//
// Incus takes that lock in the driver, inside the operation, so a write issued
// while it is held is accepted and then fails from the operation.
func (c *Connection) WaitInstanceBusy(ctx context.Context, project string, name string) error {
	// Outside the default project the operation's resource URL carries
	// ?project=, and matching a bare path reports every instance as free.
	instanceURL := api.NewURL().Path("1.0", "instances", name).Project(project).String()

	for {
		err := ctx.Err()
		if err != nil {
			return err
		}

		operations, err := c.GetOperations(ctx, project)
		if err != nil {
			return fmt.Errorf("listing the operations on %q: %w", name, err)
		}

		holder := api.Operation{}

		for _, operation := range operations {
			if operation.Class != api.OperationClassTask || operation.StatusCode.IsFinal() {
				continue
			}

			if slices.Contains(operation.Resources["instances"], instanceURL) {
				holder = operation

				break
			}
		}

		if holder.ID == "" {
			return nil
		}

		// How it ended is the business of whoever started it; this only waits.
		_, err = c.WaitOperationID(ctx, project, holder.ID)
		if err != nil {
			// Gone between the listing and here means it has finished.
			if api.StatusErrorCheck(err, http.StatusNotFound) {
				continue
			}

			return fmt.Errorf("waiting for operation %s on %q: %w", holder.ID, name, err)
		}
	}
}

// PatchInstanceConfig merges keys into an instance's config, leaving the rest
// of the instance alone. No cluster target is sent; instance config is
// cluster-wide state.
func (c *Connection) PatchInstanceConfig(ctx context.Context, project string, name string, config map[string]string) error {
	patch := struct {
		Config map[string]string `json:"config"`
	}{Config: config}

	_, _, err := c.do(ctx, project, http.MethodPatch, incusInstancePath(name), nil, patch, "")

	return err
}