github.com/daniellockard/packer@v0.7.6-0.20141210173435-5a9390934716/provisioner/ansible-local/provisioner.go (about)

     1  package ansiblelocal
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/mitchellh/packer/common"
    11  	"github.com/mitchellh/packer/packer"
    12  )
    13  
    14  const DefaultStagingDir = "/tmp/packer-provisioner-ansible-local"
    15  
    16  type Config struct {
    17  	common.PackerConfig `mapstructure:",squash"`
    18  	tpl                 *packer.ConfigTemplate
    19  
    20  	// The command to run ansible
    21  	Command string
    22  
    23  	// Extra options to pass to the ansible command
    24  	ExtraArguments []string `mapstructure:"extra_arguments"`
    25  
    26  	// Path to group_vars directory
    27  	GroupVars string `mapstructure:"group_vars"`
    28  
    29  	// Path to host_vars directory
    30  	HostVars string `mapstructure:"host_vars"`
    31  
    32  	// The playbook dir to upload.
    33  	PlaybookDir string `mapstructure:"playbook_dir"`
    34  
    35  	// The main playbook file to execute.
    36  	PlaybookFile string `mapstructure:"playbook_file"`
    37  
    38  	// An array of local paths of playbook files to upload.
    39  	PlaybookPaths []string `mapstructure:"playbook_paths"`
    40  
    41  	// An array of local paths of roles to upload.
    42  	RolePaths []string `mapstructure:"role_paths"`
    43  
    44  	// The directory where files will be uploaded. Packer requires write
    45  	// permissions in this directory.
    46  	StagingDir string `mapstructure:"staging_directory"`
    47  
    48  	// The optional inventory file
    49  	InventoryFile string `mapstructure:"inventory_file"`
    50  }
    51  
    52  type Provisioner struct {
    53  	config Config
    54  }
    55  
    56  func (p *Provisioner) Prepare(raws ...interface{}) error {
    57  	md, err := common.DecodeConfig(&p.config, raws...)
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	p.config.tpl, err = packer.NewConfigTemplate()
    63  	if err != nil {
    64  		return err
    65  	}
    66  
    67  	p.config.tpl.UserVars = p.config.PackerUserVars
    68  
    69  	// Accumulate any errors
    70  	errs := common.CheckUnusedConfig(md)
    71  
    72  	// Defaults
    73  	if p.config.Command == "" {
    74  		p.config.Command = "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 ansible-playbook"
    75  	}
    76  
    77  	if p.config.StagingDir == "" {
    78  		p.config.StagingDir = DefaultStagingDir
    79  	}
    80  
    81  	// Templates
    82  	templates := map[string]*string{
    83  		"command":        &p.config.Command,
    84  		"group_vars":     &p.config.GroupVars,
    85  		"host_vars":      &p.config.HostVars,
    86  		"playbook_file":  &p.config.PlaybookFile,
    87  		"playbook_dir":   &p.config.PlaybookDir,
    88  		"staging_dir":    &p.config.StagingDir,
    89  		"inventory_file": &p.config.InventoryFile,
    90  	}
    91  
    92  	for n, ptr := range templates {
    93  		var err error
    94  		*ptr, err = p.config.tpl.Process(*ptr, nil)
    95  		if err != nil {
    96  			errs = packer.MultiErrorAppend(
    97  				errs, fmt.Errorf("Error processing %s: %s", n, err))
    98  		}
    99  	}
   100  
   101  	sliceTemplates := map[string][]string{
   102  		"extra_arguments": p.config.ExtraArguments,
   103  		"playbook_paths":  p.config.PlaybookPaths,
   104  		"role_paths":      p.config.RolePaths,
   105  	}
   106  
   107  	for n, slice := range sliceTemplates {
   108  		for i, elem := range slice {
   109  			var err error
   110  			slice[i], err = p.config.tpl.Process(elem, nil)
   111  			if err != nil {
   112  				errs = packer.MultiErrorAppend(
   113  					errs, fmt.Errorf("Error processing %s[%d]: %s", n, i, err))
   114  			}
   115  		}
   116  	}
   117  
   118  	// Validation
   119  	err = validateFileConfig(p.config.PlaybookFile, "playbook_file", true)
   120  	if err != nil {
   121  		errs = packer.MultiErrorAppend(errs, err)
   122  	}
   123  
   124  	// Check that the inventory file exists, if configured
   125  	if len(p.config.InventoryFile) > 0 {
   126  		err = validateFileConfig(p.config.InventoryFile, "inventory_file", true)
   127  		if err != nil {
   128  			errs = packer.MultiErrorAppend(errs, err)
   129  		}
   130  	}
   131  
   132  	// Check that the playbook_dir directory exists, if configured
   133  	if len(p.config.PlaybookDir) > 0 {
   134  		if err := validateDirConfig(p.config.PlaybookDir, "playbook_dir"); err != nil {
   135  			errs = packer.MultiErrorAppend(errs, err)
   136  		}
   137  	}
   138  
   139  	// Check that the group_vars directory exists, if configured
   140  	if len(p.config.GroupVars) > 0 {
   141  		if err := validateDirConfig(p.config.GroupVars, "group_vars"); err != nil {
   142  			errs = packer.MultiErrorAppend(errs, err)
   143  		}
   144  	}
   145  
   146  	// Check that the host_vars directory exists, if configured
   147  	if len(p.config.HostVars) > 0 {
   148  		if err := validateDirConfig(p.config.HostVars, "host_vars"); err != nil {
   149  			errs = packer.MultiErrorAppend(errs, err)
   150  		}
   151  	}
   152  
   153  	for _, path := range p.config.PlaybookPaths {
   154  		err := validateDirConfig(path, "playbook_paths")
   155  		if err != nil {
   156  			errs = packer.MultiErrorAppend(errs, err)
   157  		}
   158  	}
   159  	for _, path := range p.config.RolePaths {
   160  		if err := validateDirConfig(path, "role_paths"); err != nil {
   161  			errs = packer.MultiErrorAppend(errs, err)
   162  		}
   163  	}
   164  
   165  	if errs != nil && len(errs.Errors) > 0 {
   166  		return errs
   167  	}
   168  	return nil
   169  }
   170  
   171  func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
   172  	ui.Say("Provisioning with Ansible...")
   173  
   174  	if len(p.config.PlaybookDir) > 0 {
   175  		ui.Message("Uploading Playbook directory to Ansible staging directory...")
   176  		if err := p.uploadDir(ui, comm, p.config.StagingDir, p.config.PlaybookDir); err != nil {
   177  			return fmt.Errorf("Error uploading playbook_dir directory: %s", err)
   178  		}
   179  	} else {
   180  		ui.Message("Creating Ansible staging directory...")
   181  		if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
   182  			return fmt.Errorf("Error creating staging directory: %s", err)
   183  		}
   184  	}
   185  
   186  	ui.Message("Uploading main Playbook file...")
   187  	src := p.config.PlaybookFile
   188  	dst := filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(src)))
   189  	if err := p.uploadFile(ui, comm, dst, src); err != nil {
   190  		return fmt.Errorf("Error uploading main playbook: %s", err)
   191  	}
   192  
   193  	if len(p.config.InventoryFile) == 0 {
   194  		tf, err := ioutil.TempFile("", "packer-provisioner-ansible-local")
   195  		if err != nil {
   196  			return fmt.Errorf("Error preparing inventory file: %s", err)
   197  		}
   198  		defer os.Remove(tf.Name())
   199  		_, err = tf.Write([]byte("127.0.0.1"))
   200  		if err != nil {
   201  			tf.Close()
   202  			return fmt.Errorf("Error preparing inventory file: %s", err)
   203  		}
   204  		tf.Close()
   205  		p.config.InventoryFile = tf.Name()
   206  		defer func() {
   207  			p.config.InventoryFile = ""
   208  		}()
   209  	}
   210  
   211  	ui.Message("Uploading inventory file...")
   212  	src = p.config.InventoryFile
   213  	dst = filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(src)))
   214  	if err := p.uploadFile(ui, comm, dst, src); err != nil {
   215  		return fmt.Errorf("Error uploading inventory file: %s", err)
   216  	}
   217  
   218  	if len(p.config.GroupVars) > 0 {
   219  		ui.Message("Uploading group_vars directory...")
   220  		src := p.config.GroupVars
   221  		dst := filepath.ToSlash(filepath.Join(p.config.StagingDir, "group_vars"))
   222  		if err := p.uploadDir(ui, comm, dst, src); err != nil {
   223  			return fmt.Errorf("Error uploading group_vars directory: %s", err)
   224  		}
   225  	}
   226  
   227  	if len(p.config.HostVars) > 0 {
   228  		ui.Message("Uploading host_vars directory...")
   229  		src := p.config.HostVars
   230  		dst := filepath.ToSlash(filepath.Join(p.config.StagingDir, "host_vars"))
   231  		if err := p.uploadDir(ui, comm, dst, src); err != nil {
   232  			return fmt.Errorf("Error uploading host_vars directory: %s", err)
   233  		}
   234  	}
   235  
   236  	if len(p.config.RolePaths) > 0 {
   237  		ui.Message("Uploading role directories...")
   238  		for _, src := range p.config.RolePaths {
   239  			dst := filepath.ToSlash(filepath.Join(p.config.StagingDir, "roles", filepath.Base(src)))
   240  			if err := p.uploadDir(ui, comm, dst, src); err != nil {
   241  				return fmt.Errorf("Error uploading roles: %s", err)
   242  			}
   243  		}
   244  	}
   245  
   246  	if len(p.config.PlaybookPaths) > 0 {
   247  		ui.Message("Uploading additional Playbooks...")
   248  		playbookDir := filepath.ToSlash(filepath.Join(p.config.StagingDir, "playbooks"))
   249  		if err := p.createDir(ui, comm, playbookDir); err != nil {
   250  			return fmt.Errorf("Error creating playbooks directory: %s", err)
   251  		}
   252  		for _, src := range p.config.PlaybookPaths {
   253  			dst := filepath.ToSlash(filepath.Join(playbookDir, filepath.Base(src)))
   254  			if err := p.uploadDir(ui, comm, dst, src); err != nil {
   255  				return fmt.Errorf("Error uploading playbooks: %s", err)
   256  			}
   257  		}
   258  	}
   259  
   260  	if err := p.executeAnsible(ui, comm); err != nil {
   261  		return fmt.Errorf("Error executing Ansible: %s", err)
   262  	}
   263  	return nil
   264  }
   265  
   266  func (p *Provisioner) Cancel() {
   267  	// Just hard quit. It isn't a big deal if what we're doing keeps
   268  	// running on the other side.
   269  	os.Exit(0)
   270  }
   271  
   272  func (p *Provisioner) executeAnsible(ui packer.Ui, comm packer.Communicator) error {
   273  	playbook := filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(p.config.PlaybookFile)))
   274  	inventory := filepath.ToSlash(filepath.Join(p.config.StagingDir, filepath.Base(p.config.InventoryFile)))
   275  
   276  	extraArgs := ""
   277  	if len(p.config.ExtraArguments) > 0 {
   278  		extraArgs = " " + strings.Join(p.config.ExtraArguments, " ")
   279  	}
   280  
   281  	command := fmt.Sprintf("cd %s && %s %s%s -c local -i %s",
   282  		p.config.StagingDir, p.config.Command, playbook, extraArgs, inventory)
   283  	ui.Message(fmt.Sprintf("Executing Ansible: %s", command))
   284  	cmd := &packer.RemoteCmd{
   285  		Command: command,
   286  	}
   287  	if err := cmd.StartWithUi(comm, ui); err != nil {
   288  		return err
   289  	}
   290  	if cmd.ExitStatus != 0 {
   291  		if cmd.ExitStatus == 127 {
   292  			return fmt.Errorf("%s could not be found. Verify that it is available on the\n"+
   293  				"PATH after connecting to the machine.",
   294  				p.config.Command)
   295  		}
   296  
   297  		return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus)
   298  	}
   299  	return nil
   300  }
   301  
   302  func validateDirConfig(path string, config string) error {
   303  	info, err := os.Stat(path)
   304  	if err != nil {
   305  		return fmt.Errorf("%s: %s is invalid: %s", config, path, err)
   306  	} else if !info.IsDir() {
   307  		return fmt.Errorf("%s: %s must point to a directory", config, path)
   308  	}
   309  	return nil
   310  }
   311  
   312  func validateFileConfig(name string, config string, req bool) error {
   313  	if req {
   314  		if name == "" {
   315  			return fmt.Errorf("%s must be specified.", config)
   316  		}
   317  	}
   318  	info, err := os.Stat(name)
   319  	if err != nil {
   320  		return fmt.Errorf("%s: %s is invalid: %s", config, name, err)
   321  	} else if info.IsDir() {
   322  		return fmt.Errorf("%s: %s must point to a file", config, name)
   323  	}
   324  	return nil
   325  }
   326  
   327  func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, dst, src string) error {
   328  	f, err := os.Open(src)
   329  	if err != nil {
   330  		return fmt.Errorf("Error opening: %s", err)
   331  	}
   332  	defer f.Close()
   333  
   334  	if err = comm.Upload(dst, f, nil); err != nil {
   335  		return fmt.Errorf("Error uploading %s: %s", src, err)
   336  	}
   337  	return nil
   338  }
   339  
   340  func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error {
   341  	ui.Message(fmt.Sprintf("Creating directory: %s", dir))
   342  	cmd := &packer.RemoteCmd{
   343  		Command: fmt.Sprintf("mkdir -p '%s'", dir),
   344  	}
   345  	if err := cmd.StartWithUi(comm, ui); err != nil {
   346  		return err
   347  	}
   348  	if cmd.ExitStatus != 0 {
   349  		return fmt.Errorf("Non-zero exit status.")
   350  	}
   351  	return nil
   352  }
   353  
   354  func (p *Provisioner) uploadDir(ui packer.Ui, comm packer.Communicator, dst, src string) error {
   355  	if err := p.createDir(ui, comm, dst); err != nil {
   356  		return err
   357  	}
   358  
   359  	// Make sure there is a trailing "/" so that the directory isn't
   360  	// created on the other side.
   361  	if src[len(src)-1] != '/' {
   362  		src = src + "/"
   363  	}
   364  	return comm.UploadDir(dst, src, nil)
   365  }