github.com/jbronn/packer@v0.1.6-0.20140120165540-8a1364dbd817/provisioner/ansible-local/provisioner.go (about)

     1  package ansiblelocal
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/mitchellh/packer/common"
     6  	"github.com/mitchellh/packer/packer"
     7  	"os"
     8  	"path/filepath"
     9  )
    10  
    11  const DefaultStagingDir = "/tmp/packer-provisioner-ansible-local"
    12  
    13  type Config struct {
    14  	common.PackerConfig `mapstructure:",squash"`
    15  	tpl                 *packer.ConfigTemplate
    16  
    17  	// The main playbook file to execute.
    18  	PlaybookFile string `mapstructure:"playbook_file"`
    19  
    20  	// An array of local paths of playbook files to upload.
    21  	PlaybookPaths []string `mapstructure:"playbook_paths"`
    22  
    23  	// An array of local paths of roles to upload.
    24  	RolePaths []string `mapstructure:"role_paths"`
    25  
    26  	// The directory where files will be uploaded. Packer requires write
    27  	// permissions in this directory.
    28  	StagingDir string `mapstructure:"staging_directory"`
    29  }
    30  
    31  type Provisioner struct {
    32  	config Config
    33  }
    34  
    35  func (p *Provisioner) Prepare(raws ...interface{}) error {
    36  	md, err := common.DecodeConfig(&p.config, raws...)
    37  	if err != nil {
    38  		return err
    39  	}
    40  
    41  	p.config.tpl, err = packer.NewConfigTemplate()
    42  	if err != nil {
    43  		return err
    44  	}
    45  
    46  	p.config.tpl.UserVars = p.config.PackerUserVars
    47  
    48  	// Accumulate any errors
    49  	errs := common.CheckUnusedConfig(md)
    50  
    51  	if p.config.StagingDir == "" {
    52  		p.config.StagingDir = DefaultStagingDir
    53  	}
    54  
    55  	// Templates
    56  	templates := map[string]*string{
    57  		"playbook_file": &p.config.PlaybookFile,
    58  		"staging_dir":   &p.config.StagingDir,
    59  	}
    60  
    61  	for n, ptr := range templates {
    62  		var err error
    63  		*ptr, err = p.config.tpl.Process(*ptr, nil)
    64  		if err != nil {
    65  			errs = packer.MultiErrorAppend(
    66  				errs, fmt.Errorf("Error processing %s: %s", n, err))
    67  		}
    68  	}
    69  
    70  	sliceTemplates := map[string][]string{
    71  		"playbook_paths": p.config.PlaybookPaths,
    72  		"role_paths":     p.config.RolePaths,
    73  	}
    74  
    75  	for n, slice := range sliceTemplates {
    76  		for i, elem := range slice {
    77  			var err error
    78  			slice[i], err = p.config.tpl.Process(elem, nil)
    79  			if err != nil {
    80  				errs = packer.MultiErrorAppend(
    81  					errs, fmt.Errorf("Error processing %s[%d]: %s", n, i, err))
    82  			}
    83  		}
    84  	}
    85  
    86  	// Validation
    87  	err = validateFileConfig(p.config.PlaybookFile, "playbook_file", true)
    88  	if err != nil {
    89  		errs = packer.MultiErrorAppend(errs, err)
    90  	}
    91  	for _, path := range p.config.PlaybookPaths {
    92  		err := validateDirConfig(path, "playbook_paths")
    93  		if err != nil {
    94  			errs = packer.MultiErrorAppend(errs, err)
    95  		}
    96  	}
    97  	for _, path := range p.config.RolePaths {
    98  		if err := validateDirConfig(path, "role_paths"); err != nil {
    99  			errs = packer.MultiErrorAppend(errs, err)
   100  		}
   101  	}
   102  	if errs != nil && len(errs.Errors) > 0 {
   103  		return errs
   104  	}
   105  	return nil
   106  }
   107  
   108  func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
   109  	ui.Say("Provisioning with Ansible...")
   110  
   111  	ui.Message("Creating Ansible staging directory...")
   112  	if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
   113  		return fmt.Errorf("Error creating staging directory: %s", err)
   114  	}
   115  
   116  	ui.Message("Uploading main Playbook file...")
   117  	src := p.config.PlaybookFile
   118  	dst := filepath.Join(p.config.StagingDir, filepath.Base(src))
   119  	if err := p.uploadFile(ui, comm, dst, src); err != nil {
   120  		return fmt.Errorf("Error uploading main playbook: %s", err)
   121  	}
   122  
   123  	if len(p.config.RolePaths) > 0 {
   124  		ui.Message("Uploading role directories...")
   125  		for _, src := range p.config.RolePaths {
   126  			dst := filepath.Join(p.config.StagingDir, "roles", filepath.Base(src))
   127  			if err := p.uploadDir(ui, comm, dst, src); err != nil {
   128  				return fmt.Errorf("Error uploading roles: %s", err)
   129  			}
   130  		}
   131  	}
   132  
   133  	if len(p.config.PlaybookPaths) > 0 {
   134  		ui.Message("Uploading additional Playbooks...")
   135  		if err := p.createDir(ui, comm, filepath.Join(p.config.StagingDir, "playbooks")); err != nil {
   136  			return fmt.Errorf("Error creating playbooks directory: %s", err)
   137  		}
   138  		for _, src := range p.config.PlaybookPaths {
   139  			dst := filepath.Join(p.config.StagingDir, "playbooks", filepath.Base(src))
   140  			if err := p.uploadDir(ui, comm, dst, src); err != nil {
   141  				return fmt.Errorf("Error uploading playbooks: %s", err)
   142  			}
   143  		}
   144  	}
   145  
   146  	if err := p.executeAnsible(ui, comm); err != nil {
   147  		return fmt.Errorf("Error executing Ansible: %s", err)
   148  	}
   149  	return nil
   150  }
   151  
   152  func (p *Provisioner) Cancel() {
   153  	// Just hard quit. It isn't a big deal if what we're doing keeps
   154  	// running on the other side.
   155  	os.Exit(0)
   156  }
   157  
   158  func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator) error {
   159  	playbook := filepath.Join(p.config.StagingDir, filepath.Base(p.config.PlaybookFile))
   160  
   161  	// The inventory must be set to "127.0.0.1,".  The comma is important
   162  	// as its the only way to override the ansible inventory when dealing
   163  	// with a single host.
   164  	command := fmt.Sprintf("ansible-playbook %s -c local -i %s", playbook, `"127.0.0.1,"`)
   165  
   166  	ui.Message(fmt.Sprintf("Executing Ansible: %s", command))
   167  	cmd := &packer.RemoteCmd{
   168  		Command: command,
   169  	}
   170  	if err := cmd.StartWithUi(comm, ui); err != nil {
   171  		return err
   172  	}
   173  	if cmd.ExitStatus != 0 {
   174  		return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus)
   175  	}
   176  	return nil
   177  }
   178  
   179  func validateDirConfig(path string, config string) error {
   180  	info, err := os.Stat(path)
   181  	if err != nil {
   182  		return fmt.Errorf("%s: %s is invalid: %s", config, path, err)
   183  	} else if !info.IsDir() {
   184  		return fmt.Errorf("%s: %s must point to a directory", config, path)
   185  	}
   186  	return nil
   187  }
   188  
   189  func validateFileConfig(name string, config string, req bool) error {
   190  	if req {
   191  		if name == "" {
   192  			return fmt.Errorf("%s must be specified.", config)
   193  		}
   194  	}
   195  	info, err := os.Stat(name)
   196  	if err != nil {
   197  		return fmt.Errorf("%s: %s is invalid: %s", config, name, err)
   198  	} else if info.IsDir() {
   199  		return fmt.Errorf("%s: %s must point to a file", config, name)
   200  	}
   201  	return nil
   202  }
   203  
   204  func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, dst, src string) error {
   205  	f, err := os.Open(src)
   206  	if err != nil {
   207  		return fmt.Errorf("Error opening: %s", err)
   208  	}
   209  	defer f.Close()
   210  
   211  	if err = comm.Upload(dst, f); err != nil {
   212  		return fmt.Errorf("Error uploading %s: %s", src, err)
   213  	}
   214  	return nil
   215  }
   216  
   217  func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
   218  	ui.Message(fmt.Sprintf("Creating directory: %s", dir))
   219  	cmd := &packer.RemoteCmd{
   220  		Command: fmt.Sprintf("mkdir -p '%s'", dir),
   221  	}
   222  	if err := cmd.StartWithUi(comm, ui); err != nil {
   223  		return err
   224  	}
   225  	if cmd.ExitStatus != 0 {
   226  		return fmt.Errorf("Non-zero exit status.")
   227  	}
   228  	return nil
   229  }
   230  
   231  func (p *Provisioner) uploadDir(ui packer.Ui, comm packer.Communicator, dst, src string) error {
   232  	if err := p.createDir(ui, comm, dst); err != nil {
   233  		return err
   234  	}
   235  
   236  	// Make sure there is a trailing "/" so that the directory isn't
   237  	// created on the other side.
   238  	if src[len(src)-1] != '/' {
   239  		src = src + "/"
   240  	}
   241  	return comm.UploadDir(dst, src, nil)
   242  }