iclient/incus_instance.go
Rama develop · réplica de lectura del repositorio original.
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
}