Ctrl K
ComunidadGuías…

iclient/incus_console.go

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

Archivo original ↗Preguntar a KiBot
package iclient

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"sync"

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

// GetInstanceConsoleLog returns the console ring buffer Incus keeps for an
// instance, which is what `incus console --show-log` prints. The caller closes
// it.
func (c *Connection) GetInstanceConsoleLog(ctx context.Context, project string, name string) (io.ReadCloser, error) {
	return c.doRaw(ctx, project, http.MethodGet, incusInstancePath(name, "/console"), nil)
}

// ConsoleInstance attaches to an instance's console and copies it to
// args.Output until the context is done or the operation ends. Canceling the
// context detaches, which ends the operation server-side.
func (c *Connection) ConsoleInstance(ctx context.Context, project string, name string, console api.InstanceConsolePost, args *InstanceConsoleArgs) (<-chan api.Operation, error) {
	if args == nil || args.Output == nil {
		return nil, fmt.Errorf("attaching to the console of %q: an output is required", name)
	}

	if console.Type == "" {
		console.Type = "console"
	}

	updates, err := c.asyncOperation(ctx, project, http.MethodPost, incusInstancePath(name, "/console"), nil, console, "")
	if err != nil {
		return nil, err
	}

	// The first value carries the fd secrets.
	started, ok := <-updates
	if !ok {
		return nil, fmt.Errorf("attaching to the console of %q: the operation reported nothing", name)
	}

	fds, ok := started.Metadata["fds"].(map[string]any)
	if !ok {
		return nil, fmt.Errorf("attaching to the console of %q: the operation advertised no file descriptors", name)
	}

	// Canceling this closes the sockets; a websocket read only ends when the socket does.
	streamCtx, closeStreams := context.WithCancel(ctx)

	// Opened and left alone: the server waits for every advertised descriptor.
	_, err = c.dialOperation(streamCtx, project, started.ID, consoleSecret(fds, api.SecretNameControl))
	if err != nil {
		closeStreams()

		return nil, fmt.Errorf("attaching to the control channel of the console on %q: %w", name, err)
	}

	socket, err := c.dialOperation(streamCtx, project, started.ID, consoleSecret(fds, "0"))
	if err != nil {
		closeStreams()

		return nil, fmt.Errorf("attaching to the console on %q: %w", name, err)
	}

	streams := &sync.WaitGroup{}
	streams.Add(1)

	go func() {
		defer streams.Done()

		drainWebsocket(socket, args.Output)
	}()

	return withDrain(updates, started, streams, closeStreams), nil
}

// consoleSecret reads one file descriptor's secret out of the operation.
func consoleSecret(fds map[string]any, name string) string {
	secret, _ := fds[name].(string)

	return secret
}