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