Remove the LXD internal provider

Canonical have relicensed the LXD project to AGPLv3. This means that we can
no longer update the go LXD client without re-licensing GARM as AGPLv3. This
is not desirable or possible.

The existing code seems to be Apache 2.0 and all code that has already been
contributed seems to stay as Apache 2.0, but new contributions from Canonical
employees will be AGPLv3.

We cannot risc including AGPLv3 code now or in the future, so we will separate
the LXD provider into its own project which can be AGPLv3. GARM will simply
execute the external provider.

If the client code of LXD will ever be split from the main project and re-licensed
as Apache 2.0 or a compatible license, we will reconsider adding it back as a
native provider. Although in the long run, I believe external providers will
be the only option as they are easier to write, easier to maintain and safer to
ship (a bug in the provider does not crash GARM itself).

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
Gabriel Adrian Samfira 2023-12-18 12:16:48 +00:00
parent fc7a7dde35
commit affb56f9a0
515 changed files with 45 additions and 95062 deletions

View file

@ -223,7 +223,6 @@ type Provider struct {
// tokens to be used. This may happen if a provider has not yet been updated to support
// JIT configuration.
DisableJITConfig bool `toml:"disable_jit_config" json:"disable-jit-config"`
LXD LXD `toml:"lxd" json:"lxd"`
External External `toml:"external" json:"external"`
}
@ -233,10 +232,6 @@ func (p *Provider) Validate() error {
}
switch p.ProviderType {
case params.LXDProvider:
if err := p.LXD.Validate(); err != nil {
return errors.Wrap(err, "validating LXD provider info")
}
case params.ExternalProvider:
if err := p.External.Validate(); err != nil {
return errors.Wrap(err, "validating external provider info")

View file

@ -20,7 +20,6 @@ import (
"testing"
"time"
"github.com/cloudbase/garm/params"
"github.com/cloudbase/garm/util/appdefaults"
"github.com/stretchr/testify/require"
)
@ -76,15 +75,7 @@ func getDefaultDatabaseConfig(dir string) Database {
}
func getDefaultProvidersConfig() []Provider {
lxdConfig := getDefaultLXDConfig()
return []Provider{
{
Name: "test_lxd",
ProviderType: params.LXDProvider,
Description: "test LXD provider",
LXD: lxdConfig,
},
}
return []Provider{}
}
func getDefaultGithubConfig() []Github {

View file

@ -1,163 +0,0 @@
// Copyright 2022 Cloudbase Solutions SRL
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package config
import (
"fmt"
"net/url"
"os"
"github.com/pkg/errors"
)
type LXDRemoteProtocol string
type LXDImageType string
func (l LXDImageType) String() string {
return string(l)
}
const (
SimpleStreams LXDRemoteProtocol = "simplestreams"
LXDImageVirtualMachine LXDImageType = "virtual-machine"
LXDImageContainer LXDImageType = "container"
)
// LXDImageRemote holds information about a remote server from which LXD can fetch
// OS images. Typically this will be a simplestreams server.
type LXDImageRemote struct {
Address string `toml:"addr" json:"addr"`
Public bool `toml:"public" json:"public"`
Protocol LXDRemoteProtocol `toml:"protocol" json:"protocol"`
InsecureSkipVerify bool `toml:"skip_verify" json:"skip-verify"`
}
func (l *LXDImageRemote) Validate() error {
if l.Protocol != SimpleStreams {
// Only supports simplestreams for now.
return fmt.Errorf("invalid remote protocol %s. Supported protocols: %s", l.Protocol, SimpleStreams)
}
if l.Address == "" {
return fmt.Errorf("missing address")
}
url, err := url.ParseRequestURI(l.Address)
if err != nil {
return errors.Wrap(err, "validating address")
}
if url.Scheme != "http" && url.Scheme != "https" {
return fmt.Errorf("address must be http or https")
}
return nil
}
// LXD holds connection information for an LXD cluster.
type LXD struct {
// UnixSocket is the path on disk to the LXD unix socket. If defined,
// this is prefered over connecting via HTTPs.
UnixSocket string `toml:"unix_socket_path" json:"unix-socket-path"`
// Project name is the name of the project in which this runner will create
// instances. If this option is not set, the default project will be used.
// The project used here, must have all required profiles created by you
// beforehand. For LXD, the "flavor" used in the runner definition for a pool
// equates to a profile in the desired project.
ProjectName string `toml:"project_name" json:"project-name"`
// IncludeDefaultProfile specifies whether or not this provider will always add
// the "default" profile to any newly created instance.
IncludeDefaultProfile bool `toml:"include_default_profile" json:"include-default-profile"`
// URL holds the URL of the remote LXD server.
// example: https://10.10.10.1:8443/
URL string `toml:"url" json:"url"`
// ClientCertificate is the x509 client certificate path used for authentication.
ClientCertificate string `toml:"client_certificate" json:"client_certificate"`
// ClientKey is the key used for client certificate authentication.
ClientKey string `toml:"client_key" json:"client-key"`
// TLS certificate of the remote server. If not specified, the system CA is used.
TLSServerCert string `toml:"tls_server_certificate" json:"tls-server-certificate"`
// TLSCA is the TLS CA certificate when running LXD in PKI mode.
TLSCA string `toml:"tls_ca" json:"tls-ca"`
// ImageRemotes is a map to a set of remote image repositories we can use to
// download images.
ImageRemotes map[string]LXDImageRemote `toml:"image_remotes" json:"image-remotes"`
// SecureBoot enables secure boot for VMs spun up using this provider.
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 {
if l.UnixSocket != "" {
if _, err := os.Stat(l.UnixSocket); err != nil {
return fmt.Errorf("could not access unix socket %s: %q", l.UnixSocket, err)
}
return nil
}
if l.URL == "" {
return fmt.Errorf("unix_socket or address must be specified")
}
url, err := url.ParseRequestURI(l.URL)
if err != nil {
return fmt.Errorf("invalid LXD URL")
}
if url.Scheme != "https" {
return fmt.Errorf("address must be https")
}
if l.ClientCertificate == "" || l.ClientKey == "" {
return fmt.Errorf("client_certificate and client_key are mandatory")
}
if _, err := os.Stat(l.ClientCertificate); err != nil {
return fmt.Errorf("failed to access client certificate %s: %q", l.ClientCertificate, err)
}
if _, err := os.Stat(l.ClientKey); err != nil {
return fmt.Errorf("failed to access client key %s: %q", l.ClientKey, err)
}
if l.TLSServerCert != "" {
if _, err := os.Stat(l.TLSServerCert); err != nil {
return fmt.Errorf("failed to access tls_server_certificate %s: %q", l.TLSServerCert, err)
}
}
for name, val := range l.ImageRemotes {
if err := val.Validate(); err != nil {
return fmt.Errorf("remote %s is invalid: %s", name, err)
}
}
return nil
}

View file

@ -1,175 +0,0 @@
// Copyright 2022 Cloudbase Solutions SRL
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package config
import (
"testing"
"github.com/stretchr/testify/require"
)
func getDefaultLXDImageRemoteConfig() LXDImageRemote {
return LXDImageRemote{
Address: "https://cloud-images.ubuntu.com/releases",
Public: true,
Protocol: SimpleStreams,
InsecureSkipVerify: false,
}
}
func getDefaultLXDConfig() LXD {
remote := getDefaultLXDImageRemoteConfig()
return LXD{
URL: "https://example.com:8443",
ProjectName: "default",
IncludeDefaultProfile: false,
ClientCertificate: "../testdata/lxd/certs/client.crt",
ClientKey: "../testdata/lxd/certs/client.key",
TLSServerCert: "../testdata/lxd/certs/servercert.crt",
ImageRemotes: map[string]LXDImageRemote{
"default": remote,
},
SecureBoot: false,
}
}
func TestLXDRemote(t *testing.T) {
cfg := getDefaultLXDImageRemoteConfig()
err := cfg.Validate()
require.Nil(t, err)
}
func TestLXDRemoteEmptyAddress(t *testing.T) {
cfg := getDefaultLXDImageRemoteConfig()
cfg.Address = ""
err := cfg.Validate()
require.NotNil(t, err)
require.EqualError(t, err, "missing address")
}
func TestLXDRemoteInvalidAddress(t *testing.T) {
cfg := getDefaultLXDImageRemoteConfig()
cfg.Address = "bogus address"
err := cfg.Validate()
require.NotNil(t, err)
require.EqualError(t, err, "validating address: parse \"bogus address\": invalid URI for request")
}
func TestLXDRemoteIvalidAddressScheme(t *testing.T) {
cfg := getDefaultLXDImageRemoteConfig()
cfg.Address = "ftp://whatever"
err := cfg.Validate()
require.NotNil(t, err)
require.EqualError(t, err, "address must be http or https")
}
func TestLXDConfig(t *testing.T) {
cfg := getDefaultLXDConfig()
err := cfg.Validate()
require.Nil(t, err)
}
func TestLXDWithInvalidUnixSocket(t *testing.T) {
cfg := getDefaultLXDConfig()
cfg.UnixSocket = "bogus unix socket"
err := cfg.Validate()
require.NotNil(t, err)
require.EqualError(t, err, "could not access unix socket bogus unix socket: \"stat bogus unix socket: no such file or directory\"")
}
func TestMissingUnixSocketAndMissingURL(t *testing.T) {
cfg := getDefaultLXDConfig()
cfg.URL = ""
cfg.UnixSocket = ""
err := cfg.Validate()
require.NotNil(t, err)
require.EqualError(t, err, "unix_socket or address must be specified")
}
func TestInvalidLXDURL(t *testing.T) {
cfg := getDefaultLXDConfig()
cfg.URL = "bogus"
err := cfg.Validate()
require.NotNil(t, err)
require.EqualError(t, err, "invalid LXD URL")
}
func TestLXDURLIsHTTPS(t *testing.T) {
cfg := getDefaultLXDConfig()
cfg.URL = "http://example.com"
err := cfg.Validate()
require.NotNil(t, err)
require.EqualError(t, err, "address must be https")
}
func TestMissingClientCertOrKey(t *testing.T) {
cfg := getDefaultLXDConfig()
cfg.ClientKey = ""
err := cfg.Validate()
require.NotNil(t, err)
require.EqualError(t, err, "client_certificate and client_key are mandatory")
cfg = getDefaultLXDConfig()
cfg.ClientCertificate = ""
err = cfg.Validate()
require.NotNil(t, err)
require.EqualError(t, err, "client_certificate and client_key are mandatory")
}
func TestLXDIvalidCertOrKeyPaths(t *testing.T) {
cfg := getDefaultLXDConfig()
cfg.ClientCertificate = "/i/am/not/here"
err := cfg.Validate()
require.NotNil(t, err)
require.EqualError(t, err, "failed to access client certificate /i/am/not/here: \"stat /i/am/not/here: no such file or directory\"")
cfg.ClientCertificate = "../testdata/lxd/certs/client.crt"
cfg.ClientKey = "/me/neither"
err = cfg.Validate()
require.NotNil(t, err)
require.EqualError(t, err, "failed to access client key /me/neither: \"stat /me/neither: no such file or directory\"")
}
func TestLXDInvalidServerCertPath(t *testing.T) {
cfg := getDefaultLXDConfig()
cfg.TLSServerCert = "/not/a/valid/server/cert/path"
err := cfg.Validate()
require.NotNil(t, err)
require.EqualError(t, err, "failed to access tls_server_certificate /not/a/valid/server/cert/path: \"stat /not/a/valid/server/cert/path: no such file or directory\"")
}
func TestInvalidLXDImageRemotes(t *testing.T) {
cfg := getDefaultLXDConfig()
cfg.ImageRemotes["default"] = LXDImageRemote{
Protocol: LXDRemoteProtocol("bogus"),
}
err := cfg.Validate()
require.NotNil(t, err)
require.EqualError(t, err, "remote default is invalid: invalid remote protocol bogus. Supported protocols: simplestreams")
}