Add container support to LXD provider plus fixes
* Add the ability to define an LXD provider which spins up containers
instead of virtual machines.
* Loading an LXD provider that is not reachable no longer crashes garm
on startup.
* Labels are no longer copied on image import. The LXD provider will
resolve the image fingerprint from the simplestreams server every time.
The image will be copied locally if a new version exists.
This commit is contained in:
parent
5566cde77f
commit
ecd476af02
4 changed files with 107 additions and 68 deletions
|
|
@ -99,7 +99,19 @@ type LXD struct {
|
|||
ImageRemotes map[string]LXDImageRemote `toml:"image_remotes" json:"image-remotes"`
|
||||
|
||||
// SecureBoot enables secure boot for VMs spun up using this provider.
|
||||
SecureBoot bool `yaml:"secure_boot" json:"secure-boot"`
|
||||
SecureBoot bool `toml:"secure_boot" json:"secure-boot"`
|
||||
|
||||
// InstanceType allows you to choose between a virtual machine and a container
|
||||
InstanceType LXDImageType `toml:"instance_type" json:"instance-type"`
|
||||
}
|
||||
|
||||
func (l *LXD) GetInstanceType() LXDImageType {
|
||||
switch l.InstanceType {
|
||||
case LXDImageVirtualMachine, LXDImageContainer:
|
||||
return l.InstanceType
|
||||
default:
|
||||
return LXDImageVirtualMachine
|
||||
}
|
||||
}
|
||||
|
||||
func (l *LXD) Validate() error {
|
||||
|
|
|
|||
|
|
@ -25,13 +25,10 @@ import (
|
|||
lxd "github.com/lxc/lxd/client"
|
||||
"github.com/lxc/lxd/shared/api"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type image struct {
|
||||
remotes map[string]config.LXDImageRemote
|
||||
|
||||
cli lxd.InstanceServer
|
||||
}
|
||||
|
||||
// parseImageName parses the image name that comes in from the config and returns a
|
||||
|
|
@ -50,8 +47,8 @@ func (i *image) parseImageName(imageName string) (config.LXDImageRemote, string,
|
|||
return config.LXDImageRemote{}, "", runnerErrors.ErrNotFound
|
||||
}
|
||||
|
||||
func (i *image) getLocalImageByAlias(imageName string, imageType config.LXDImageType, arch string) (*api.Image, error) {
|
||||
aliases, err := i.cli.GetImageAliasArchitectures(imageType.String(), imageName)
|
||||
func (i *image) getLocalImageByAlias(imageName string, imageType config.LXDImageType, arch string, cli lxd.InstanceServer) (*api.Image, error) {
|
||||
aliases, err := cli.GetImageAliasArchitectures(imageType.String(), imageName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "resolving alias: %s", imageName)
|
||||
}
|
||||
|
|
@ -61,7 +58,7 @@ func (i *image) getLocalImageByAlias(imageName string, imageType config.LXDImage
|
|||
return nil, fmt.Errorf("no image found for arch %s and image type %s with name %s", arch, imageType, imageName)
|
||||
}
|
||||
|
||||
image, _, err := i.cli.GetImage(alias.Target)
|
||||
image, _, err := cli.GetImage(alias.Target)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching image details")
|
||||
}
|
||||
|
|
@ -79,7 +76,7 @@ func (i *image) clientFromRemoteArgs(remote config.LXDImageRemote) (lxd.ImageSer
|
|||
return d, nil
|
||||
}
|
||||
|
||||
func (i *image) copyImageFromRemote(remote config.LXDImageRemote, imageName string, imageType config.LXDImageType, arch string) (*api.Image, error) {
|
||||
func (i *image) copyImageFromRemote(remote config.LXDImageRemote, imageName string, imageType config.LXDImageType, arch string, cli lxd.InstanceServer) (*api.Image, error) {
|
||||
imgCli, err := i.clientFromRemoteArgs(remote)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching image server client")
|
||||
|
|
@ -91,13 +88,6 @@ func (i *image) copyImageFromRemote(remote config.LXDImageRemote, imageName stri
|
|||
return nil, errors.Wrapf(err, "resolving alias: %s", imageName)
|
||||
}
|
||||
|
||||
yml, err := yaml.Marshal(aliases)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Println(string(yml))
|
||||
|
||||
alias, ok := aliases[arch]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no image found for arch %s and image type %s with name %s", arch, imageType, imageName)
|
||||
|
|
@ -108,25 +98,11 @@ func (i *image) copyImageFromRemote(remote config.LXDImageRemote, imageName stri
|
|||
return nil, errors.Wrap(err, "fetching image details")
|
||||
}
|
||||
|
||||
// Ask LXD to copy the image from the remote server
|
||||
imgAliases := []api.ImageAlias{}
|
||||
found := false
|
||||
for _, alias := range image.Aliases {
|
||||
if alias.Name == imageName {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
imgAliases = append(imgAliases, api.ImageAlias{Name: imageName})
|
||||
imgCopyArgs := &lxd.ImageCopyArgs{
|
||||
AutoUpdate: true,
|
||||
}
|
||||
|
||||
imgCopyArgs := &lxd.ImageCopyArgs{
|
||||
AutoUpdate: true,
|
||||
CopyAliases: true,
|
||||
Aliases: imgAliases,
|
||||
}
|
||||
op, err := i.cli.CopyImage(imgCli, *image, imgCopyArgs)
|
||||
op, err := cli.CopyImage(imgCli, *image, imgCopyArgs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "copying image %s from %s", imageName, remote.Address)
|
||||
}
|
||||
|
|
@ -139,18 +115,18 @@ func (i *image) copyImageFromRemote(remote config.LXDImageRemote, imageName stri
|
|||
|
||||
// We should now have the image locally. Force another query. This probably makes no sense,
|
||||
// but this is done only once.
|
||||
return i.getLocalImageByAlias(imageName, imageType, arch)
|
||||
return image, nil
|
||||
}
|
||||
|
||||
// EnsureImage will look for an image locally, then attempt to download it from a remote
|
||||
// server, if the name contains a remote. Allowed formats are:
|
||||
// remote_name:image_name
|
||||
// image_name
|
||||
func (i *image) EnsureImage(imageName string, imageType config.LXDImageType, arch string) (*api.Image, error) {
|
||||
func (i *image) EnsureImage(imageName string, imageType config.LXDImageType, arch string, cli lxd.InstanceServer) (*api.Image, error) {
|
||||
if !strings.Contains(imageName, ":") {
|
||||
// A remote was not specified, try to find an image using the imageName as
|
||||
// an alias.
|
||||
return i.getLocalImageByAlias(imageName, imageType, arch)
|
||||
return i.getLocalImageByAlias(imageName, imageType, arch, cli)
|
||||
}
|
||||
|
||||
remote, parsedName, err := i.parseImageName(imageName)
|
||||
|
|
@ -158,13 +134,13 @@ func (i *image) EnsureImage(imageName string, imageType config.LXDImageType, arc
|
|||
return nil, errors.Wrap(err, "parsing image name")
|
||||
}
|
||||
|
||||
if img, err := i.getLocalImageByAlias(parsedName, imageType, arch); err == nil {
|
||||
if img, err := i.getLocalImageByAlias(parsedName, imageType, arch, cli); err == nil {
|
||||
return img, nil
|
||||
} else {
|
||||
log.Printf("failed to fetch local image of type %v with name %s and arch %s: %s", imageType, parsedName, arch, err)
|
||||
}
|
||||
|
||||
img, err := i.copyImageFromRemote(remote, parsedName, imageType, arch)
|
||||
img, err := i.copyImageFromRemote(remote, parsedName, imageType, arch, cli)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching image")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ package lxd
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"garm/config"
|
||||
runnerErrors "garm/errors"
|
||||
|
|
@ -80,24 +81,11 @@ func NewProvider(ctx context.Context, cfg *config.Provider, controllerID string)
|
|||
return nil, fmt.Errorf("invalid provider type %s, expected %s", cfg.ProviderType, config.LXDProvider)
|
||||
}
|
||||
|
||||
cli, err := getClientFromConfig(ctx, &cfg.LXD)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating LXD client")
|
||||
}
|
||||
|
||||
_, _, err = cli.GetProject(projectName(cfg.LXD))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "fetching project name: %s", projectName(cfg.LXD))
|
||||
}
|
||||
cli = cli.UseProject(projectName(cfg.LXD))
|
||||
|
||||
provider := &LXD{
|
||||
ctx: ctx,
|
||||
cfg: cfg,
|
||||
cli: cli,
|
||||
controllerID: controllerID,
|
||||
imageManager: &image{
|
||||
cli: cli,
|
||||
remotes: cfg.LXD.ImageRemotes,
|
||||
},
|
||||
}
|
||||
|
|
@ -116,6 +104,30 @@ type LXD struct {
|
|||
imageManager *image
|
||||
// controllerID is the ID of this controller
|
||||
controllerID string
|
||||
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
func (l *LXD) getCLI() (lxd.InstanceServer, error) {
|
||||
l.mux.Lock()
|
||||
defer l.mux.Unlock()
|
||||
|
||||
if l.cli != nil {
|
||||
return l.cli, nil
|
||||
}
|
||||
cli, err := getClientFromConfig(l.ctx, &l.cfg.LXD)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "creating LXD client")
|
||||
}
|
||||
|
||||
_, _, err = cli.GetProject(projectName(l.cfg.LXD))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "fetching project name: %s", projectName(l.cfg.LXD))
|
||||
}
|
||||
cli = cli.UseProject(projectName(l.cfg.LXD))
|
||||
l.cli = cli
|
||||
|
||||
return cli, nil
|
||||
}
|
||||
|
||||
func (l *LXD) getProfiles(flavor string) ([]string, error) {
|
||||
|
|
@ -126,7 +138,12 @@ func (l *LXD) getProfiles(flavor string) ([]string, error) {
|
|||
|
||||
set := map[string]struct{}{}
|
||||
|
||||
profiles, err := l.cli.GetProfileNames()
|
||||
cli, err := l.getCLI()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching client")
|
||||
}
|
||||
|
||||
profiles, err := cli.GetProfileNames()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetching profile names")
|
||||
}
|
||||
|
|
@ -208,7 +225,9 @@ func (l *LXD) getCreateInstanceArgs(bootstrapParams params.BootstrapInstance) (a
|
|||
return api.InstancesPost{}, errors.Wrap(err, "fetching archictecture")
|
||||
}
|
||||
|
||||
image, err := l.imageManager.EnsureImage(bootstrapParams.Image, config.LXDImageVirtualMachine, arch)
|
||||
instanceType := l.cfg.LXD.GetInstanceType()
|
||||
|
||||
image, err := l.imageManager.EnsureImage(bootstrapParams.Image, instanceType, arch, l.cli)
|
||||
if err != nil {
|
||||
return api.InstancesPost{}, errors.Wrap(err, "getting image details")
|
||||
}
|
||||
|
|
@ -223,24 +242,29 @@ func (l *LXD) getCreateInstanceArgs(bootstrapParams params.BootstrapInstance) (a
|
|||
return api.InstancesPost{}, errors.Wrap(err, "generating cloud-config")
|
||||
}
|
||||
|
||||
configMap := map[string]string{
|
||||
"user.user-data": cloudCfg,
|
||||
controllerIDKeyName: l.controllerID,
|
||||
poolIDKey: bootstrapParams.PoolID,
|
||||
}
|
||||
|
||||
if instanceType == config.LXDImageVirtualMachine {
|
||||
configMap["security.secureboot"] = l.secureBootEnabled()
|
||||
}
|
||||
|
||||
args := api.InstancesPost{
|
||||
InstancePut: api.InstancePut{
|
||||
Architecture: image.Architecture,
|
||||
Profiles: profiles,
|
||||
Description: "Github runner provisioned by garm",
|
||||
Config: map[string]string{
|
||||
"user.user-data": cloudCfg,
|
||||
"security.secureboot": l.secureBootEnabled(),
|
||||
controllerIDKeyName: l.controllerID,
|
||||
poolIDKey: bootstrapParams.PoolID,
|
||||
},
|
||||
Config: configMap,
|
||||
},
|
||||
Source: api.InstanceSource{
|
||||
Type: "image",
|
||||
Fingerprint: image.Fingerprint,
|
||||
},
|
||||
Name: bootstrapParams.Name,
|
||||
Type: api.InstanceTypeVM,
|
||||
Type: api.InstanceType(instanceType),
|
||||
}
|
||||
return args, nil
|
||||
}
|
||||
|
|
@ -254,8 +278,12 @@ func (l *LXD) AsParams() params.Provider {
|
|||
}
|
||||
|
||||
func (l *LXD) launchInstance(createArgs api.InstancesPost) error {
|
||||
cli, err := l.getCLI()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "fetching client")
|
||||
}
|
||||
// Get LXD to create the instance (background operation)
|
||||
op, err := l.cli.CreateInstance(createArgs)
|
||||
op, err := cli.CreateInstance(createArgs)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating instance")
|
||||
}
|
||||
|
|
@ -272,7 +300,7 @@ func (l *LXD) launchInstance(createArgs api.InstancesPost) error {
|
|||
Timeout: -1,
|
||||
}
|
||||
|
||||
op, err = l.cli.UpdateInstanceState(createArgs.Name, reqState, "")
|
||||
op, err = cli.UpdateInstanceState(createArgs.Name, reqState, "")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "starting instance")
|
||||
}
|
||||
|
|
@ -292,8 +320,6 @@ func (l *LXD) CreateInstance(ctx context.Context, bootstrapParams params.Bootstr
|
|||
return params.Instance{}, errors.Wrap(err, "fetching create args")
|
||||
}
|
||||
|
||||
// asJs, err := yaml.Marshal(args)
|
||||
// fmt.Println(string(asJs), err)
|
||||
if err := l.launchInstance(args); err != nil {
|
||||
return params.Instance{}, errors.Wrap(err, "creating instance")
|
||||
}
|
||||
|
|
@ -308,7 +334,11 @@ func (l *LXD) CreateInstance(ctx context.Context, bootstrapParams params.Bootstr
|
|||
|
||||
// GetInstance will return details about one instance.
|
||||
func (l *LXD) GetInstance(ctx context.Context, instanceName string) (params.Instance, error) {
|
||||
instance, _, err := l.cli.GetInstanceFull(instanceName)
|
||||
cli, err := l.getCLI()
|
||||
if err != nil {
|
||||
return params.Instance{}, errors.Wrap(err, "fetching client")
|
||||
}
|
||||
instance, _, err := cli.GetInstanceFull(instanceName)
|
||||
if err != nil {
|
||||
if isNotFoundError(err) {
|
||||
return params.Instance{}, errors.Wrapf(runnerErrors.ErrNotFound, "fetching instance: %q", err)
|
||||
|
|
@ -321,6 +351,11 @@ func (l *LXD) GetInstance(ctx context.Context, instanceName string) (params.Inst
|
|||
|
||||
// Delete instance will delete the instance in a provider.
|
||||
func (l *LXD) DeleteInstance(ctx context.Context, instance string) error {
|
||||
cli, err := l.getCLI()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "fetching client")
|
||||
}
|
||||
|
||||
if err := l.setState(instance, "stop", true); err != nil {
|
||||
if isNotFoundError(err) {
|
||||
return nil
|
||||
|
|
@ -333,7 +368,7 @@ func (l *LXD) DeleteInstance(ctx context.Context, instance string) error {
|
|||
}
|
||||
}
|
||||
|
||||
op, err := l.cli.DeleteInstance(instance)
|
||||
op, err := cli.DeleteInstance(instance)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "removing instance")
|
||||
}
|
||||
|
|
@ -347,7 +382,12 @@ func (l *LXD) DeleteInstance(ctx context.Context, instance string) error {
|
|||
|
||||
// ListInstances will list all instances for a provider.
|
||||
func (l *LXD) ListInstances(ctx context.Context, poolID string) ([]params.Instance, error) {
|
||||
instances, err := l.cli.GetInstancesFull(api.InstanceTypeAny)
|
||||
cli, err := l.getCLI()
|
||||
if err != nil {
|
||||
return []params.Instance{}, errors.Wrap(err, "fetching client")
|
||||
}
|
||||
|
||||
instances, err := cli.GetInstancesFull(api.InstanceTypeAny)
|
||||
if err != nil {
|
||||
return []params.Instance{}, errors.Wrap(err, "fetching instances")
|
||||
}
|
||||
|
|
@ -394,7 +434,12 @@ func (l *LXD) setState(instance, state string, force bool) error {
|
|||
Force: force,
|
||||
}
|
||||
|
||||
op, err := l.cli.UpdateInstanceState(instance, reqState, "")
|
||||
cli, err := l.getCLI()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "fetching client")
|
||||
}
|
||||
|
||||
op, err := cli.UpdateInstanceState(instance, reqState, "")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "setting state to %s", state)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -161,7 +161,13 @@ func getClientFromConfig(ctx context.Context, cfg *config.LXD) (cli lxd.Instance
|
|||
TLSClientCert: string(clientCertContents),
|
||||
TLSClientKey: string(clientKeyContents),
|
||||
}
|
||||
return lxd.ConnectLXD(cfg.URL, &connectArgs)
|
||||
|
||||
lxdCLI, err := lxd.ConnectLXD(cfg.URL, &connectArgs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "connecting to LXD")
|
||||
}
|
||||
|
||||
return lxdCLI, nil
|
||||
}
|
||||
|
||||
func projectName(cfg config.LXD) string {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue