/* Copyright (c) Edgeless Systems GmbH SPDX-License-Identifier: AGPL-3.0-only */ package libvirt import ( "context" "errors" "io" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" docker "github.com/docker/docker/client" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/spf13/afero" ) // LibvirtTCPConnectURI is the default URI to connect to containerized libvirt. // Non standard port to avoid conflict with host libvirt. // Changes here should also be reflected in the Dockerfile in "cli/internal/libvirt/Dockerfile". const LibvirtTCPConnectURI = "qemu+tcp://localhost:16599/system" // Runner handles starting and stopping of containerized libvirt instances. type Runner struct { nameFile string file file.Handler } // New creates a new LibvirtRunner. func New() *Runner { return &Runner{ nameFile: "libvirt.name", file: file.NewHandler(afero.NewOsFs()), } } // Start starts a containerized libvirt instance. func (r *Runner) Start(ctx context.Context, name, imageName string) error { docker, err := docker.NewClientWithOpts(docker.FromEnv, docker.WithAPIVersionNegotiation()) if err != nil { return err } defer docker.Close() containerName := name + "-libvirt" // check if image exists locally, if not pull it // this allows us to use a custom image without having to push it to a registry images, err := docker.ImageList(ctx, types.ImageListOptions{ Filters: filters.NewArgs( filters.KeyValuePair{ Key: "reference", Value: imageName, }, ), }) if err != nil { return err } if len(images) == 0 { reader, err := docker.ImagePull(ctx, imageName, types.ImagePullOptions{}) if err != nil { return err } defer reader.Close() if _, err := io.Copy(io.Discard, reader); err != nil { return err } } // create and start the libvirt container if _, err := docker.ContainerCreate(ctx, &container.Config{ Image: imageName, }, &container.HostConfig{ NetworkMode: container.NetworkMode("host"), AutoRemove: true, // container has to be "privileged" so libvirt has access to proc fs Privileged: true, }, nil, nil, containerName, ); err != nil { return err } if err := docker.ContainerStart(ctx, containerName, types.ContainerStartOptions{}); err != nil { _ = docker.ContainerRemove(ctx, containerName, types.ContainerRemoveOptions{Force: true}) return err } // write the name of the container to a file so we can remove it later if err := r.file.Write(r.nameFile, []byte(containerName)); err != nil { _ = docker.ContainerRemove(ctx, containerName, types.ContainerRemoveOptions{Force: true}) return err } return nil } // Stop stops a containerized libvirt instance. func (r *Runner) Stop(ctx context.Context) error { name, err := r.file.Read(r.nameFile) if err != nil { if errors.Is(err, afero.ErrFileNotFound) { return nil } return err } docker, err := docker.NewClientWithOpts(docker.FromEnv, docker.WithAPIVersionNegotiation()) if err != nil { return err } defer docker.Close() if err := docker.ContainerRemove(ctx, string(name), types.ContainerRemoveOptions{Force: true}); err != nil { return err } if err := r.file.Remove(r.nameFile); err != nil && !errors.Is(err, afero.ErrFileNotFound) { return err } return nil }