Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
329 lines (276 sloc) 8.68 KB
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"path"
"strings"
"github.com/docker/go-units"
"github.com/go-debos/debos"
"github.com/go-debos/debos/recipe"
"github.com/go-debos/fakemachine"
"github.com/jessevdk/go-flags"
)
func checkError(context *debos.DebosContext, err error, a debos.Action, stage string) int {
if err == nil {
return 0
}
context.State = debos.Failed
log.Printf("Action `%s` failed at stage %s, error: %s", a, stage, err)
debos.DebugShell(*context)
return 1
}
func do_run(r recipe.Recipe, context *debos.DebosContext) int {
for _, a := range r.Actions {
err := a.Run(context)
// This does not stop the call of stacked Cleanup methods for other Actions
// Stack Cleanup methods
defer a.Cleanup(context)
// Check the state of Run method
if exitcode := checkError(context, err, a, "Run"); exitcode != 0 {
return exitcode
}
}
return 0
}
func warnLocalhost(variable string, value string) {
message := `WARNING: Environment variable %[1]s contains a reference to
localhost. This may not work when running from fakemachine.
Consider using an address that is valid on your network.`
if strings.Contains(value, "localhost") ||
strings.Contains(value, "127.0.0.1") ||
strings.Contains(value, "::1") {
log.Printf(message, variable)
}
}
func main() {
var context debos.DebosContext
var options struct {
ArtifactDir string `long:"artifactdir" description:"Directory for packed archives and ostree repositories (default: current directory)"`
InternalImage string `long:"internal-image" hidden:"true"`
TemplateVars map[string]string `short:"t" long:"template-var" description:"Template variables (use -t VARIABLE:VALUE syntax)"`
DebugShell bool `long:"debug-shell" description:"Fall into interactive shell on error"`
Shell string `short:"s" long:"shell" description:"Redefine interactive shell binary (default: bash)" optionsl:"" default:"/bin/bash"`
ScratchSize string `long:"scratchsize" description:"Size of disk backed scratch space"`
CPUs int `short:"c" long:"cpus" description:"Number of CPUs to use for build VM (default: 2)"`
Memory string `short:"m" long:"memory" description:"Amount of memory for build VM (default: 2048MB)"`
ShowBoot bool `long:"show-boot" description:"Show boot/console messages from the fake machine"`
EnvironVars map[string]string `short:"e" long:"environ-var" description:"Environment variables (use -e VARIABLE:VALUE syntax)"`
}
// These are the environment variables that will be detected on the
// host and propagated to fakemachine. These are listed lower case, but
// they are detected and configured in both lower case and upper case.
var environ_vars = [...]string {
"http_proxy",
"https_proxy",
"ftp_proxy",
"rsync_proxy",
"all_proxy",
"no_proxy",
}
var exitcode int = 0
// Allow to run all deferred calls prior to os.Exit()
defer func() {
os.Exit(exitcode)
}()
parser := flags.NewParser(&options, flags.Default)
args, err := parser.Parse()
if err != nil {
flagsErr, ok := err.(*flags.Error)
if ok && flagsErr.Type == flags.ErrHelp {
return
} else {
fmt.Printf("%v\n", flagsErr)
exitcode = 1
return
}
}
if len(args) != 1 {
log.Println("No recipe given!")
exitcode = 1
return
}
// Set interactive shell binary only if '--debug-shell' options passed
if options.DebugShell {
context.DebugShell = options.Shell
}
file := args[0]
file = debos.CleanPath(file)
r := recipe.Recipe{}
if _, err := os.Stat(file); os.IsNotExist(err) {
log.Println(err)
exitcode = 1
return
}
if err := r.Parse(file, options.TemplateVars); err != nil {
log.Println(err)
exitcode = 1
return
}
/* If fakemachine is supported the outer fake machine will never use the
* scratchdir, so just set it to /scratch as a dummy to prevent the
* outer debos creating a temporary direction */
if fakemachine.InMachine() || fakemachine.Supported() {
context.Scratchdir = "/scratch"
} else {
log.Printf("fakemachine not supported, running on the host!")
cwd, _ := os.Getwd()
context.Scratchdir, err = ioutil.TempDir(cwd, ".debos-")
defer os.RemoveAll(context.Scratchdir)
}
context.Rootdir = path.Join(context.Scratchdir, "root")
context.Image = options.InternalImage
context.RecipeDir = path.Dir(file)
context.Artifactdir = options.ArtifactDir
if context.Artifactdir == "" {
context.Artifactdir, _ = os.Getwd()
}
context.Artifactdir = debos.CleanPath(context.Artifactdir)
// Initialise origins map
context.Origins = make(map[string]string)
context.Origins["artifacts"] = context.Artifactdir
context.Origins["filesystem"] = context.Rootdir
context.Origins["recipe"] = context.RecipeDir
context.Architecture = r.Architecture
context.State = debos.Success
// Initialize environment variables map
context.EnvironVars = make(map[string]string)
// First add variables from host
for _, e := range environ_vars {
lowerVar := strings.ToLower(e) // lowercase not really needed
lowerVal := os.Getenv(lowerVar)
if lowerVal != "" {
context.EnvironVars[lowerVar] = lowerVal
}
upperVar := strings.ToUpper(e)
upperVal := os.Getenv(upperVar)
if upperVal != "" {
context.EnvironVars[upperVar] = upperVal
}
}
// Then add/overwrite with variables from command line
for k, v := range options.EnvironVars {
// Allows the user to unset environ variables with -e
if v == "" {
delete(context.EnvironVars, k)
} else {
context.EnvironVars[k] = v
}
}
for _, a := range r.Actions {
err = a.Verify(&context)
if exitcode = checkError(&context, err, a, "Verify"); exitcode != 0 {
return
}
}
if !fakemachine.InMachine() && fakemachine.Supported() {
m := fakemachine.NewMachine()
var args []string
if options.Memory == "" {
// Set default memory size for fakemachine
options.Memory = "2Gb"
}
memsize, err := units.RAMInBytes(options.Memory)
if err != nil {
fmt.Printf("Couldn't parse memory size: %v\n", err)
exitcode = 1
return
}
m.SetMemory(int(memsize / 1024 / 1024))
if options.CPUs == 0 {
// Set default CPU count for fakemachine
options.CPUs = 2
}
m.SetNumCPUs(options.CPUs)
if options.ScratchSize != "" {
size, err := units.FromHumanSize(options.ScratchSize)
if err != nil {
fmt.Printf("Couldn't parse scratch size: %v\n", err)
exitcode = 1
return
}
m.SetScratch(size, "")
}
m.SetShowBoot(options.ShowBoot)
// Puts in a format that is compatible with output of os.Environ()
if context.EnvironVars != nil {
EnvironString := []string{}
for k, v := range context.EnvironVars {
warnLocalhost(k, v)
EnvironString = append(EnvironString, fmt.Sprintf("%s=%s", k, v))
}
m.SetEnviron(EnvironString) // And save the resulting environ vars on m
}
m.AddVolume(context.Artifactdir)
args = append(args, "--artifactdir", context.Artifactdir)
for k, v := range options.TemplateVars {
args = append(args, "--template-var", fmt.Sprintf("%s:\"%s\"", k, v))
}
for k, v := range options.EnvironVars {
args = append(args, "--environ-var", fmt.Sprintf("%s:\"%s\"", k, v))
}
m.AddVolume(context.RecipeDir)
args = append(args, file)
if options.DebugShell {
args = append(args, "--debug-shell")
args = append(args, "--shell", fmt.Sprintf("%s", options.Shell))
}
for _, a := range r.Actions {
// Stack PostMachineCleanup methods
defer a.PostMachineCleanup(&context)
err = a.PreMachine(&context, m, &args)
if exitcode = checkError(&context, err, a, "PreMachine"); exitcode != 0 {
return
}
}
exitcode, err = m.RunInMachineWithArgs(args)
if err != nil {
fmt.Println(err)
return
}
if exitcode != 0 {
context.State = debos.Failed
return
}
for _, a := range r.Actions {
err = a.PostMachine(&context)
if exitcode = checkError(&context, err, a, "Postmachine"); exitcode != 0 {
return
}
}
log.Printf("==== Recipe done ====")
return
}
if !fakemachine.InMachine() {
for _, a := range r.Actions {
// Stack PostMachineCleanup methods
defer a.PostMachineCleanup(&context)
err = a.PreNoMachine(&context)
if exitcode = checkError(&context, err, a, "PreNoMachine"); exitcode != 0 {
return
}
}
}
// Create Rootdir
if _, err = os.Stat(context.Rootdir); os.IsNotExist(err) {
err = os.Mkdir(context.Rootdir, 0755)
if err != nil && os.IsNotExist(err) {
exitcode = 1
return
}
}
exitcode = do_run(r, &context)
if exitcode != 0 {
return
}
if !fakemachine.InMachine() {
for _, a := range r.Actions {
err = a.PostMachine(&context)
if exitcode = checkError(&context, err, a, "PostMachine"); exitcode != 0 {
return
}
}
log.Printf("==== Recipe done ====")
}
}