github.com/Cloud-Foundations/Dominator@v0.3.4/imagebuilder/builder/bootstrapImage.go (about)

     1  package builder
     2  
     3  import (
     4  	"bufio"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"syscall"
    13  	"time"
    14  
    15  	"github.com/Cloud-Foundations/Dominator/lib/format"
    16  	"github.com/Cloud-Foundations/Dominator/lib/goroutine"
    17  	"github.com/Cloud-Foundations/Dominator/lib/image"
    18  	"github.com/Cloud-Foundations/Dominator/lib/srpc"
    19  	proto "github.com/Cloud-Foundations/Dominator/proto/imaginator"
    20  )
    21  
    22  const (
    23  	cmdPerms = syscall.S_IRWXU | syscall.S_IRGRP | syscall.S_IXGRP |
    24  		syscall.S_IROTH | syscall.S_IXOTH
    25  	dirPerms = syscall.S_IRWXU | syscall.S_IRGRP | syscall.S_IXGRP |
    26  		syscall.S_IROTH | syscall.S_IXOTH
    27  	packagerPathname = "/bin/generic-packager"
    28  )
    29  
    30  var environmentToCopy = map[string]struct{}{
    31  	"PATH":  {},
    32  	"TZ":    {},
    33  	"SHELL": {},
    34  }
    35  
    36  var environmentToSet = map[string]string{
    37  	"HOME":    "/",
    38  	"LOGNAME": "root",
    39  	"USER":    "root",
    40  }
    41  
    42  func cleanPackages(g *goroutine.Goroutine, rootDir string,
    43  	buildLog io.Writer) error {
    44  	fmt.Fprintln(buildLog, "\nCleaning packages:")
    45  	startTime := time.Now()
    46  	err := runInTarget(g, nil, buildLog, rootDir, nil, packagerPathname,
    47  		"clean")
    48  	if err != nil {
    49  		return errors.New("error cleaning: " + err.Error())
    50  	}
    51  	fmt.Fprintf(buildLog, "Package clean took: %s\n",
    52  		format.Duration(time.Since(startTime)))
    53  	return nil
    54  }
    55  
    56  func clearResolvConf(g *goroutine.Goroutine, writer io.Writer,
    57  	rootDir string) error {
    58  	return runInTarget(g, nil, writer, rootDir, nil,
    59  		"/bin/cp", "/dev/null", "/etc/resolv.conf")
    60  }
    61  
    62  func makeTempDirectory(dir, prefix string) (string, error) {
    63  	tmpDir, err := ioutil.TempDir(dir, prefix)
    64  	if err != nil {
    65  		return "", err
    66  	}
    67  	if err := os.Chmod(tmpDir, dirPerms); err != nil {
    68  		os.RemoveAll(tmpDir)
    69  		return "", err
    70  	}
    71  	return tmpDir, nil
    72  }
    73  
    74  func (stream *bootstrapStream) build(b *Builder, client srpc.ClientI,
    75  	request proto.BuildImageRequest,
    76  	buildLog buildLogger) (*image.Image, error) {
    77  	startTime := time.Now()
    78  	args := make([]string, 0, len(stream.BootstrapCommand))
    79  	rootDir, err := makeTempDirectory("",
    80  		strings.Replace(request.StreamName, "/", "_", -1))
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	defer os.RemoveAll(rootDir)
    85  	fmt.Fprintf(buildLog, "Created image working directory: %s\n", rootDir)
    86  	vg := variablesGetter(request.Variables).copy()
    87  	vg.add("dir", rootDir)
    88  	request.Variables = vg
    89  	for _, exp := range stream.BootstrapCommand {
    90  		arg := expandExpression(exp, func(name string) string {
    91  			return vg[name]
    92  		})
    93  		args = append(args, arg)
    94  	}
    95  	fmt.Fprintf(buildLog, "Running command: %s with args:\n", args[0])
    96  	for _, arg := range args[1:] {
    97  		fmt.Fprintf(buildLog, "    %s\n", arg)
    98  	}
    99  	g, err := newNamespaceTarget()
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	defer g.Quit()
   104  	err = runInTarget(g, nil, buildLog, "", nil, args[0], args[1:]...)
   105  	if err != nil {
   106  		return nil, err
   107  	} else {
   108  		packager := b.packagerTypes[stream.PackagerType]
   109  		if err := packager.writePackageInstaller(rootDir); err != nil {
   110  			return nil, err
   111  		}
   112  		if err := clearResolvConf(g, buildLog, rootDir); err != nil {
   113  			return nil, err
   114  		}
   115  		buildDuration := time.Since(startTime)
   116  		fmt.Fprintf(buildLog, "\nBuild time: %s\n",
   117  			format.Duration(buildDuration))
   118  		if err := cleanPackages(g, rootDir, buildLog); err != nil {
   119  			return nil, err
   120  		}
   121  		return packImage(g, client, request, rootDir,
   122  			stream.Filter, nil, nil, stream.imageFilter, stream.imageTags,
   123  			stream.imageTriggers, b.mtimesCopyFilter, buildLog)
   124  	}
   125  }
   126  
   127  func (packager *packagerType) writePackageInstaller(rootDir string) error {
   128  	filename := filepath.Join(rootDir, packagerPathname)
   129  	file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, cmdPerms)
   130  	if err != nil {
   131  		return err
   132  	}
   133  	defer file.Close()
   134  	writer := bufio.NewWriter(file)
   135  	defer writer.Flush()
   136  	packager.writePackageInstallerContents(writer)
   137  	return writer.Flush()
   138  }
   139  
   140  func (packager *packagerType) writePackageInstallerContents(writer io.Writer) {
   141  	fmt.Fprintln(writer, "#! /bin/sh")
   142  	fmt.Fprintln(writer, "# Created by imaginator.")
   143  	for _, line := range packager.Verbatim {
   144  		fmt.Fprintln(writer, line)
   145  	}
   146  	fmt.Fprintln(writer, "cmd=\"$1\"; shift")
   147  	writePackagerCommand(writer, "clean", packager.CleanCommand)
   148  	fmt.Fprintln(writer, `[ "$cmd" = "copy-in" ] && exec cat > "$1"`)
   149  	writePackagerCommand(writer, "install", packager.InstallCommand)
   150  	writePackagerCommand(writer, "list", packager.ListCommand.ArgList)
   151  	writePackagerCommand(writer, "remove", packager.RemoveCommand)
   152  	fmt.Fprintln(writer, `[ "$cmd" = "run" ] && exec "$@"`)
   153  	multiplier := packager.ListCommand.SizeMultiplier
   154  	if multiplier < 1 {
   155  		multiplier = 1
   156  	}
   157  	fmt.Fprintf(writer,
   158  		"[ \"$cmd\" = \"show-size-multiplier\" ] && exec echo %d\n", multiplier)
   159  	writePackagerCommand(writer, "update", packager.UpdateCommand)
   160  	writePackagerCommand(writer, "upgrade", packager.UpgradeCommand)
   161  	fmt.Fprintln(writer, "echo \"Invalid command: $cmd\"")
   162  	fmt.Fprintln(writer, "exit 2")
   163  }
   164  
   165  func writePackagerCommand(writer io.Writer, cmd string, command []string) {
   166  	if len(command) < 1 {
   167  		fmt.Fprintf(writer, "[ \"$cmd\" = \"%s\" ] && exit 0\n", cmd)
   168  	} else {
   169  		fmt.Fprintf(writer, "[ \"$cmd\" = \"%s\" ] && exec", cmd)
   170  		for _, arg := range command {
   171  			writeArgument(writer, arg)
   172  		}
   173  		fmt.Fprintf(writer, " \"$@\"\n")
   174  	}
   175  }
   176  
   177  func writeArgument(writer io.Writer, arg string) {
   178  	if len(strings.Fields(arg)) < 2 {
   179  		fmt.Fprintf(writer, " %s", arg)
   180  	} else {
   181  		lenArg := len(arg)
   182  		if lenArg > 0 && arg[lenArg-1] == '\n' {
   183  			arg = arg[:lenArg-1] + `\n`
   184  		}
   185  		fmt.Fprintf(writer, " '%s'", arg)
   186  	}
   187  }