2015-08-18 14:07:00 +00:00
package control
import (
2016-12-01 03:55:19 +00:00
"bufio"
"bytes"
2015-08-18 14:07:00 +00:00
"fmt"
2016-12-01 03:55:19 +00:00
"io"
"io/ioutil"
2015-08-18 14:07:00 +00:00
"os"
"os/exec"
2016-12-01 03:55:19 +00:00
"path/filepath"
2016-12-07 11:56:32 +00:00
"strconv"
2016-09-27 00:27:53 +00:00
"strings"
2016-12-01 03:55:19 +00:00
"text/template"
2015-08-18 14:07:00 +00:00
2016-11-23 10:49:35 +00:00
"github.com/rancher/os/log"
2015-08-18 14:07:00 +00:00
"github.com/codegangsta/cli"
2015-10-12 11:50:17 +00:00
"github.com/rancher/os/cmd/power"
"github.com/rancher/os/config"
2016-12-01 03:55:19 +00:00
"github.com/rancher/os/dfs" // TODO: move CopyFile into util or something.
2015-10-12 11:50:17 +00:00
"github.com/rancher/os/util"
2015-08-18 14:07:00 +00:00
)
2016-12-01 03:55:19 +00:00
type MenuEntry struct {
Name , bootDir , Version , KernelArgs , Append string
}
type bootVars struct {
baseName , bootDir string
Timeout uint
Fallback int
Entries [ ] MenuEntry
}
2015-08-18 14:07:00 +00:00
var installCommand = cli . Command {
Name : "install" ,
Usage : "install RancherOS to disk" ,
HideHelp : true ,
Action : installAction ,
Flags : [ ] cli . Flag {
cli . StringFlag {
2016-10-12 12:41:52 +00:00
// TODO: need to validate ? -i rancher/os:v0.3.1 just sat there.
Name : "image, i" ,
Usage : ` install from a certain image ( e . g . , ' rancher / os : v0 .7 .0 ' )
use ' ros os list ' to see what versions are available . ` ,
2015-08-18 14:07:00 +00:00
} ,
cli . StringFlag {
Name : "install-type, t" ,
2016-12-15 01:54:43 +00:00
Usage : ` generic : ( Default ) Creates 1 ext4 partition and installs RancherOS ( syslinux )
2016-12-14 11:01:58 +00:00
amazon - ebs : Installs RancherOS and sets up PV - GRUB
2017-01-05 11:29:09 +00:00
gptsyslinux : partition and format disk ( gpt ) , then install RancherOS and setup Syslinux
2016-12-14 11:01:58 +00:00
` ,
2015-08-18 14:07:00 +00:00
} ,
cli . StringFlag {
Name : "cloud-config, c" ,
Usage : "cloud-config yml file - needed for SSH authorized keys" ,
} ,
cli . StringFlag {
Name : "device, d" ,
Usage : "storage device" ,
} ,
cli . BoolFlag {
Name : "force, f" ,
Usage : "[ DANGEROUS! Data loss can happen ] partition/format without prompting" ,
} ,
cli . BoolFlag {
Name : "no-reboot" ,
Usage : "do not reboot after install" ,
} ,
2016-09-27 00:27:53 +00:00
cli . StringFlag {
Name : "append, a" ,
Usage : "append additional kernel parameters" ,
} ,
2016-12-07 03:33:43 +00:00
cli . BoolFlag {
Name : "mountiso" ,
Usage : "mount the iso to get kernel and initrd" ,
} ,
2015-08-18 14:07:00 +00:00
} ,
}
2016-05-17 03:36:08 +00:00
func installAction ( c * cli . Context ) error {
2015-08-18 14:07:00 +00:00
if c . Args ( ) . Present ( ) {
log . Fatalf ( "invalid arguments %v" , c . Args ( ) )
}
device := c . String ( "device" )
if device == "" {
log . Fatal ( "Can not proceed without -d <dev> specified" )
}
image := c . String ( "image" )
2016-06-02 01:41:55 +00:00
cfg := config . LoadConfig ( )
2015-08-18 14:07:00 +00:00
if image == "" {
2016-11-28 08:06:00 +00:00
image = cfg . Rancher . Upgrade . Image + ":" + config . Version + config . Suffix
2015-08-18 14:07:00 +00:00
}
installType := c . String ( "install-type" )
if installType == "" {
log . Info ( "No install type specified...defaulting to generic" )
installType = "generic"
}
cloudConfig := c . String ( "cloud-config" )
if cloudConfig == "" {
2016-12-01 03:55:19 +00:00
log . Warn ( "Cloud-config not provided: you might need to provide cloud-config on bootDir with ssh_authorized_keys" )
2015-08-18 14:07:00 +00:00
} else {
uc := "/opt/user_config.yml"
if err := util . FileCopy ( cloudConfig , uc ) ; err != nil {
log . WithFields ( log . Fields { "cloudConfig" : cloudConfig } ) . Fatal ( "Failed to copy cloud-config" )
}
cloudConfig = uc
}
2016-12-01 03:55:19 +00:00
kappend := strings . TrimSpace ( c . String ( "append" ) )
2015-08-18 14:07:00 +00:00
force := c . Bool ( "force" )
reboot := ! c . Bool ( "no-reboot" )
2016-12-07 03:33:43 +00:00
mountiso := c . Bool ( "mountiso" )
2015-08-18 14:07:00 +00:00
2016-12-07 03:33:43 +00:00
if err := runInstall ( image , installType , cloudConfig , device , kappend , force , reboot , mountiso ) ; err != nil {
2015-08-18 14:07:00 +00:00
log . WithFields ( log . Fields { "err" : err } ) . Fatal ( "Failed to run install" )
}
2016-05-17 03:36:08 +00:00
return nil
2015-08-18 14:07:00 +00:00
}
2016-12-07 03:33:43 +00:00
func runInstall ( image , installType , cloudConfig , device , kappend string , force , reboot , mountiso bool ) error {
2015-08-18 14:07:00 +00:00
fmt . Printf ( "Installing from %s\n" , image )
if ! force {
2016-10-12 12:41:52 +00:00
if ! yes ( "Continue" ) {
2016-12-01 03:55:19 +00:00
log . Infof ( "Not continuing with installation due to user not saying 'yes'" )
2015-08-18 14:07:00 +00:00
os . Exit ( 1 )
}
}
2016-12-14 11:01:58 +00:00
diskType := "msdos"
if installType == "gptsyslinux" {
diskType = "gpt"
}
2016-12-07 11:56:32 +00:00
// Versions before 0.8.0-rc2 use the old calling convention (from the lay-down-os shell script)
imageVersion := strings . TrimPrefix ( image , "rancher/os:v" )
if image != imageVersion {
2016-12-15 01:54:43 +00:00
log . Infof ( "user spcified different install image: %s != %s" , image , imageVersion )
2016-12-07 11:56:32 +00:00
imageVersion = strings . Replace ( imageVersion , "-" , "." , - 1 )
vArray := strings . Split ( imageVersion , "." )
v , _ := strconv . ParseFloat ( vArray [ 0 ] + "." + vArray [ 1 ] , 32 )
if v < 0.8 || imageVersion == "0.8.0-rc1" {
log . Infof ( "starting installer container for %s" , image )
2016-12-15 01:54:43 +00:00
if installType == "generic" ||
installType == "syslinux" ||
installType == "gptsyslinux" {
2016-12-07 11:56:32 +00:00
cmd := exec . Command ( "system-docker" , "run" , "--net=host" , "--privileged" , "--volumes-from=all-volumes" ,
"--entrypoint=/scripts/set-disk-partitions" , image , device , diskType )
cmd . Stdout , cmd . Stderr = os . Stdout , os . Stderr
if err := cmd . Run ( ) ; err != nil {
return err
}
}
cmd := exec . Command ( "system-docker" , "run" , "--net=host" , "--privileged" , "--volumes-from=user-volumes" ,
2016-12-15 01:54:43 +00:00
"--volumes-from=command-volumes" , image , "-d" , device , "-t" , installType , "-c" , cloudConfig ,
"-a" , kappend )
2016-12-01 03:55:19 +00:00
cmd . Stdout , cmd . Stderr = os . Stdout , os . Stderr
if err := cmd . Run ( ) ; err != nil {
return err
}
2016-12-07 11:56:32 +00:00
return nil
2016-12-01 03:55:19 +00:00
}
2015-08-18 14:07:00 +00:00
}
2016-12-01 03:55:19 +00:00
2016-12-06 03:16:18 +00:00
useIso := false
2016-12-07 03:33:43 +00:00
if ! mountiso {
if _ , err := os . Stat ( "/dist/initrd" ) ; os . IsNotExist ( err ) {
if err = mountBootIso ( ) ; err == nil {
if _ , err := os . Stat ( "/bootiso/rancheros/" ) ; err == nil {
cmd := exec . Command ( "system-docker" , "load" , "-i" , "/bootiso/rancheros/installer.tar.gz" )
cmd . Stdout , cmd . Stderr = os . Stdout , os . Stderr
if err := cmd . Run ( ) ; err != nil {
log . Infof ( "failed to load images from /bootiso/rancheros: %s" , err )
} else {
log . Infof ( "Loaded images from /bootiso/rancheros/installer.tar.gz" )
//TODO: add if os-installer:latest exists - we might have loaded a full installer?
useIso = true
// now use the installer image
cfg := config . LoadConfig ( )
// TODO: fix the fullinstaller Dockerfile to use the ${VERSION}${SUFFIX}
image = cfg . Rancher . Upgrade . Image + "-installer" + ":latest"
}
2016-12-01 03:55:19 +00:00
}
2016-12-07 03:33:43 +00:00
// TODO: also poke around looking for the /boot/vmlinuz and initrd...
2016-12-01 03:55:19 +00:00
}
2016-12-07 03:33:43 +00:00
log . Infof ( "starting installer container for %s (new)" , image )
installerCmd := [ ] string {
"run" , "--rm" , "--net=host" , "--privileged" ,
// bind mount host fs to access its ros, vmlinuz, initrd and /dev (udev isn't running in container)
"-v" , "/:/host" ,
2016-12-15 01:54:43 +00:00
"--volumes-from=all-volumes" ,
2016-12-07 03:33:43 +00:00
image ,
"install" ,
"-t" , installType ,
"-d" , device ,
}
if force {
installerCmd = append ( installerCmd , "-f" )
}
if ! reboot {
installerCmd = append ( installerCmd , "--no-reboot" )
}
if cloudConfig != "" {
installerCmd = append ( installerCmd , "-c" , cloudConfig )
}
if kappend != "" {
installerCmd = append ( installerCmd , "-a" , kappend )
}
if useIso {
installerCmd = append ( installerCmd , "--mountiso" )
}
2016-12-01 03:55:19 +00:00
2016-12-07 03:33:43 +00:00
cmd := exec . Command ( "system-docker" , installerCmd ... )
log . Debugf ( "Run(%v)" , cmd )
cmd . Stdout , cmd . Stderr = os . Stdout , os . Stderr
if err := cmd . Run ( ) ; err != nil {
if useIso {
util . Unmount ( "/bootiso" )
}
return err
}
2016-12-06 03:16:18 +00:00
if useIso {
util . Unmount ( "/bootiso" )
}
2016-12-07 03:33:43 +00:00
return nil
2016-12-06 03:16:18 +00:00
}
2016-12-01 03:55:19 +00:00
}
// TODO: needs to pass the log level on to the container
log . InitLogger ( )
2016-12-07 11:56:32 +00:00
log . SetLevel ( log . InfoLevel )
2016-12-01 03:55:19 +00:00
log . Infof ( "running installation" )
2016-12-15 01:54:43 +00:00
if installType == "generic" ||
installType == "syslinux" ||
installType == "gptsyslinux" {
diskType := "msdos"
if installType == "gptsyslinux" {
diskType = "gpt"
}
err := setDiskpartitions ( device , diskType )
2016-12-01 03:55:19 +00:00
if err != nil {
2016-12-07 03:33:43 +00:00
log . Errorf ( "error setDiskpartitions %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
// use the bind mounted host filesystem to get access to the /dev/vda1 device that udev on the host sets up (TODO: can we run a udevd inside the container? `mknod b 253 1 /dev/vda1` doesn't work)
device = "/host" + device
2016-12-07 03:33:43 +00:00
}
if mountiso {
2016-12-07 10:55:38 +00:00
// TODO: I hope to remove this from here later.
2016-12-07 11:56:32 +00:00
if err := mountBootIso ( ) ; err != nil {
return err
2016-12-07 03:33:43 +00:00
}
2016-12-01 03:55:19 +00:00
}
err := layDownOS ( image , installType , cloudConfig , device , kappend )
if err != nil {
log . Infof ( "error layDownOS %s" , err )
2015-08-18 14:07:00 +00:00
return err
}
2016-10-12 12:41:52 +00:00
if reboot && ( force || yes ( "Continue with reboot" ) ) {
2015-08-18 14:07:00 +00:00
log . Info ( "Rebooting" )
power . Reboot ( )
}
return nil
}
2016-12-01 03:55:19 +00:00
2016-12-07 03:33:43 +00:00
func mountBootIso ( ) error {
2016-12-07 11:56:32 +00:00
deviceName := "/dev/sr0"
deviceType := "iso9660"
2016-12-15 01:54:43 +00:00
{ // force the defer
mountsFile , err := os . Open ( "/proc/mounts" )
if err != nil {
log . Errorf ( "failed to read /proc/mounts %s" , err )
return err
}
defer mountsFile . Close ( )
if partitionMounted ( deviceName , mountsFile ) {
return nil
}
}
os . MkdirAll ( "/bootiso" , 0755 )
2016-12-07 11:56:32 +00:00
// find the installation device
cmd := exec . Command ( "blkid" , "-L" , "RancherOS" )
log . Debugf ( "Run(%v)" , cmd )
cmd . Stderr = os . Stderr
out , err := cmd . Output ( )
if err != nil {
log . Errorf ( "Failed to get RancherOS boot device: %s" , err )
return err
}
deviceName = strings . TrimSpace ( string ( out ) )
log . Debugf ( "blkid found -L RancherOS: %s" , deviceName )
cmd = exec . Command ( "blkid" , deviceName )
log . Debugf ( "Run(%v)" , cmd )
cmd . Stderr = os . Stderr
if out , err = cmd . Output ( ) ; err != nil {
log . Errorf ( "Failed to get RancherOS boot device type: %s" , err )
return err
}
deviceType = strings . TrimSpace ( string ( out ) )
s1 := strings . Split ( deviceType , "TYPE=\"" )
s2 := strings . Split ( s1 [ 1 ] , "\"" )
deviceType = s2 [ 0 ]
log . Debugf ( "blkid type of %s: %s" , deviceName , deviceType )
cmd = exec . Command ( "mount" , "-t" , deviceType , deviceName , "/bootiso" )
log . Debugf ( "Run(%v)" , cmd )
2016-12-07 03:33:43 +00:00
cmd . Stdout , cmd . Stderr = os . Stdout , os . Stderr
2016-12-07 11:56:32 +00:00
err = cmd . Run ( )
2016-12-07 03:33:43 +00:00
if err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "tried and failed to mount %s: %s" , deviceName , err )
2016-12-07 03:33:43 +00:00
} else {
2016-12-07 11:56:32 +00:00
log . Debugf ( "Mounted %s" , deviceName )
2016-12-07 03:33:43 +00:00
}
return err
}
2016-12-01 03:55:19 +00:00
func layDownOS ( image , installType , cloudConfig , device , kappend string ) error {
// ENV == installType
//[[ "$ARCH" == "arm" && "$ENV" != "rancher-upgrade" ]] && ENV=arm
// image == rancher/os:v0.7.0_arm
// TODO: remove the _arm suffix (but watch out, its not always there..)
VERSION := image [ strings . Index ( image , ":" ) + 1 : ]
var FILES [ ] string
DIST := "/dist" //${DIST:-/dist}
//cloudConfig := SCRIPTS_DIR + "/conf/empty.yml" //${cloudConfig:-"${SCRIPTS_DIR}/conf/empty.yml"}
CONSOLE := "tty0"
baseName := "/mnt/new_img"
bootDir := "boot/" // set by mountdevice
//# TODO: Change this to a number so that users can specify.
//# Will need to make it so that our builds and packer APIs remain consistent.
partition := device + "1" //${partition:=${device}1}
kernelArgs := "rancher.state.dev=LABEL=RANCHER_STATE rancher.state.wait" // console="+CONSOLE
// unmount on trap
defer util . Unmount ( baseName )
2016-12-15 01:54:43 +00:00
diskType := "msdos"
if installType == "gptsyslinux" {
diskType = "gpt"
}
2016-12-01 03:55:19 +00:00
switch installType {
2016-12-15 01:54:43 +00:00
case "syslinux" :
fallthrough
case "gptsyslinux" :
fallthrough
2016-12-01 03:55:19 +00:00
case "generic" :
2016-12-07 11:56:32 +00:00
log . Debugf ( "formatAndMount" )
2016-12-01 03:55:19 +00:00
var err error
bootDir , err = formatAndMount ( baseName , bootDir , device , partition )
if err != nil {
log . Errorf ( "%s" , err )
return err
}
2016-12-04 11:27:06 +00:00
//log.Infof("installGrub")
//err = installGrub(baseName, device)
2016-12-07 11:56:32 +00:00
log . Debugf ( "installSyslinux" )
2016-12-15 01:54:43 +00:00
err = installSyslinux ( device , baseName , bootDir , diskType )
2016-12-04 11:27:06 +00:00
2016-12-01 03:55:19 +00:00
if err != nil {
log . Errorf ( "%s" , err )
return err
}
2016-12-07 11:56:32 +00:00
log . Debugf ( "seedData" )
2016-12-01 03:55:19 +00:00
err = seedData ( baseName , cloudConfig , FILES )
if err != nil {
log . Errorf ( "%s" , err )
return err
}
2016-12-07 11:56:32 +00:00
log . Debugf ( "seedData done" )
2016-12-01 03:55:19 +00:00
case "arm" :
var err error
bootDir , err = formatAndMount ( baseName , bootDir , device , partition )
if err != nil {
return err
}
seedData ( baseName , cloudConfig , FILES )
case "amazon-ebs-pv" :
fallthrough
case "amazon-ebs-hvm" :
CONSOLE = "ttyS0"
var err error
bootDir , err = formatAndMount ( baseName , bootDir , device , partition )
if err != nil {
return err
}
if installType == "amazon-ebs-hvm" {
installGrub ( baseName , device )
}
//# AWS Networking recommends disabling.
seedData ( baseName , cloudConfig , FILES )
case "googlecompute" :
CONSOLE = "ttyS0"
var err error
bootDir , err = formatAndMount ( baseName , bootDir , device , partition )
if err != nil {
return err
}
installGrub ( baseName , device )
seedData ( baseName , cloudConfig , FILES )
case "noformat" :
var err error
bootDir , err = mountdevice ( baseName , bootDir , partition , false )
if err != nil {
return err
}
createbootDirs ( baseName , bootDir )
2016-12-15 01:54:43 +00:00
installSyslinux ( device , baseName , bootDir , diskType )
2016-12-01 03:55:19 +00:00
case "raid" :
var err error
bootDir , err = mountdevice ( baseName , bootDir , partition , false )
if err != nil {
return err
}
createbootDirs ( baseName , bootDir )
2016-12-15 01:54:43 +00:00
installSyslinuxRaid ( baseName , bootDir , diskType )
2016-12-01 03:55:19 +00:00
case "bootstrap" :
CONSOLE = "ttyS0"
var err error
bootDir , err = mountdevice ( baseName , bootDir , partition , true )
if err != nil {
return err
}
createbootDirs ( baseName , bootDir )
kernelArgs = kernelArgs + " rancher.cloud_init.datasources=[ec2,gce]"
case "rancher-upgrade" :
var err error
bootDir , err = mountdevice ( baseName , bootDir , partition , false )
if err != nil {
return err
}
createbootDirs ( baseName , bootDir )
default :
return fmt . Errorf ( "unexpected install type %s" , installType )
}
kernelArgs = kernelArgs + " console=" + CONSOLE
if kappend == "" {
preservedAppend , _ := ioutil . ReadFile ( filepath . Join ( baseName , bootDir + "append" ) )
kappend = string ( preservedAppend )
} else {
ioutil . WriteFile ( filepath . Join ( baseName , bootDir + "append" ) , [ ] byte ( kappend ) , 0644 )
}
2016-12-07 03:33:43 +00:00
//menu := bootVars{
// baseName: baseName,
// bootDir: bootDir,
// Timeout: 1,
// Fallback: 1, // need to be conditional on there being a 'rollback'?
// Entries: []MenuEntry{
// MenuEntry{"RancherOS-current", bootDir, VERSION, kernelArgs, kappend},
// // MenuEntry{"RancherOS-rollback", bootDir, ROLLBACK_VERSION, kernelArgs, kappend},
// },
//}
//log.Debugf("grubConfig")
//grubConfig(menu)
//log.Debugf("syslinuxConfig")
//syslinuxConfig(menu)
//log.Debugf("pvGrubConfig")
//pvGrubConfig(menu)
2016-12-01 03:55:19 +00:00
log . Debugf ( "installRancher" )
2016-12-07 10:55:38 +00:00
err := installRancher ( baseName , bootDir , VERSION , DIST , kernelArgs + " " + kappend )
2016-12-01 03:55:19 +00:00
if err != nil {
log . Errorf ( "%s" , err )
return err
}
log . Debugf ( "installRancher done" )
//unused by us? :)
//if [ "$KEXEC" = "y" ]; then
// kexec -l ${DIST}/vmlinuz --initrd=${DIST}/initrd --append="${kernelArgs} ${APPEND}" -f
//fi
return nil
}
// files is an array of 'sourcefile:destination' - but i've not seen any examples of it being used.
func seedData ( baseName , cloudData string , files [ ] string ) error {
log . Debugf ( "seedData" )
_ , err := os . Stat ( baseName )
if err != nil {
return err
}
if err = os . MkdirAll ( filepath . Join ( baseName , "/var/lib/rancher/conf/cloud-config.d" ) , 0755 ) ; err != nil {
return err
}
if strings . HasSuffix ( cloudData , "empty.yml" ) {
if err = dfs . CopyFile ( cloudData , baseName + "/var/lib/rancher/conf/cloud-config.d/" , filepath . Base ( cloudData ) ) ; err != nil {
return err
}
}
for _ , f := range files {
e := strings . Split ( f , ":" )
if err = dfs . CopyFile ( e [ 0 ] , baseName , e [ 1 ] ) ; err != nil {
return err
}
}
return nil
}
// set-disk-partitions is called with device == **/dev/sda**
2016-12-15 01:54:43 +00:00
func setDiskpartitions ( device , diskType string ) error {
2016-12-01 03:55:19 +00:00
log . Debugf ( "setDiskpartitions" )
d := strings . Split ( device , "/" )
if len ( d ) != 3 {
return fmt . Errorf ( "bad device name (%s)" , device )
}
deviceName := d [ 2 ]
file , err := os . Open ( "/proc/partitions" )
if err != nil {
2016-12-07 03:33:43 +00:00
log . Debugf ( "failed to read /proc/partitions %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
defer file . Close ( )
exists := false
haspartitions := false
scanner := bufio . NewScanner ( file )
for scanner . Scan ( ) {
str := scanner . Text ( )
last := strings . LastIndex ( str , " " )
if last > - 1 {
dev := str [ last + 1 : ]
if strings . HasPrefix ( dev , deviceName ) {
if dev == deviceName {
exists = true
} else {
haspartitions = true
}
}
}
}
if ! exists {
2016-12-07 11:56:32 +00:00
return fmt . Errorf ( "disk %s not found: %s" , device , err )
2016-12-01 03:55:19 +00:00
}
if haspartitions {
2016-12-07 03:33:43 +00:00
log . Debugf ( "device %s already partitioned - checking if any are mounted" , device )
2016-12-01 03:55:19 +00:00
file , err := os . Open ( "/proc/mounts" )
if err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "failed to read /proc/mounts %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
defer file . Close ( )
if partitionMounted ( device , file ) {
err = fmt . Errorf ( "partition %s mounted, cannot repartition" , device )
2016-12-07 11:56:32 +00:00
log . Errorf ( "%s" , err )
2016-12-01 03:55:19 +00:00
return err
}
cmd := exec . Command ( "system-docker" , "ps" , "-q" )
var outb bytes . Buffer
cmd . Stdout = & outb
if err := cmd . Run ( ) ; err != nil {
log . Printf ( "%s" , err )
return err
}
for _ , image := range strings . Split ( outb . String ( ) , "\n" ) {
if image == "" {
continue
}
r , w := io . Pipe ( )
go func ( ) {
// TODO: consider a timeout
cmd := exec . Command ( "system-docker" , "exec" , image , "cat /proc/mount" )
cmd . Stdout = w
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "%s" , err )
2016-12-01 03:55:19 +00:00
}
} ( )
if partitionMounted ( device , r ) {
err = fmt . Errorf ( "partition %s mounted in %s, cannot repartition" , device , image )
2016-12-07 11:56:32 +00:00
log . Errorf ( "%s" , err )
2016-12-01 03:55:19 +00:00
return err
}
}
}
//do it!
2016-12-07 03:33:43 +00:00
log . Debugf ( "running dd" )
2016-12-01 03:55:19 +00:00
cmd := exec . Command ( "dd" , "if=/dev/zero" , "of=" + device , "bs=512" , "count=2048" )
2016-12-07 11:56:32 +00:00
//cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "%s" , err )
2016-12-01 03:55:19 +00:00
return err
}
2016-12-07 03:33:43 +00:00
log . Debugf ( "running partprobe" )
2016-12-01 03:55:19 +00:00
cmd = exec . Command ( "partprobe" , device )
2016-12-07 11:56:32 +00:00
//cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "%s" , err )
2016-12-01 03:55:19 +00:00
return err
}
2016-12-15 01:54:43 +00:00
bootflag := "boot"
if diskType == "gpt" {
bootflag = "legacy_boot"
}
log . Debugf ( "running parted" )
cmd = exec . Command ( "parted" , "-s" , "-a" , "optimal" , device ,
"mklabel " + diskType , "--" ,
"mkpart primary ext4 1 -1" ,
"set 1 " + bootflag + " on" )
cmd . Stdout , cmd . Stderr = os . Stdout , os . Stderr
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "%s" , err )
2016-12-01 03:55:19 +00:00
return err
}
return nil
}
func partitionMounted ( device string , file io . Reader ) bool {
scanner := bufio . NewScanner ( file )
for scanner . Scan ( ) {
str := scanner . Text ( )
// /dev/sdb1 /data ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
ele := strings . Split ( str , " " )
if len ( ele ) > 5 {
if strings . HasPrefix ( ele [ 0 ] , device ) {
return true
}
}
if err := scanner . Err ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "%s" , err )
2016-12-01 03:55:19 +00:00
return false
}
}
return false
}
func formatdevice ( device , partition string ) error {
log . Debugf ( "formatdevice %s" , partition )
//mkfs.ext4 -F -i 4096 -L RANCHER_STATE ${partition}
2016-12-04 11:27:06 +00:00
// -O ^64bit: for syslinux: http://www.syslinux.org/wiki/index.php?title=Filesystem#ext
cmd := exec . Command ( "mkfs.ext4" , "-F" , "-i" , "4096" , "-O" , "^64bit" , "-L" , "RANCHER_STATE" , partition )
2016-12-01 03:55:19 +00:00
log . Debugf ( "Run(%v)" , cmd )
2016-12-07 11:56:32 +00:00
//cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "mkfs.ext4: %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
return nil
}
func mountdevice ( baseName , bootDir , partition string , raw bool ) ( string , error ) {
log . Debugf ( "mountdevice %s, raw %v" , partition , raw )
if raw {
log . Debugf ( "util.Mount (raw) %s, %s" , partition , baseName )
return bootDir , util . Mount ( partition , baseName , "" , "" )
}
rootfs := partition
// Don't use ResolveDevice - it can fail, whereas `blkid -L LABEL` works more often
//if dev := util.ResolveDevice("LABEL=RANCHER_BOOT"); dev != "" {
cmd := exec . Command ( "blkid" , "-L" , "RANCHER_BOOT" )
log . Debugf ( "Run(%v)" , cmd )
cmd . Stderr = os . Stderr
if out , err := cmd . Output ( ) ; err == nil {
rootfs = string ( out )
} else {
cmd := exec . Command ( "blkid" , "-L" , "RANCHER_STATE" )
log . Debugf ( "Run(%v)" , cmd )
cmd . Stderr = os . Stderr
if out , err := cmd . Output ( ) ; err == nil {
rootfs = string ( out )
}
}
log . Debugf ( "util.Mount %s, %s" , rootfs , baseName )
// return bootDir, util.Mount(rootfs, baseName, "", "")
os . MkdirAll ( baseName , 0755 )
cmd = exec . Command ( "mount" , rootfs , baseName )
log . Debugf ( "Run(%v)" , cmd )
2016-12-07 11:56:32 +00:00
//cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
2016-12-01 03:55:19 +00:00
return bootDir , cmd . Run ( )
}
func formatAndMount ( baseName , bootDir , device , partition string ) ( string , error ) {
log . Debugf ( "formatAndMount" )
err := formatdevice ( device , partition )
if err != nil {
return bootDir , err
}
bootDir , err = mountdevice ( baseName , bootDir , partition , false )
if err != nil {
return bootDir , err
}
err = createbootDirs ( baseName , bootDir )
if err != nil {
return bootDir , err
}
return bootDir , nil
}
func createbootDirs ( baseName , bootDir string ) error {
log . Debugf ( "createbootDirs" )
if err := os . MkdirAll ( filepath . Join ( baseName , bootDir + "grub" ) , 0755 ) ; err != nil {
return err
}
if err := os . MkdirAll ( filepath . Join ( baseName , bootDir + "syslinux" ) , 0755 ) ; err != nil {
return err
}
return nil
}
2016-12-15 01:54:43 +00:00
func installSyslinux ( device , baseName , bootDir , diskType string ) error {
2016-12-01 03:55:19 +00:00
log . Debugf ( "installSyslinux" )
2016-12-15 01:54:43 +00:00
mbrFile := "mbr.bin"
if diskType == "gpt" {
mbrFile = "gptmbr.bin"
}
2016-12-01 03:55:19 +00:00
//dd bs=440 count=1 if=/usr/lib/syslinux/mbr/mbr.bin of=${device}
2016-12-04 11:27:06 +00:00
// ubuntu: /usr/lib/syslinux/mbr/mbr.bin
// alpine: /usr/share/syslinux/mbr.bin
2016-12-15 01:54:43 +00:00
cmd := exec . Command ( "dd" , "bs=440" , "count=1" , "if=/usr/share/syslinux/" + mbrFile , "of=" + device )
2016-12-07 11:56:32 +00:00
//cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
2016-12-04 11:27:06 +00:00
log . Debugf ( "Run(%v)" , cmd )
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "dd: %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
//cp /usr/lib/syslinux/modules/bios/* ${baseName}/${bootDir}syslinux
2016-12-04 11:27:06 +00:00
files , _ := ioutil . ReadDir ( "/usr/share/syslinux/" )
for _ , file := range files {
if file . IsDir ( ) {
continue
}
if err := dfs . CopyFile ( filepath . Join ( "/usr/share/syslinux/" , file . Name ( ) ) , filepath . Join ( baseName , bootDir , "syslinux" ) , file . Name ( ) ) ; err != nil {
log . Errorf ( "copy syslinux: %s" , err )
return err
}
2016-12-01 03:55:19 +00:00
}
2016-12-04 11:27:06 +00:00
2016-12-01 03:55:19 +00:00
//extlinux --install ${baseName}/${bootDir}syslinux
cmd = exec . Command ( "extlinux" , "--install" , filepath . Join ( baseName , bootDir + "syslinux" ) )
2016-12-07 11:56:32 +00:00
//cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
2016-12-04 11:27:06 +00:00
log . Debugf ( "Run(%v)" , cmd )
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "extlinuux: %s" , err )
2016-12-01 03:55:19 +00:00
return err
}
return nil
}
2016-12-15 01:54:43 +00:00
func installSyslinuxRaid ( baseName , bootDir , diskType string ) error {
2016-12-01 03:55:19 +00:00
log . Debugf ( "installSyslinuxRaid" )
2016-12-15 01:54:43 +00:00
mbrFile := "mbr.bin"
if diskType == "gpt" {
mbrFile = "gptmbr.bin"
}
2016-12-01 03:55:19 +00:00
//dd bs=440 count=1 if=/usr/lib/syslinux/mbr/mbr.bin of=/dev/sda
//dd bs=440 count=1 if=/usr/lib/syslinux/mbr/mbr.bin of=/dev/sdb
//cp /usr/lib/syslinux/modules/bios/* ${baseName}/${bootDir}syslinux
//extlinux --install --raid ${baseName}/${bootDir}syslinux
2016-12-15 01:54:43 +00:00
cmd := exec . Command ( "dd" , "bs=440" , "count=1" , "if=/usr/share/syslinux/" + mbrFile , "of=/dev/sda" )
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "%s" , err )
2016-12-01 03:55:19 +00:00
return err
}
2016-12-15 01:54:43 +00:00
cmd = exec . Command ( "dd" , "bs=440" , "count=1" , "if=/usr/share/syslinux/" + mbrFile , "of=/dev/sdb" )
2016-12-01 03:55:19 +00:00
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "%s" , err )
2016-12-01 03:55:19 +00:00
return err
}
2016-12-04 11:27:06 +00:00
//cp /usr/lib/syslinux/modules/bios/* ${baseName}/${bootDir}syslinux
files , _ := ioutil . ReadDir ( "/usr/share/syslinux/" )
for _ , file := range files {
if file . IsDir ( ) {
continue
}
if err := dfs . CopyFile ( filepath . Join ( "/usr/share/syslinux/" , file . Name ( ) ) , filepath . Join ( baseName , bootDir , "syslinux" ) , file . Name ( ) ) ; err != nil {
log . Errorf ( "copy syslinux: %s" , err )
return err
}
2016-12-01 03:55:19 +00:00
}
cmd = exec . Command ( "extlinux" , "--install" , filepath . Join ( baseName , bootDir + "syslinux" ) )
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "%s" , err )
2016-12-01 03:55:19 +00:00
return err
}
return nil
}
func installGrub ( baseName , device string ) error {
log . Debugf ( "installGrub" )
//grub-install --boot-directory=${baseName}/boot ${device}
cmd := exec . Command ( "grub-install" , "--boot-directory=" + baseName + "/boot" , device )
if err := cmd . Run ( ) ; err != nil {
2016-12-07 11:56:32 +00:00
log . Errorf ( "%s" , err )
2016-12-01 03:55:19 +00:00
return err
}
return nil
}
func grubConfig ( menu bootVars ) error {
log . Debugf ( "grubConfig" )
filetmpl , err := template . New ( "grub2config" ) . Parse ( ` { { define "grub2menu" } } menuentry "{{.Name}}" {
set root = ( hd0 , msdos1 )
linux / { { . bootDir } } vmlinuz - { { . Version } } - rancheros { { . KernelArgs } } { { . Append } }
initrd / { { . bootDir } } initrd - { { . Version } } - rancheros
}
{ { end } }
set default = "0"
set timeout = "{{.Timeout}}"
{ { if . Fallback } } set fallback = { { . Fallback } } { { end } }
{ { - range . Entries } }
{ { template "grub2menu" . } }
{ { - end } }
` )
if err != nil {
log . Errorf ( "grub2config %s" , err )
return err
}
cfgFile := filepath . Join ( menu . baseName , menu . bootDir + "grub/grub.cfg" )
log . Debugf ( "grubConfig written to %s" , cfgFile )
f , err := os . Create ( cfgFile )
if err != nil {
return err
}
err = filetmpl . Execute ( f , menu )
if err != nil {
return err
}
return nil
}
func syslinuxConfig ( menu bootVars ) error {
log . Debugf ( "syslinuxConfig" )
filetmpl , err := template . New ( "syslinuxconfig" ) . Parse ( ` { { define "syslinuxmenu" } }
LABEL { { . Name } }
LINUX . . / vmlinuz - { { . Version } } - rancheros
APPEND { { . KernelArgs } } { { . Append } }
INITRD . . / initrd - { { . Version } } - rancheros
{ { end } }
2016-12-07 03:33:43 +00:00
TIMEOUT 20 # 2 seconds
2016-12-01 03:55:19 +00:00
DEFAULT RancherOS - current
{ { - range . Entries } }
{ { template "syslinuxmenu" . } }
{ { - end } }
` )
if err != nil {
log . Errorf ( "syslinuxconfig %s" , err )
return err
}
cfgFile := filepath . Join ( menu . baseName , menu . bootDir + "syslinux/syslinux.cfg" )
log . Debugf ( "syslinuxConfig written to %s" , cfgFile )
f , err := os . Create ( cfgFile )
if err != nil {
log . Errorf ( "Create(%s) %s" , cfgFile , err )
return err
}
err = filetmpl . Execute ( f , menu )
if err != nil {
return err
}
return nil
}
2016-12-07 10:55:38 +00:00
func installRancher ( baseName , bootDir , VERSION , DIST , kappend string ) error {
2016-12-01 03:55:19 +00:00
log . Debugf ( "installRancher" )
2016-12-07 10:55:38 +00:00
// TODO detect if there already is a linux-current.cfg, if so, move it to linux-previous.cfg, and replace only current with the one in the image/iso
2016-12-07 03:33:43 +00:00
2016-12-07 10:55:38 +00:00
// The image/ISO have all the files in it - the syslinux cfg's and the kernel&initrd, so we can copy them all from there
2016-12-07 03:33:43 +00:00
files , _ := ioutil . ReadDir ( DIST )
for _ , file := range files {
if file . IsDir ( ) {
continue
}
if err := dfs . CopyFile ( filepath . Join ( DIST , file . Name ( ) ) , filepath . Join ( baseName , bootDir ) , file . Name ( ) ) ; err != nil {
log . Errorf ( "copy %s: %s" , file . Name ( ) , err )
return err
}
}
// the main syslinuxcfg
if err := dfs . CopyFile ( filepath . Join ( DIST , "isolinux" , "isolinux.cfg" ) , filepath . Join ( baseName , bootDir , "syslinux" ) , "syslinux.cfg" ) ; err != nil {
log . Errorf ( "copy %s: %s" , "syslinux.cfg" , err )
2016-12-01 03:55:19 +00:00
return err
}
2016-12-07 10:55:38 +00:00
// The global.cfg INCLUDE - useful for over-riding the APPEND line
err := ioutil . WriteFile ( filepath . Join ( filepath . Join ( baseName , bootDir ) , "global.cfg" ) , [ ] byte ( "APPEND " + kappend ) , 0644 )
if err != nil {
log . Errorf ( "write (%s) %s" , "global.cfg" , err )
return err
}
2016-12-01 03:55:19 +00:00
return nil
}
func pvGrubConfig ( menu bootVars ) error {
log . Debugf ( "pvGrubConfig" )
filetmpl , err := template . New ( "grublst" ) . Parse ( ` { { define "grubmenu" } }
title RancherOS { { . Version } } - ( { { . Name } } )
root ( hd0 )
kernel / $ { bootDir } vmlinuz - { { . Version } } - rancheros { { . KernelArgs } } { { . Append } }
initrd / $ { bootDir } initrd - { { . Version } } - rancheros
{ { end } }
default 0
timeout { { . Timeout } }
{ { if . Fallback } } fallback { { . Fallback } } { { end } }
hiddenmenu
{ { - range . Entries } }
{ { template "grubmenu" . } }
{ { - end } }
` )
if err != nil {
log . Errorf ( "pv grublst: %s" , err )
return err
}
cfgFile := filepath . Join ( menu . baseName , menu . bootDir + "grub/menu.lst" )
log . Debugf ( "grubMenu written to %s" , cfgFile )
f , err := os . Create ( cfgFile )
if err != nil {
log . Errorf ( "Create(%s) %s" , cfgFile , err )
return err
}
err = filetmpl . Execute ( f , menu )
if err != nil {
log . Errorf ( "execute %s" , err )
return err
}
return nil
}