Merge pull request #22 from monstermunchkin/issues/15
Extend GPG support and add Suite option to Source
This commit is contained in:
commit
11c776100a
@ -161,8 +161,8 @@ func run(c *cli.Context) error {
|
||||
}
|
||||
|
||||
// Download the root filesystem
|
||||
err = downloader.Run(def.Source, def.Image.Release, def.Image.Variant,
|
||||
arch, c.GlobalString("cache-dir"))
|
||||
err = downloader.Run(def.Source, def.Image.Release, arch,
|
||||
c.GlobalString("cache-dir"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error while downloading source: %s", err)
|
||||
}
|
||||
|
@ -32,9 +32,11 @@ type DefinitionImage struct {
|
||||
// A DefinitionSource specifies the download type and location
|
||||
type DefinitionSource struct {
|
||||
Downloader string `yaml:"downloader"`
|
||||
URL string `yaml:"url"`
|
||||
Keys []string `yaml:"keys"`
|
||||
URL string `yaml:"url,omitempty"`
|
||||
Keys []string `yaml:"keys,omitempty"`
|
||||
Keyserver string `yaml:"keyserver,omitempty"`
|
||||
Variant string `yaml:"variant,omitempty"`
|
||||
Suite string `yaml:"suite,omitempty"`
|
||||
}
|
||||
|
||||
// A DefinitionTargetLXC represents LXC specific files as part of the metadata.
|
||||
@ -97,6 +99,11 @@ func SetDefinitionDefaults(def *Definition) {
|
||||
if def.Source.Keyserver == "" {
|
||||
def.Source.Keyserver = "hkps.pool.sks-keyservers.net"
|
||||
}
|
||||
|
||||
// If no Source.Variant is specified, use the one in Image.Variant.
|
||||
if def.Source.Variant == "" {
|
||||
def.Source.Variant = def.Image.Variant
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateDefinition validates the given Definition.
|
||||
@ -116,14 +123,6 @@ func ValidateDefinition(def Definition) error {
|
||||
return fmt.Errorf("source.downloader must be one of %v", validDownloaders)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(def.Source.URL) == "" {
|
||||
return errors.New("source.url may not be empty")
|
||||
}
|
||||
|
||||
if len(def.Source.Keys) == 0 {
|
||||
return errors.New("source.keys may not be empty")
|
||||
}
|
||||
|
||||
validManagers := []string{
|
||||
"apk",
|
||||
"apt",
|
||||
|
@ -47,6 +47,41 @@ func TestValidateDefinition(t *testing.T) {
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"valid Definition without source.keys",
|
||||
Definition{
|
||||
Image: DefinitionImage{
|
||||
Distribution: "ubuntu",
|
||||
Release: "artful",
|
||||
},
|
||||
Source: DefinitionSource{
|
||||
Downloader: "debootstrap",
|
||||
URL: "https://ubuntu.com",
|
||||
},
|
||||
Packages: DefinitionPackages{
|
||||
Manager: "apt",
|
||||
},
|
||||
},
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"valid Defintion without source.url",
|
||||
Definition{
|
||||
Image: DefinitionImage{
|
||||
Distribution: "ubuntu",
|
||||
Release: "artful",
|
||||
},
|
||||
Source: DefinitionSource{
|
||||
Downloader: "debootstrap",
|
||||
},
|
||||
Packages: DefinitionPackages{
|
||||
Manager: "apt",
|
||||
},
|
||||
},
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"empty image.distribution",
|
||||
Definition{},
|
||||
@ -67,35 +102,6 @@ func TestValidateDefinition(t *testing.T) {
|
||||
"source.downloader must be one of .+",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"empty source.url",
|
||||
Definition{
|
||||
Image: DefinitionImage{
|
||||
Distribution: "ubuntu",
|
||||
Release: "artful",
|
||||
},
|
||||
Source: DefinitionSource{
|
||||
Downloader: "debootstrap",
|
||||
},
|
||||
},
|
||||
"source.url may not be empty",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"empty source.keys",
|
||||
Definition{
|
||||
Image: DefinitionImage{
|
||||
Distribution: "ubuntu",
|
||||
Release: "artful",
|
||||
},
|
||||
Source: DefinitionSource{
|
||||
Downloader: "debootstrap",
|
||||
URL: "https://ubuntu.com",
|
||||
},
|
||||
},
|
||||
"source.keys may not be empty",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"invalid package.manager",
|
||||
Definition{
|
||||
@ -123,6 +129,9 @@ func TestValidateDefinition(t *testing.T) {
|
||||
if !tt.shouldFail && err != nil {
|
||||
t.Fatalf("Validation failed: %s", err)
|
||||
} else if tt.shouldFail {
|
||||
if err == nil {
|
||||
t.Fatal("Expected failure")
|
||||
}
|
||||
match, _ := regexp.MatchString(tt.expected, err.Error())
|
||||
if !match {
|
||||
t.Fatalf("Validation failed: Expected '%s', got '%s'", tt.expected, err.Error())
|
||||
|
@ -57,29 +57,19 @@ func RunCommand(name string, arg ...string) error {
|
||||
|
||||
// VerifyFile verifies a file using gpg.
|
||||
func VerifyFile(signedFile, signatureFile string, keys []string, keyserver string) (bool, error) {
|
||||
var out string
|
||||
|
||||
gpgDir := filepath.Join(os.TempDir(), "distrobuilder.gpg")
|
||||
|
||||
err := os.MkdirAll(gpgDir, 0700)
|
||||
gpgDir, err := CreateGPGKeyring(keyserver, keys)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer os.RemoveAll(gpgDir)
|
||||
|
||||
out, err = lxd.RunCommand("gpg", append([]string{
|
||||
"--homedir", gpgDir, "--keyserver", keyserver, "--recv-keys"}, keys...)...)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Failed to receive keys: %s", out)
|
||||
}
|
||||
|
||||
if signatureFile != "" {
|
||||
out, err = lxd.RunCommand("gpg", "--homedir", gpgDir, "--verify", signatureFile, signedFile)
|
||||
out, err := lxd.RunCommand("gpg", "--homedir", gpgDir, "--verify", signatureFile, signedFile)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Failed to verify: %s", out)
|
||||
}
|
||||
} else {
|
||||
out, err = lxd.RunCommand("gpg", "--homedir", gpgDir, "--verify", signedFile)
|
||||
out, err := lxd.RunCommand("gpg", "--homedir", gpgDir, "--verify", signedFile)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("Failed to verify: %s", out)
|
||||
}
|
||||
@ -88,6 +78,32 @@ func VerifyFile(signedFile, signatureFile string, keys []string, keyserver strin
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CreateGPGKeyring creates a new GPG keyring.
|
||||
func CreateGPGKeyring(keyserver string, keys []string) (string, error) {
|
||||
gpgDir := filepath.Join(os.TempDir(), "distrobuilder.gpg")
|
||||
|
||||
err := os.MkdirAll(gpgDir, 0700)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
args := []string{"--homedir", gpgDir}
|
||||
|
||||
if keyserver != "" {
|
||||
args = append(args, "--keyserver", keyserver)
|
||||
}
|
||||
|
||||
args = append(args, append([]string{"--recv-keys"}, keys...)...)
|
||||
|
||||
out, err := lxd.RunCommand("gpg", args...)
|
||||
if err != nil {
|
||||
os.RemoveAll(gpgDir)
|
||||
return "", fmt.Errorf("Failed to create keyring: %s", out)
|
||||
}
|
||||
|
||||
return gpgDir, nil
|
||||
}
|
||||
|
||||
// Pack creates an xz-compressed tarball.
|
||||
func Pack(filename, path string, args ...string) error {
|
||||
return RunCommand("tar", append([]string{"-cJf", filename, "-C", path}, args...)...)
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
lxd "github.com/lxc/lxd/shared"
|
||||
)
|
||||
|
||||
func TestVerifyFile(t *testing.T) {
|
||||
@ -87,3 +89,26 @@ func TestVerifyFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateGPGKeyring(t *testing.T) {
|
||||
gpgDir, err := CreateGPGKeyring("pgp.mit.edu", []string{"0x5DE8949A899C8D99"})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if !lxd.PathExists(gpgDir) {
|
||||
t.Fatalf("Failed to create gpg directory: %s", gpgDir)
|
||||
}
|
||||
os.RemoveAll(gpgDir)
|
||||
|
||||
// This shouldn't fail either.
|
||||
gpgDir, err = CreateGPGKeyring("", []string{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if !lxd.PathExists(gpgDir) {
|
||||
t.Fatalf("Failed to create gpg directory: %s", gpgDir)
|
||||
}
|
||||
os.RemoveAll(gpgDir)
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ func NewAlpineLinuxHTTP() *AlpineLinuxHTTP {
|
||||
}
|
||||
|
||||
// Run downloads an Alpine Linux mini root filesystem.
|
||||
func (s *AlpineLinuxHTTP) Run(source shared.DefinitionSource, release, variant, arch, cacheDir string) error {
|
||||
func (s *AlpineLinuxHTTP) Run(source shared.DefinitionSource, release, arch, cacheDir string) error {
|
||||
fname := fmt.Sprintf("alpine-minirootfs-%s-%s.tar.gz", release, arch)
|
||||
tarball := fmt.Sprintf("%s/v%s/releases/%s/%s", source.URL,
|
||||
strings.Join(strings.Split(release, ".")[0:2], "."), arch, fname)
|
||||
|
@ -19,7 +19,7 @@ func NewArchLinuxHTTP() *ArchLinuxHTTP {
|
||||
}
|
||||
|
||||
// Run downloads an Arch Linux tarball.
|
||||
func (s *ArchLinuxHTTP) Run(source shared.DefinitionSource, release, variant, arch, cacheDir string) error {
|
||||
func (s *ArchLinuxHTTP) Run(source shared.DefinitionSource, release, arch, cacheDir string) error {
|
||||
fname := fmt.Sprintf("archlinux-bootstrap-%s-x86_64.tar.gz", release)
|
||||
tarball := fmt.Sprintf("%s/%s/%s", source.URL, release, fname)
|
||||
|
||||
|
@ -27,11 +27,11 @@ func NewCentOSHTTP() *CentOSHTTP {
|
||||
}
|
||||
|
||||
// Run downloads the tarball and unpacks it.
|
||||
func (s *CentOSHTTP) Run(source shared.DefinitionSource, release, variant, arch, cacheDir string) error {
|
||||
func (s *CentOSHTTP) Run(source shared.DefinitionSource, release, arch, cacheDir string) error {
|
||||
s.cacheDir = cacheDir
|
||||
baseURL := fmt.Sprintf("%s/%s/isos/%s/", source.URL, strings.Split(release, ".")[0], arch)
|
||||
|
||||
s.fname = getRelease(source.URL, release, variant, arch)
|
||||
s.fname = getRelease(source.URL, release, source.Variant, arch)
|
||||
if s.fname == "" {
|
||||
return fmt.Errorf("Couldn't get name of iso")
|
||||
}
|
||||
|
@ -16,24 +16,45 @@ func NewDebootstrap() *Debootstrap {
|
||||
}
|
||||
|
||||
// Run runs debootstrap.
|
||||
func (s *Debootstrap) Run(source shared.DefinitionSource, release, variant, arch, cacheDir string) error {
|
||||
func (s *Debootstrap) Run(source shared.DefinitionSource, release, arch, cacheDir string) error {
|
||||
var args []string
|
||||
|
||||
os.RemoveAll(filepath.Join(cacheDir, "rootfs"))
|
||||
|
||||
if variant != "" {
|
||||
args = append(args, "--variant", variant)
|
||||
if source.Variant != "" {
|
||||
args = append(args, "--variant", source.Variant)
|
||||
}
|
||||
|
||||
if arch != "" {
|
||||
args = append(args, "--arch", arch)
|
||||
}
|
||||
|
||||
if len(source.Keys) > 0 {
|
||||
gpgDir, err := shared.CreateGPGKeyring(source.Keyserver, source.Keys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(gpgDir)
|
||||
|
||||
args = append(args, "--keyring", filepath.Join(gpgDir, "pubring.kbx"))
|
||||
}
|
||||
|
||||
args = append(args, release, filepath.Join(cacheDir, "rootfs"))
|
||||
|
||||
if source.URL != "" {
|
||||
args = append(args, source.URL)
|
||||
}
|
||||
|
||||
// If source.Suite is set, create a symlink in /usr/share/debootstrap/scripts
|
||||
// pointing release to source.Suite.
|
||||
if source.Suite != "" {
|
||||
link := filepath.Join("/usr/share/debootstrap/scripts", release)
|
||||
err := os.Symlink(source.Suite, link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(link)
|
||||
}
|
||||
|
||||
return shared.RunCommand("debootstrap", args...)
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import "github.com/lxc/distrobuilder/shared"
|
||||
|
||||
// A Downloader represents a source downloader.
|
||||
type Downloader interface {
|
||||
Run(shared.DefinitionSource, string, string, string, string) error
|
||||
Run(shared.DefinitionSource, string, string, string) error
|
||||
}
|
||||
|
||||
// Get returns a Downloader.
|
||||
|
@ -25,7 +25,7 @@ func NewUbuntuHTTP() *UbuntuHTTP {
|
||||
}
|
||||
|
||||
// Run downloads the tarball and unpacks it.
|
||||
func (s *UbuntuHTTP) Run(source shared.DefinitionSource, release, variant, arch, cacheDir string) error {
|
||||
func (s *UbuntuHTTP) Run(source shared.DefinitionSource, release, arch, cacheDir string) error {
|
||||
baseURL := fmt.Sprintf("%s/releases/%s/release/", source.URL, release)
|
||||
|
||||
if strings.ContainsAny(release, "0123456789") {
|
||||
|
Loading…
x
Reference in New Issue
Block a user