initial commit
Signed-off-by: Thomas Hipp <thomas.hipp@canonical.com>
This commit is contained in:
commit
64c04995b3
5
AUTHORS
Normal file
5
AUTHORS
Normal file
@ -0,0 +1,5 @@
|
||||
Unless mentioned otherwise in a specific file's header, all code in this
|
||||
project is released under the Apache 2.0 license.
|
||||
|
||||
The list of authors and contributors can be retrieved from the git
|
||||
commit history and in some cases, the file headers.
|
202
COPYING
Normal file
202
COPYING
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
14
Makefile
Normal file
14
Makefile
Normal file
@ -0,0 +1,14 @@
|
||||
.PHONY: default check
|
||||
|
||||
default:
|
||||
gofmt -s -w .
|
||||
go get -t -v -d ./...
|
||||
go install -v ./...
|
||||
@echo "distrobuilder built successfully"
|
||||
|
||||
check: default
|
||||
go get -v -x github.com/rogpeppe/godeps
|
||||
go get -v -x github.com/remyoudompheng/go-misc/deadcode
|
||||
go get -v -x github.com/golang/lint/golint
|
||||
go test -v ./...
|
||||
golint -set_exit_status ./...
|
69
README.md
Normal file
69
README.md
Normal file
@ -0,0 +1,69 @@
|
||||
# distrobuilder
|
||||
Custom image generator
|
||||
|
||||
## Example yaml file
|
||||
|
||||
```yaml
|
||||
image:
|
||||
distribution: ubuntu # required
|
||||
release: artful # required
|
||||
variant: default # optional
|
||||
description: Ubuntu Artful # optional
|
||||
expiry: 30d # optional: defaults to 30d
|
||||
arch: x86_64 # optional: defaults to local architecture
|
||||
|
||||
source:
|
||||
downloader: ubuntu-http
|
||||
url: http://cdimage.ubuntu.com/ubuntu-base
|
||||
|
||||
targets:
|
||||
lxc:
|
||||
create-message: |
|
||||
You just created an Ubuntu container (release=artful, arch=amd64, variant=default)
|
||||
|
||||
To enable sshd, run: apt-get install openssh-server
|
||||
|
||||
For security reason, container images ship without user accounts
|
||||
and without a root password.
|
||||
|
||||
Use lxc-attach or chroot directly into the rootfs to set a root password
|
||||
or create user accounts.
|
||||
config: |
|
||||
lxc.include = LXC_TEMPLATE_CONFIG/ubuntu.common.conf
|
||||
lxc.arch = x86_64
|
||||
config-user: |
|
||||
lxc.include = LXC_TEMPLATE_CONFIG/ubuntu.common.conf
|
||||
lxc.include = LXC_TEMPLATE_CONFIG/ubuntu.userns.conf
|
||||
lxc.arch = x86_64
|
||||
|
||||
files:
|
||||
# lxc: Puts the LXC_NAME placeholder in place
|
||||
# lxd: Adds a template to generate the file on create and copy
|
||||
- name: hostname
|
||||
path: /etc/hostname
|
||||
generator: hostname
|
||||
|
||||
# lxc: Puts the LXC_NAME placeholder in place
|
||||
# lxd: Adds a template to generate the file on create
|
||||
- name: hosts
|
||||
path: /etc/hosts
|
||||
generator: hosts
|
||||
|
||||
# all: Add the upstart job to deal with ttys
|
||||
- name: /etc/init/lxc-tty.conf
|
||||
generator: upstart-tty
|
||||
releases:
|
||||
- precise
|
||||
- trusty
|
||||
|
||||
packages:
|
||||
manager: apt
|
||||
|
||||
update: false
|
||||
install:
|
||||
- systemd
|
||||
- nginx
|
||||
- vim
|
||||
remove:
|
||||
- vim
|
||||
```
|
154
distrobuilder/chroot.go
Normal file
154
distrobuilder/chroot.go
Normal file
@ -0,0 +1,154 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/lxc/distrobuilder/managers"
|
||||
"github.com/lxc/distrobuilder/shared"
|
||||
)
|
||||
|
||||
func prepareChroot(cacheDir string) ([]string, error) {
|
||||
type Mount struct {
|
||||
source string
|
||||
target string
|
||||
fstype string
|
||||
flags uintptr
|
||||
data string
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
unmounts []string
|
||||
)
|
||||
|
||||
mounts := []Mount{
|
||||
{filepath.Join(cacheDir, "rootfs"), "", "tmpfs", syscall.MS_BIND, ""},
|
||||
{"proc", "proc", "proc", 0, ""},
|
||||
{"sys", "sys", "sysfs", 0, ""},
|
||||
{"udev", "dev", "devtmpfs", 0, ""},
|
||||
{"shm", "/dev/shm", "tmpfs", 0, ""},
|
||||
{"/dev/pts", "/dev/pts", "tmpfs", syscall.MS_BIND, ""},
|
||||
{"run", "/run", "tmpfs", 0, ""},
|
||||
{"tmp", "/tmp", "tmpfs", 0, ""},
|
||||
}
|
||||
|
||||
os.MkdirAll(filepath.Join(cacheDir, "rootfs", "dev", "pts"), 0755)
|
||||
|
||||
for _, mount := range mounts {
|
||||
err = syscall.Mount(mount.source, filepath.Join(cacheDir, "rootfs", mount.target),
|
||||
mount.fstype, mount.flags, mount.data)
|
||||
if err != nil {
|
||||
return unmounts, err
|
||||
}
|
||||
unmounts = append(unmounts, filepath.Join(cacheDir, "rootfs", mount.target))
|
||||
}
|
||||
|
||||
return unmounts, nil
|
||||
}
|
||||
|
||||
func setupChroot(cacheDir string) (func() error, error) {
|
||||
var err error
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = shared.Copy("/etc/resolv.conf", filepath.Join(cacheDir, "rootfs", "etc", "resolv.conf"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unmounts, err := prepareChroot(cacheDir)
|
||||
if err != nil {
|
||||
for i := len(unmounts) - 1; i >= 0; i-- {
|
||||
syscall.Unmount(unmounts[i], 0)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
syscall.Mknod(filepath.Join(cacheDir, "rootfs", "dev", "null"), 1, 3)
|
||||
|
||||
root, err := os.Open("/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = syscall.Chroot(filepath.Join(cacheDir, "rootfs"))
|
||||
if err != nil {
|
||||
root.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = syscall.Chdir("/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func() error {
|
||||
// unmount targets in reversed order
|
||||
for i := len(unmounts) - 1; i >= 0; i-- {
|
||||
syscall.Unmount(unmounts[i], 0)
|
||||
}
|
||||
|
||||
defer root.Close()
|
||||
|
||||
err = root.Chdir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = syscall.Chroot(".")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = syscall.Chdir(cwd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func manageChroot(def shared.DefinitionPackages) error {
|
||||
var err error
|
||||
|
||||
manager := managers.Get(def.Manager)
|
||||
if manager == nil {
|
||||
return fmt.Errorf("Couldn't get manager")
|
||||
}
|
||||
|
||||
err = manager.Refresh()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if def.Update {
|
||||
err = manager.Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = manager.Install(def.Install)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = manager.Remove(def.Remove)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = manager.Clean()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
199
distrobuilder/main.go
Normal file
199
distrobuilder/main.go
Normal file
@ -0,0 +1,199 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
__attribute__((constructor)) void init(void) {
|
||||
pid_t pid;
|
||||
int ret;
|
||||
|
||||
if (geteuid() != 0) {
|
||||
fprintf(stderr, "Need to run as root\n");
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
// Unshare a new mntns so our mounts don't leak
|
||||
if (unshare(CLONE_NEWNS | CLONE_NEWPID) < 0) {
|
||||
fprintf(stderr, "Failed to unshare namespaces: %s\n", strerror(errno));
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
// Prevent mount propagation back to initial namespace
|
||||
if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) < 0) {
|
||||
fprintf(stderr, "Failed to mark / private: %s\n", strerror(errno));
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
fprintf(stderr, "Failed to fork: %s\n", strerror(errno));
|
||||
_exit(1);
|
||||
} else if (pid > 0) {
|
||||
// parent
|
||||
waitpid(pid, &ret, 0);
|
||||
_exit(WEXITSTATUS(ret));
|
||||
}
|
||||
|
||||
// We're done, jump back to Go
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/lxc/distrobuilder/shared"
|
||||
"github.com/lxc/distrobuilder/sources"
|
||||
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
os.Setenv("PATH", "/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin")
|
||||
os.Setenv("SHELL", "/bin/sh")
|
||||
os.Setenv("TERM", "xterm")
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Usage = "image generator"
|
||||
// INPUT can either be a file or '-' which reads from stdin
|
||||
app.ArgsUsage = "[file|-]"
|
||||
app.HideHelp = true
|
||||
app.Action = run
|
||||
app.Flags = []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "lxc",
|
||||
Usage: "generate LXC image files",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "lxd",
|
||||
Usage: "generate LXD image files",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "plain",
|
||||
Usage: "generate plain chroot",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "unified",
|
||||
Usage: "output unified tarball for LXD images",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "cleanup",
|
||||
Usage: "clean up build directory",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "template-dir",
|
||||
Usage: "template directory",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "cache-dir",
|
||||
Usage: "cache directory",
|
||||
Value: "/var/cache/distrobuilder",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "compression",
|
||||
Usage: "compression algorithm",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "help, h",
|
||||
Usage: "show help",
|
||||
},
|
||||
}
|
||||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run(c *cli.Context) error {
|
||||
var (
|
||||
def shared.Definition
|
||||
// distro distributions.Distribution
|
||||
downloader sources.Downloader
|
||||
)
|
||||
|
||||
os.RemoveAll(c.GlobalString("cache-dir"))
|
||||
os.MkdirAll(c.GlobalString("cache-dir"), 0755)
|
||||
|
||||
def, err := getDefinition(c.Args().Get(0))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting definition: %s", err)
|
||||
}
|
||||
|
||||
downloader = sources.Get(def.Source.Downloader)
|
||||
if downloader == nil {
|
||||
return fmt.Errorf("Unsupported source downloader: %s", def.Source.Downloader)
|
||||
}
|
||||
|
||||
err = downloader.Run(def.Source.URL, def.Image.Release, def.Image.Variant,
|
||||
def.Image.Arch, c.GlobalString("cache-dir"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error while downloading source: %s", err)
|
||||
}
|
||||
|
||||
if c.GlobalBoolT("cleanup") {
|
||||
defer os.RemoveAll(c.GlobalString("cache-dir"))
|
||||
}
|
||||
|
||||
exitFunc, err := setupChroot(c.GlobalString("cache-dir"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to chroot: %s", err)
|
||||
}
|
||||
defer exitFunc()
|
||||
|
||||
err = manageChroot(def.Packages)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to run setup: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDefinition(fname string) (shared.Definition, error) {
|
||||
var (
|
||||
err error
|
||||
buf bytes.Buffer
|
||||
def shared.Definition
|
||||
)
|
||||
|
||||
if fname == "" || fname == "-" {
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
buf.WriteString(scanner.Text())
|
||||
}
|
||||
} else {
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
return def, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(&buf, f)
|
||||
if err != nil {
|
||||
return def, err
|
||||
}
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(buf.Bytes(), &def)
|
||||
if err != nil {
|
||||
return def, err
|
||||
}
|
||||
|
||||
return def, err
|
||||
}
|
25
managers/apk.go
Normal file
25
managers/apk.go
Normal file
@ -0,0 +1,25 @@
|
||||
package managers
|
||||
|
||||
// NewApk creates a new Manager instance.
|
||||
func NewApk() *Manager {
|
||||
return &Manager{
|
||||
command: "apk",
|
||||
flags: ManagerFlags{
|
||||
global: []string{
|
||||
"--no-cache",
|
||||
},
|
||||
install: []string{
|
||||
"add",
|
||||
},
|
||||
remove: []string{
|
||||
"del", "--rdepends",
|
||||
},
|
||||
refresh: []string{
|
||||
"update",
|
||||
},
|
||||
update: []string{
|
||||
"upgrade",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
28
managers/apt.go
Normal file
28
managers/apt.go
Normal file
@ -0,0 +1,28 @@
|
||||
package managers
|
||||
|
||||
// NewApt creates a new Manager instance.
|
||||
func NewApt() *Manager {
|
||||
return &Manager{
|
||||
command: "apt-get",
|
||||
flags: ManagerFlags{
|
||||
clean: []string{
|
||||
"clean",
|
||||
},
|
||||
global: []string{
|
||||
"-y",
|
||||
},
|
||||
install: []string{
|
||||
"install", "--no-install-recommends",
|
||||
},
|
||||
remove: []string{
|
||||
"remove", "--auto-remove",
|
||||
},
|
||||
refresh: []string{
|
||||
"update",
|
||||
},
|
||||
update: []string{
|
||||
"upgrade",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
109
managers/manager.go
Normal file
109
managers/manager.go
Normal file
@ -0,0 +1,109 @@
|
||||
package managers
|
||||
|
||||
import "github.com/lxc/distrobuilder/shared"
|
||||
|
||||
// ManagerFlags represents flags for all subcommands of a package manager.
|
||||
type ManagerFlags struct {
|
||||
global []string
|
||||
install []string
|
||||
remove []string
|
||||
clean []string
|
||||
update []string
|
||||
refresh []string
|
||||
}
|
||||
|
||||
// ManagerHooks represents custom hooks.
|
||||
type ManagerHooks struct {
|
||||
clean func() error
|
||||
}
|
||||
|
||||
// A Manager represents a package manager.
|
||||
type Manager struct {
|
||||
command string
|
||||
flags ManagerFlags
|
||||
hooks ManagerHooks
|
||||
}
|
||||
|
||||
// Get returns a Manager specified by name.
|
||||
func Get(name string) *Manager {
|
||||
switch name {
|
||||
case "apt":
|
||||
return NewApt()
|
||||
case "pacman":
|
||||
return NewPacman()
|
||||
case "yum":
|
||||
return NewYum()
|
||||
case "apk":
|
||||
return NewApk()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Install installs packages to the rootfs.
|
||||
func (m Manager) Install(pkgs []string) error {
|
||||
if len(m.flags.install) == 0 || pkgs == nil || len(pkgs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
args := append(m.flags.global, m.flags.install...)
|
||||
args = append(args, pkgs...)
|
||||
|
||||
return shared.RunCommand(m.command, args...)
|
||||
}
|
||||
|
||||
// Remove removes packages from the rootfs.
|
||||
func (m Manager) Remove(pkgs []string) error {
|
||||
if len(m.flags.remove) == 0 || pkgs == nil || len(pkgs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
args := append(m.flags.global, m.flags.remove...)
|
||||
args = append(args, pkgs...)
|
||||
|
||||
return shared.RunCommand(m.command, args...)
|
||||
}
|
||||
|
||||
// Clean cleans up cached files used by the package managers.
|
||||
func (m Manager) Clean() error {
|
||||
var err error
|
||||
|
||||
if len(m.flags.clean) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
args := append(m.flags.global, m.flags.clean...)
|
||||
|
||||
err = shared.RunCommand(m.command, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if m.hooks.clean != nil {
|
||||
err = m.hooks.clean()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Refresh refreshes the local package database.
|
||||
func (m Manager) Refresh() error {
|
||||
if len(m.flags.refresh) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
args := append(m.flags.global, m.flags.refresh...)
|
||||
|
||||
return shared.RunCommand(m.command, args...)
|
||||
}
|
||||
|
||||
// Update updates all packages.
|
||||
func (m Manager) Update() error {
|
||||
if len(m.flags.update) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
args := append(m.flags.global, m.flags.update...)
|
||||
|
||||
return shared.RunCommand(m.command, args...)
|
||||
}
|
89
managers/pacman.go
Normal file
89
managers/pacman.go
Normal file
@ -0,0 +1,89 @@
|
||||
package managers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/monstermunchkin/distrobuilder/shared"
|
||||
)
|
||||
|
||||
// NewPacman creates a new Manager instance.
|
||||
func NewPacman() *Manager {
|
||||
err := setMirrorlist()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// shared.RunCommand("pacman", "-Syy")
|
||||
|
||||
err = setupTrustedKeys()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Manager{
|
||||
command: "pacman",
|
||||
flags: ManagerFlags{
|
||||
clean: []string{
|
||||
"-Sc",
|
||||
},
|
||||
global: []string{
|
||||
"--noconfirm",
|
||||
},
|
||||
install: []string{
|
||||
"-S", "--needed",
|
||||
},
|
||||
remove: []string{
|
||||
"-Rcs",
|
||||
},
|
||||
refresh: []string{
|
||||
"-Syy",
|
||||
},
|
||||
update: []string{
|
||||
"-Su",
|
||||
},
|
||||
},
|
||||
hooks: ManagerHooks{
|
||||
clean: func() error {
|
||||
return os.RemoveAll("/var/cache/pacman/pkg")
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setupTrustedKeys() error {
|
||||
var err error
|
||||
|
||||
_, err = os.Stat("/etc/pacman.d/gnupg")
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = shared.RunCommand("pacman-key", "--init")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error initializing with pacman-key: %s", err)
|
||||
}
|
||||
|
||||
err = shared.RunCommand("pacman-key", "--populate", "archlinux")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error populating with pacman-key: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setMirrorlist() error {
|
||||
f, err := os.Create(filepath.Join("etc", "pacman.d", "mirrorlist"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString("Server = http://mirrors.kernel.org/archlinux/$repo/os/$arch")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
28
managers/yum.go
Normal file
28
managers/yum.go
Normal file
@ -0,0 +1,28 @@
|
||||
package managers
|
||||
|
||||
// NewYum creates a new Manager instance.
|
||||
func NewYum() *Manager {
|
||||
return &Manager{
|
||||
command: "yum",
|
||||
flags: ManagerFlags{
|
||||
clean: []string{
|
||||
"clean",
|
||||
},
|
||||
global: []string{
|
||||
"-y",
|
||||
},
|
||||
install: []string{
|
||||
"install",
|
||||
},
|
||||
remove: []string{
|
||||
"remove",
|
||||
},
|
||||
refresh: []string{
|
||||
"update",
|
||||
},
|
||||
update: []string{
|
||||
"upgrade",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
55
shared/definition.go
Normal file
55
shared/definition.go
Normal file
@ -0,0 +1,55 @@
|
||||
package shared
|
||||
|
||||
// A DefinitionPackages list packages which are to be either installed or
|
||||
// removed.
|
||||
type DefinitionPackages struct {
|
||||
Manager string `yaml:"manager"`
|
||||
Install []string `yaml:"install,omitempty"`
|
||||
Remove []string `yaml:"remove,omitempty"`
|
||||
Update bool `yaml:"update,omitempty"`
|
||||
}
|
||||
|
||||
// A DefinitionImage represents the image.
|
||||
type DefinitionImage struct {
|
||||
Description string `yaml:"description"`
|
||||
Distribution string `yaml:"distribution"`
|
||||
Release string `yaml:"release"`
|
||||
Arch string `yaml:"arch,omitempty"`
|
||||
Expiry string `yaml:"expiry,omitempty"`
|
||||
Variant string `yaml:"variant,omitempty"`
|
||||
}
|
||||
|
||||
// A DefinitionSource specifies the download type and location
|
||||
type DefinitionSource struct {
|
||||
Downloader string `yaml:"downloader"`
|
||||
URL string `yaml:"url"`
|
||||
}
|
||||
|
||||
// A DefinitionTargetLXC represents LXC specific files as part of the metadata.
|
||||
type DefinitionTargetLXC struct {
|
||||
CreateMessage string `yaml:"create-message,omitempty"`
|
||||
Config string `yaml:"config,omitempty"`
|
||||
ConfigUser string `yaml:"config-user,omitempty"`
|
||||
}
|
||||
|
||||
// A DefinitionTarget specifies target dependent files.
|
||||
type DefinitionTarget struct {
|
||||
LXC DefinitionTargetLXC `yaml:"lxc,omitempty"`
|
||||
}
|
||||
|
||||
// A DefinitionFile represents a file which is to be created inside to chroot.
|
||||
type DefinitionFile struct {
|
||||
Name string `yaml:"name"`
|
||||
Generator string `yaml:"generator"`
|
||||
Path string `yaml:"path,omitempty"`
|
||||
Releases []string `yaml:"releases,omitempty"`
|
||||
}
|
||||
|
||||
// A Definition a definition.
|
||||
type Definition struct {
|
||||
Image DefinitionImage `yaml:"image"`
|
||||
Source DefinitionSource `yaml:"source"`
|
||||
Targets DefinitionTarget `yaml:"targets,omitempty"`
|
||||
Files []DefinitionFile `yaml:"files,omitempty"`
|
||||
Packages DefinitionPackages `yaml:"packages,omitempty"`
|
||||
}
|
113
shared/net.go
Normal file
113
shared/net.go
Normal file
@ -0,0 +1,113 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
lxd "github.com/lxc/lxd/shared"
|
||||
"github.com/lxc/lxd/shared/ioprogress"
|
||||
)
|
||||
|
||||
// Download downloads a file. If a checksum file is provided will try and match
|
||||
// the hash.
|
||||
func Download(file, checksum string) error {
|
||||
var (
|
||||
client http.Client
|
||||
hash string
|
||||
err error
|
||||
)
|
||||
|
||||
if checksum != "" {
|
||||
hash, err = downloadChecksum(checksum, file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error while downloading checksum: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
imagePath := filepath.Join(os.TempDir(), filepath.Base(file))
|
||||
|
||||
stat, err := os.Stat(imagePath)
|
||||
if err == nil && stat.Size() > 0 {
|
||||
image, err := os.Open(imagePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer image.Close()
|
||||
|
||||
if checksum != "" {
|
||||
sha256 := sha256.New()
|
||||
_, err = io.Copy(sha256, image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := fmt.Sprintf("%x", sha256.Sum(nil))
|
||||
if result != hash {
|
||||
return fmt.Errorf("Hash mismatch for %s: %s != %s", imagePath, result, hash)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
image, err := os.Create(imagePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer image.Close()
|
||||
|
||||
progress := func(progress ioprogress.ProgressData) {
|
||||
fmt.Printf("%s\r", progress.Text)
|
||||
}
|
||||
|
||||
_, err = lxd.DownloadFileSha256(&client, "", progress, nil, imagePath,
|
||||
file, hash, image)
|
||||
if err != nil {
|
||||
if checksum == "" && strings.HasPrefix(err.Error(), "Hash mismatch") {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadChecksum(URL string, fname string) (string, error) {
|
||||
var client http.Client
|
||||
|
||||
tempFile, err := ioutil.TempFile(os.TempDir(), "sha256.")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
_, err = lxd.DownloadFileSha256(&client, "", nil, nil, "", URL, "", tempFile)
|
||||
// ignore hash mismatch
|
||||
if err != nil && !strings.HasPrefix(err.Error(), "Hash mismatch") {
|
||||
return "", err
|
||||
}
|
||||
|
||||
tempFile.Seek(0, 0)
|
||||
|
||||
scanner := bufio.NewScanner(tempFile)
|
||||
for scanner.Scan() {
|
||||
s := strings.Split(scanner.Text(), " ")
|
||||
//if len(s) == 2 && s[1] == fmt.Sprintf("*%s", filepath.Base(fname)) {
|
||||
matched, _ := regexp.MatchString(fmt.Sprintf(".*%s", filepath.Base(fname)), s[len(s)-1])
|
||||
if matched {
|
||||
return s[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Could not find checksum")
|
||||
}
|
44
shared/util.go
Normal file
44
shared/util.go
Normal file
@ -0,0 +1,44 @@
|
||||
package shared
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Copy copies a file.
|
||||
func Copy(src, dest string) error {
|
||||
var err error
|
||||
|
||||
srcFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
destFile, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
_, err = io.Copy(destFile, srcFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return destFile.Sync()
|
||||
}
|
||||
|
||||
// RunCommand runs a command hereby setting the SHELL and PATH env variables,
|
||||
// and redirecting the process's stdout and stderr to the real stdout and stderr
|
||||
// respectively.
|
||||
func RunCommand(name string, arg ...string) error {
|
||||
cmd := exec.Command(name, arg...)
|
||||
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
return cmd.Run()
|
||||
}
|
54
sources/alpine-http.go
Normal file
54
sources/alpine-http.go
Normal file
@ -0,0 +1,54 @@
|
||||
package sources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/lxc/distrobuilder/shared"
|
||||
lxd "github.com/lxc/lxd/shared"
|
||||
)
|
||||
|
||||
// AlpineLinuxHTTP represents the debootstrap downloader.
|
||||
type AlpineLinuxHTTP struct{}
|
||||
|
||||
// NewAlpineLinuxHTTP creates a new AlpineLinuxHTTP instance.
|
||||
func NewAlpineLinuxHTTP() *AlpineLinuxHTTP {
|
||||
return &AlpineLinuxHTTP{}
|
||||
}
|
||||
|
||||
// Run runs debootstrap.
|
||||
func (s *AlpineLinuxHTTP) Run(URL, release, variant, arch, cacheDir string) error {
|
||||
realArch := arch
|
||||
|
||||
if arch == "amd64" {
|
||||
realArch = "x86_64"
|
||||
}
|
||||
|
||||
fname := fmt.Sprintf("alpine-minirootfs-%s-%s.tar.gz", release, arch)
|
||||
|
||||
// Download
|
||||
parts := strings.Split("3.7.0", ".")
|
||||
strings.Join(parts[0:2], ".")
|
||||
err := shared.Download(URL+path.Join("/",
|
||||
fmt.Sprintf("v%s", strings.Join(strings.Split(release, ".")[0:2], ".")),
|
||||
"releases", realArch, fname), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.MkdirAll(filepath.Join(cacheDir, "rootfs"), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unpack
|
||||
err = lxd.Unpack(filepath.Join(os.TempDir(), fname), filepath.Join(cacheDir, "rootfs"), false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
50
sources/archlinux-http.go
Normal file
50
sources/archlinux-http.go
Normal file
@ -0,0 +1,50 @@
|
||||
package sources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/lxc/distrobuilder/shared"
|
||||
lxd "github.com/lxc/lxd/shared"
|
||||
)
|
||||
|
||||
// ArchLinuxHTTP represents the debootstrap downloader.
|
||||
type ArchLinuxHTTP struct{}
|
||||
|
||||
// NewArchLinuxHTTP creates a new ArchLinuxHTTP instance.
|
||||
func NewArchLinuxHTTP() *ArchLinuxHTTP {
|
||||
return &ArchLinuxHTTP{}
|
||||
}
|
||||
|
||||
// Run runs debootstrap.
|
||||
func (s *ArchLinuxHTTP) Run(URL, release, variant, arch, cacheDir string) error {
|
||||
fname := fmt.Sprintf("archlinux-bootstrap-%s-x86_64.tar.gz", release)
|
||||
|
||||
// Download
|
||||
err := shared.Download(URL+path.Join("/", release, fname), "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.MkdirAll(cacheDir, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unpack
|
||||
err = lxd.Unpack(filepath.Join(os.TempDir(), fname), cacheDir, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
os.RemoveAll(filepath.Join(cacheDir, "rootfs"))
|
||||
|
||||
err = os.Rename(filepath.Join(cacheDir, "root.x86_64"), filepath.Join(cacheDir, "rootfs"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
102
sources/centos-http.go
Normal file
102
sources/centos-http.go
Normal file
@ -0,0 +1,102 @@
|
||||
package sources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/lxc/distrobuilder/shared"
|
||||
)
|
||||
|
||||
// CentOSHTTP represents the CentOS HTTP downloader.
|
||||
type CentOSHTTP struct {
|
||||
fname string
|
||||
cacheDir string
|
||||
}
|
||||
|
||||
// NewCentOSHTTP creates a new CentOSHTTP instance.
|
||||
func NewCentOSHTTP() *CentOSHTTP {
|
||||
return &CentOSHTTP{}
|
||||
}
|
||||
|
||||
// Run downloads the tarball and unpacks it.
|
||||
func (s *CentOSHTTP) Run(URL, release, variant, arch, cacheDir string) error {
|
||||
s.cacheDir = cacheDir
|
||||
|
||||
realArch := arch
|
||||
if realArch == "amd64" {
|
||||
realArch = "x86_64"
|
||||
}
|
||||
|
||||
s.fname = getRelease(URL, release, variant, arch)
|
||||
if s.fname == "" {
|
||||
return fmt.Errorf("Couldn't get name of iso")
|
||||
}
|
||||
|
||||
err := shared.Download(
|
||||
URL+path.Join("/", strings.Split(release, ".")[0], "isos", arch, s.fname),
|
||||
URL+path.Join("/", strings.Split(release, ".")[0], "isos", arch, "sha256sum.txt"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error downloading CentOS image: %s", err)
|
||||
}
|
||||
|
||||
return s.unpack(filepath.Join(os.TempDir(), s.fname), cacheDir)
|
||||
}
|
||||
|
||||
func (s CentOSHTTP) unpack(filePath, cacheDir string) error {
|
||||
isoDir := filepath.Join(os.TempDir(), "distrobuilder", "iso")
|
||||
squashfsDir := filepath.Join(os.TempDir(), "distrobuilder", "squashfs")
|
||||
tempRootDir := filepath.Join(os.TempDir(), "distrobuilder", "rootfs")
|
||||
|
||||
os.RemoveAll(filepath.Join(cacheDir, "rootfs"))
|
||||
|
||||
os.MkdirAll(isoDir, 0755)
|
||||
os.MkdirAll(tempRootDir, 0755)
|
||||
defer os.RemoveAll(filepath.Join(os.TempDir(), "distrobuilder"))
|
||||
|
||||
// this is easier than doing the whole loop thing ourselves
|
||||
err := shared.RunCommand("mount", filePath, isoDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Unmount(isoDir, 0)
|
||||
|
||||
err = shared.RunCommand("unsquashfs", "-d", squashfsDir,
|
||||
filepath.Join(isoDir, "LiveOS/squashfs.img"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = shared.RunCommand("mount", filepath.Join(squashfsDir, "LiveOS", "rootfs.img"), tempRootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Unmount(tempRootDir, 0)
|
||||
|
||||
err = shared.RunCommand("rsync", "-qa", tempRootDir, cacheDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRelease(URL, release, variant, arch string) string {
|
||||
resp, err := http.Get(URL + path.Join("/", strings.Split(release, ".")[0], "isos", arch))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
regex := regexp.MustCompile(fmt.Sprintf("CentOS-%s-%s-(?i:%s)(-\\d+)?.iso", release, arch, variant))
|
||||
return regex.FindString(string(body))
|
||||
}
|
40
sources/debootstrap.go
Normal file
40
sources/debootstrap.go
Normal file
@ -0,0 +1,40 @@
|
||||
package sources
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/lxc/distrobuilder/shared"
|
||||
)
|
||||
|
||||
// Debootstrap represents the debootstrap downloader.
|
||||
type Debootstrap struct{}
|
||||
|
||||
// NewDebootstrap creates a new Debootstrap instance.
|
||||
func NewDebootstrap() *Debootstrap {
|
||||
return &Debootstrap{}
|
||||
}
|
||||
|
||||
// Run runs debootstrap.
|
||||
func (s *Debootstrap) Run(URL, release, variant, arch, cacheDir string) error {
|
||||
var args []string
|
||||
|
||||
os.RemoveAll(filepath.Join(cacheDir, "rootfs"))
|
||||
|
||||
if variant != "" {
|
||||
args = append(args, "--variant", variant)
|
||||
}
|
||||
|
||||
if arch != arch {
|
||||
args = append(args, "--arch", arch)
|
||||
}
|
||||
|
||||
args = append(args, release, filepath.Join(cacheDir, "rootfs"))
|
||||
|
||||
if URL != "" {
|
||||
args = append(args, URL)
|
||||
}
|
||||
|
||||
return shared.RunCommand("debootstrap", args...)
|
||||
|
||||
}
|
24
sources/source.go
Normal file
24
sources/source.go
Normal file
@ -0,0 +1,24 @@
|
||||
package sources
|
||||
|
||||
// A Downloader represents a source downloader.
|
||||
type Downloader interface {
|
||||
Run(string, string, string, string, string) error
|
||||
}
|
||||
|
||||
// Get returns a Downloader.
|
||||
func Get(name string) Downloader {
|
||||
switch name {
|
||||
case "ubuntu-http":
|
||||
return NewUbuntuHTTP()
|
||||
case "debootstrap":
|
||||
return NewDebootstrap()
|
||||
case "archlinux-http":
|
||||
return NewArchLinuxHTTP()
|
||||
case "centos-http":
|
||||
return NewCentOSHTTP()
|
||||
case "alpinelinux-http":
|
||||
return NewAlpineLinuxHTTP()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
83
sources/ubuntu-http.go
Normal file
83
sources/ubuntu-http.go
Normal file
@ -0,0 +1,83 @@
|
||||
package sources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/lxc/distrobuilder/shared"
|
||||
lxd "github.com/lxc/lxd/shared"
|
||||
)
|
||||
|
||||
// UbuntuHTTP represents the Ubuntu HTTP downloader.
|
||||
type UbuntuHTTP struct {
|
||||
fname string
|
||||
}
|
||||
|
||||
// NewUbuntuHTTP creates a new UbuntuHTTP instance.
|
||||
func NewUbuntuHTTP() *UbuntuHTTP {
|
||||
return &UbuntuHTTP{}
|
||||
}
|
||||
|
||||
// Run downloads the tarball and unpacks it.
|
||||
func (s *UbuntuHTTP) Run(URL, release, variant, arch, cacheDir string) error {
|
||||
realArch := arch
|
||||
if realArch == "x86_64" {
|
||||
realArch = "amd64"
|
||||
}
|
||||
|
||||
if strings.ContainsAny(release, "0123456789") {
|
||||
s.fname = fmt.Sprintf("ubuntu-base-%s-base-%s.tar.gz", release, realArch)
|
||||
} else {
|
||||
// if release is non-numerical, find the latest release
|
||||
s.fname = getLatestRelease(URL, release, realArch)
|
||||
if s.fname == "" {
|
||||
return fmt.Errorf("Couldn't find latest release")
|
||||
}
|
||||
}
|
||||
|
||||
err := shared.Download(
|
||||
URL+path.Join("/", "releases", release, "release", s.fname),
|
||||
URL+path.Join("/", "releases", release, "release", "SHA256SUMS"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error downloading Ubuntu image: %s", err)
|
||||
}
|
||||
|
||||
return s.unpack(filepath.Join(os.TempDir(), s.fname), filepath.Join(cacheDir, "rootfs"))
|
||||
}
|
||||
|
||||
func (s UbuntuHTTP) unpack(filePath, rootDir string) error {
|
||||
os.RemoveAll(rootDir)
|
||||
os.MkdirAll(rootDir, 0755)
|
||||
|
||||
err := lxd.Unpack(filePath, rootDir, false, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to unpack tarball: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getLatestRelease(URL, release, arch string) string {
|
||||
resp, err := http.Get(URL + path.Join("/", "releases", release, "release"))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
regex := regexp.MustCompile(fmt.Sprintf("ubuntu-base-\\d{2}\\.\\d{2}(\\.\\d+)?-base-%s.tar.gz", arch))
|
||||
releases := regex.FindAllString(string(body), -1)
|
||||
|
||||
if len(releases) > 1 {
|
||||
return string(releases[len(releases)-1])
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user