github.com/phobos182/packer@v0.2.3-0.20130819023704-c84d2aeffc68/provisioner/salt-masterless/provisioner.go (about) 1 // This package implements a provisioner for Packer that executes a 2 // saltstack highstate within the remote machine 3 package saltmasterless 4 5 import ( 6 "errors" 7 "fmt" 8 "github.com/mitchellh/packer/common" 9 "github.com/mitchellh/packer/packer" 10 "os" 11 "path/filepath" 12 "strings" 13 ) 14 15 const DefaultTempConfigDir = "/tmp/salt" 16 17 type Config struct { 18 common.PackerConfig `mapstructure:",squash"` 19 20 // If true, run the salt-bootstrap script 21 SkipBootstrap bool `mapstructure:"skip_bootstrap"` 22 BootstrapArgs string `mapstructure:"bootstrap_args"` 23 24 // Local path to the salt state tree 25 LocalStateTree string `mapstructure:"local_state_tree"` 26 27 // Where files will be copied before moving to the /srv/salt directory 28 TempConfigDir string `mapstructure:"temp_config_dir"` 29 30 tpl *packer.ConfigTemplate 31 } 32 33 type Provisioner struct { 34 config Config 35 } 36 37 func (p *Provisioner) Prepare(raws ...interface{}) error { 38 md, err := common.DecodeConfig(&p.config, raws...) 39 if err != nil { 40 return err 41 } 42 43 p.config.tpl, err = packer.NewConfigTemplate() 44 if err != nil { 45 return err 46 } 47 p.config.tpl.UserVars = p.config.PackerUserVars 48 49 if p.config.TempConfigDir == "" { 50 p.config.TempConfigDir = DefaultTempConfigDir 51 } 52 53 // Accumulate any errors 54 errs := common.CheckUnusedConfig(md) 55 56 templates := map[string]*string{ 57 "bootstrap_args": &p.config.BootstrapArgs, 58 "local_state_tree": &p.config.LocalStateTree, 59 "temp_config_dir": &p.config.TempConfigDir, 60 } 61 62 for n, ptr := range templates { 63 var err error 64 *ptr, err = p.config.tpl.Process(*ptr, nil) 65 if err != nil { 66 errs = packer.MultiErrorAppend( 67 errs, fmt.Errorf("Error processing %s: %s", n, err)) 68 } 69 } 70 71 if p.config.LocalStateTree != "" { 72 if _, err := os.Stat(p.config.LocalStateTree); err != nil { 73 errs = packer.MultiErrorAppend(errs, 74 errors.New("local_state_tree must exist and be accessible")) 75 } 76 } 77 78 if errs != nil && len(errs.Errors) > 0 { 79 return errs 80 } 81 82 return nil 83 } 84 85 func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { 86 var err error 87 88 ui.Say("Provisioning with Salt...") 89 if !p.config.SkipBootstrap { 90 cmd := &packer.RemoteCmd{ 91 Command: fmt.Sprintf("wget -O - http://bootstrap.saltstack.org | sudo sh -s %s", p.config.BootstrapArgs), 92 } 93 ui.Message(fmt.Sprintf("Installing Salt with command %s", cmd)) 94 if err = cmd.StartWithUi(comm, ui); err != nil { 95 return fmt.Errorf("Unable to install Salt: %d", err) 96 } 97 } 98 99 ui.Message(fmt.Sprintf("Creating remote directory: %s", p.config.TempConfigDir)) 100 cmd := &packer.RemoteCmd{Command: fmt.Sprintf("mkdir -p %s", p.config.TempConfigDir)} 101 if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 { 102 if err == nil { 103 err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus) 104 } 105 106 return fmt.Errorf("Error creating remote salt state directory: %s", err) 107 } 108 109 ui.Message(fmt.Sprintf("Uploading local state tree: %s", p.config.LocalStateTree)) 110 if err = UploadLocalDirectory(p.config.LocalStateTree, p.config.TempConfigDir, comm, ui); err != nil { 111 return fmt.Errorf("Error uploading local state tree to remote: %s", err) 112 } 113 114 ui.Message(fmt.Sprintf("Moving %s to /srv/salt", p.config.TempConfigDir)) 115 cmd = &packer.RemoteCmd{Command: fmt.Sprintf("sudo mv %s /srv/salt", p.config.TempConfigDir)} 116 if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 { 117 if err == nil { 118 err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus) 119 } 120 121 return fmt.Errorf("Unable to move %s to /srv/salt: %d", p.config.TempConfigDir, err) 122 } 123 124 ui.Message("Running highstate") 125 cmd = &packer.RemoteCmd{Command: "sudo salt-call --local state.highstate -l info"} 126 if err = cmd.StartWithUi(comm, ui); err != nil || cmd.ExitStatus != 0 { 127 if err == nil { 128 err = fmt.Errorf("Bad exit status: %d", cmd.ExitStatus) 129 } 130 131 return fmt.Errorf("Error executing highstate: %s", err) 132 } 133 134 return nil 135 } 136 137 func UploadLocalDirectory(localDir string, remoteDir string, comm packer.Communicator, ui packer.Ui) (err error) { 138 visitPath := func(localPath string, f os.FileInfo, err error) (err2 error) { 139 localRelPath := strings.Replace(localPath, localDir, "", 1) 140 localRelPath = strings.Replace(localRelPath, "\\", "/", -1) 141 remotePath := fmt.Sprintf("%s%s", remoteDir, localRelPath) 142 if f.IsDir() && f.Name() == ".git" { 143 return filepath.SkipDir 144 } 145 if f.IsDir() { 146 // Make remote directory 147 cmd := &packer.RemoteCmd{Command: fmt.Sprintf("mkdir -p %s", remotePath)} 148 if err = cmd.StartWithUi(comm, ui); err != nil { 149 return err 150 } 151 } else { 152 // Upload file to existing directory 153 file, err := os.Open(localPath) 154 if err != nil { 155 return fmt.Errorf("Error opening file: %s", err) 156 } 157 defer file.Close() 158 159 ui.Message(fmt.Sprintf("Uploading file %s: %s", localPath, remotePath)) 160 if err = comm.Upload(remotePath, file); err != nil { 161 return fmt.Errorf("Error uploading file: %s", err) 162 } 163 } 164 return 165 } 166 167 err = filepath.Walk(localDir, visitPath) 168 if err != nil { 169 return fmt.Errorf("Error uploading local directory %s: %s", localDir, err) 170 } 171 172 return nil 173 }