Skip to content

Commit

Permalink
Automatic package management installation + PoSH module installation
Browse files Browse the repository at this point in the history
  • Loading branch information
mefellows committed Apr 23, 2016
1 parent dd06ca0 commit f56982a
Show file tree
Hide file tree
Showing 8 changed files with 389 additions and 45 deletions.
29 changes: 22 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ A [Desired State Configuration](http://technet.microsoft.com/en-au/library/dn249

Works nicely when combined with a Vagrant development workflow, possibly leveraging the [Vagrant DSC](https://github.com/mefellows/vagrant-dsc) plugin.

Features:
* Automatically run DSC on a remote machine, from local DSC Configurations.
* Automatically configure PowerShell Package Management (specify `install_package_management` as `true`)
* Install DSC Resources from local or remote sources, including the PowerShell Gallery (see `resource_paths` and `install_modules`)
* Ability to use pre-generated MOF files, if required

[![Coverage Status](https://coveralls.io/repos/github/mefellows/packer-dsc/badge.svg?branch=HEAD)](https://coveralls.io/github/mefellows/packer-dsc?branch=HEAD)
[![wercker status](https://app.wercker.com/status/ef7336f65a3636531141a653e775d58f/s "wercker status")](https://app.wercker.com/project/bykey/ef7336f65a3636531141a653e775d58f)

### Getting Started

The plugin can be used by downloading pre-built binary, or by building the project locally and ensuring the binary is installed in the correct location.
The plugin can be used by downloading the pre-built binary, or by building the project locally and ensuring the binary is installed in the correct location.

### On Mac OSX using Homebrew

Expand Down Expand Up @@ -38,7 +44,7 @@ With [Go 1.2+](http://golang.org) installed, follow these steps to use these com
1. Run `make dev`
1. Copy the plugin binaries located in `./bin` to [a location where Packer will detect them at run-time](https://packer.io/docs/extend/plugins.html), such as any of the following:
- The directory where the packer binary is. If you've built Packer locally, then Packer and the new plugins are already in `$GOPATH/bin` together.
- `~/.packer.d/plugins` on Unix systems or `%APPDATA%/packer.d` on Windows.
- `~/.packer.d/plugins` on Unix systems or `%APPDATA%/packer.d/plugins` on Windows.
- The current working directory.
1. Change to a directory where you have packer templates, and run as usual.

Expand Down Expand Up @@ -83,23 +89,32 @@ Required parameters:

Optional parameters:

- `configuration_name` (string) - The name of the Configuration module. Defaults to the base name of
the `manifest_file`. e.g. `Default.ps1` would result in `Default`.
- `configuration_name` (string) - The name of the Configuration module. Defaults to the base
name of the `manifest_file`. e.g. `Default.ps1` would result in `Default`.

- `mof_path` (string) - Relative path to a folder, containing the pre-generated MOF file.

- `configuration_file` (string) - Relative path to the DSC Configuration Data file.
Configuration data is used to parameterise the configuration_file.

- `configuration_params` (object of key/value strings) - Set of Parameters to pass to the DSC Configuration.
- `configuration_params` (object of key/value strings) - Set of Parameters to pass to the DSC
Configuration.

- `module_paths` (array of strings) - Set of relative module paths.
These paths are added to the DSC Configuration running environment to enable _local_ modules to be addressed.

- `resource_paths` (array of strings) - Set of DSC Resources to upload for system-wide use.
These paths are uploaded into `%SystemDrive%\WindowsPowershell\Modules` to be used system-wide, unlike
These paths are uploaded into `${env:programfiles}\WindowsPowershell\Modules` to be used system-wide, unlike
`module_paths` which is scoped to the current Configuration.

`install_modules` (array of strings) - Set of PowerShell modules to be installed
with the `Install-Module` command. See `install_package_management` if you would
like the DSC Provisioner to install this command for you.

`install_package_management` (bool) - Automatically installs the
[Package Management](https://github.com/OneGet/oneget) package manager
(formerly OneGet) on the server.

- `staging_dir` (string) - The directory where files will be uploaded.
Packer requires write permissions in this directory.

Expand All @@ -109,7 +124,7 @@ Optional parameters:
Packer requires the directory to exist when running DSC.

- `ignore_exit_codes` (boolean) - If true, Packer will never consider the
provisioner a failure.
DSC provisioning process a failure.

- `execute_command` (string) - The command used to execute DSC. This has
various [configuration template
Expand Down
10 changes: 9 additions & 1 deletion dsc.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ Optional parameters:
These paths are uploaded into `%SystemDrive%\WindowsPowershell\Modules` to be used system-wide, unlike
`module_paths` which is scoped to the current Configuration.

`install_modules` (array of strings) - Set of PowerShell modules to be installed
with the `Install-Module` command. See `install_package_management` if you would
like the DSC Provisioner to install this command for you.

`install_package_management` (bool) - Automatically installs the
[Package Management](https://github.com/OneGet/oneget) package manager
(formerly OneGet) on the server.

- `staging_dir` (string) - The directory where files will be uploaded.
Packer requires write permissions in this directory.

Expand All @@ -75,7 +83,7 @@ Optional parameters:
Packer requires the directory to exist when running DSC.

- `ignore_exit_codes` (boolean) - If true, Packer will never consider the
provisioner a failure.
DSC provisioning a failure.

- `execute_command` (string) - The command used to execute DSC. This has
various [configuration template
Expand Down
19 changes: 7 additions & 12 deletions examples/packer.aws.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,6 @@
}
],
"provisioners": [
{
"type":"powershell",
"inline": [
"Write-Verbose 'Install PackageManagement'",
"(New-Object System.Net.WebClient).DownloadFile('https://download.microsoft.com/download/C/4/1/C41378D4-7F41-4BBE-9D0D-0E4F98585C61/PackageManagement_x64.msi','c:\\PackageManagement_x64.msi')",
"msiexec /qb /i C:\\PackageManagement_x64.msi",
"Start-Sleep -s 20",
"Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force",
"Install-Module -Name xWebAdministration -Force",
"Get-DSCResource"
]
},
{
"type": "dsc",
"configuration_name": "Beanstalk",
Expand All @@ -46,6 +34,13 @@
"module_paths": [
"modules"
],
"install_package_management": true,
"install_modules": {
"xWebAdministration": "1.10.0.0"
},
"module_paths": [
"modules"
],
"configuration_params": {
"-WebAppPath": "c:\\tmp",
"-MachineName": "localhost"
Expand Down
18 changes: 5 additions & 13 deletions examples/packer.ovf.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"guest_additions_mode": "disable",
"boot_wait": "30s",
"winrm_username": "vagrant",
"winrm_password": "vagrant",
"winrm_password": "FooBar@123",
"winrm_timeout": "5m",
"winrm_port":5985,
"winrm_host":"localhost",
Expand Down Expand Up @@ -47,23 +47,15 @@
}
],
"provisioners": [
{
"type":"powershell",
"inline": [
"Write-Verbose 'Install PackageManagement'",
"(New-Object System.Net.WebClient).DownloadFile('https://download.microsoft.com/download/C/4/1/C41378D4-7F41-4BBE-9D0D-0E4F98585C61/PackageManagement_x64.msi','c:\\PackageManagement_x64.msi')",
"msiexec /qb /i C:\\PackageManagement_x64.msi",
"Start-Sleep -s 5",
"Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force",
"Install-Module -Name xWebAdministration -Force",
"Get-DSCResource"
]
},
{
"type": "dsc",
"configuration_name": "Beanstalk",
"configuration_file": "manifests/BeanstalkConfig.psd1",
"manifest_file": "manifests/Beanstalk.ps1",
"install_package_management": true,
"install_modules": {
"xWebAdministration": "1.10.0.0"
},
"module_paths": [
"modules"
],
Expand Down
9 changes: 9 additions & 0 deletions provisioner/dsc/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ type Config struct {
// to be used system-wide.
ResourcePaths []string `mapstructure:"resource_paths"`

// Install the latest Windows PackageManagement software?
InstallPackageManagement bool `mapstructure:"install_package_management"`

// Modules to install, using the latest PackageManagement tooling
// e.g. { "xWebAdministration": "1.0.0.0" }
//
// See InstallPackageManagement if
InstallModules map[string]string `mapstructure:"install_modules"`

// The directory where files will be uploaded. Packer requires write
// permissions in this directory.
StagingDir string `mapstructure:"staging_dir"`
Expand Down
89 changes: 86 additions & 3 deletions provisioner/dsc/provisioner.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// This package implements a provisioner for Packer that executes
// Package dsc implements a provisioner for Packer that executes
// DSC on the remote machine, configured to apply a local manifest
// versus connecting to a DSC push server.
//
Expand All @@ -17,10 +17,13 @@ import (
"github.com/mitchellh/packer/template/interpolate"
)

// Provisioner DSC
type Provisioner struct {
config Config
}

// ExecuteTemplate contains the template variables interpolated
// into the running DSC script
type ExecuteTemplate struct {
WorkingDir string
ConfigurationParams string
Expand All @@ -32,6 +35,9 @@ type ExecuteTemplate struct {
MofPath string
}

var powershellTemplate = `powershell "& { %s; exit $LastExitCode}"`

// Prepare sets up the DSC configuration
func (p *Provisioner) Prepare(raws ...interface{}) error {
err := config.Decode(&p.config, &config.DecodeOpts{
Interpolate: true,
Expand Down Expand Up @@ -167,13 +173,19 @@ Start-DscConfiguration -Force -Wait -Verbose -Path $StagingPath`
return nil
}

// Provision the remote machine with DSC
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
ui.Say("Provisioning with DSC...")
ui.Message("Creating DSC staging directory...")
if err := p.createDir(ui, comm, p.config.StagingDir); err != nil {
return fmt.Errorf("Error creating staging directory: %s", err)
}

// Install PackageManagement
if p.config.InstallPackageManagement {
p.installPackageManagement(ui, comm)
}

// Upload configuration_params config if set
remoteConfigurationFilePath := ""
if p.config.ConfigurationFilePath != "" {
Expand All @@ -196,6 +208,14 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
}
}

// Install any remote PowerShell modules
for k, v := range p.config.InstallModules {
err := p.installPackage(ui, comm, k, v)
if err != nil {
return err
}
}

// Upload all modules
modulePaths := make([]string, 0, len(p.config.ModulePaths))
for i, path := range p.config.ModulePaths {
Expand Down Expand Up @@ -270,7 +290,7 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
}

// Return command to run the DSC Runner
command := fmt.Sprintf(`powershell "& { %s; exit $LastExitCode}"`, remoteScriptPath)
command := fmt.Sprintf(powershellTemplate, remoteScriptPath)
cmd := &packer.RemoteCmd{
Command: command,
}
Expand Down Expand Up @@ -305,6 +325,7 @@ func (p *Provisioner) createDscScript(tpml ExecuteTemplate) (string, error) {
return file.Name(), err
}

// Cancel a running DSC session. This is a no-op
func (p *Provisioner) Cancel() {
// Just hard quit. It isn't a big deal if what we're doing keeps
// running on the other side.
Expand Down Expand Up @@ -352,7 +373,6 @@ func (p *Provisioner) uploadManifest(ui packer.Ui, comm packer.Communicator) (st
}

func (p *Provisioner) uploadDscRunner(ui packer.Ui, comm packer.Communicator, file string) (string, error) {
ui.Message("Uploading runner...")
ui.Message(fmt.Sprintf("Uploading DSC runner from: %s", file))

f, err := os.Open(file)
Expand Down Expand Up @@ -400,6 +420,69 @@ func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir stri
return nil
}

// Template to upload as a script and provision Package Management
var installTemplate = `
Write-Verbose 'Install PackageManagement'
(New-Object System.Net.WebClient).DownloadFile('https://download.microsoft.com/download/C/4/1/C41378D4-7F41-4BBE-9D0D-0E4F98585C61/PackageManagement_x64.msi','c:\PackageManagement_x64.msi')
Start-Process -FilePath "msiexec.exe" -ArgumentList "/qb /i C:\PackageManagement_x64.msi" -Wait -Passthru
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Get-DSCResource
`

// Install a package on the remote host
func (p *Provisioner) installPackageManagement(ui packer.Ui, comm packer.Communicator) error {
ui.Message("Installing PowerShell Package Management")

// Inject template variables
script, err := interpolate.Render(installTemplate, &p.config.ctx)
if err != nil {
return err
}

// Upload script
file, _ := ioutil.TempFile("/tmp", "packer-dsc-packagemanagement")
err = ioutil.WriteFile(file.Name(), []byte(script), 0655)

remoteScriptFile := fmt.Sprintf("/tmp/%s.ps1", filepath.Base(file.Name()))
if err := comm.Upload(remoteScriptFile, file, nil); err != nil {
return err
}

// Run script
cmd := &packer.RemoteCmd{
Command: fmt.Sprintf(`powershell "& { %s; exit $LastExitCode}"`, remoteScriptFile),
}

if err := cmd.StartWithUi(comm, ui); err != nil {
return err
}

if cmd.ExitStatus != 0 {
return fmt.Errorf("Install Package Management return a non-zero exit status: %d", cmd.ExitStatus)
}

return nil
}

// Install a package on the remote host
func (p *Provisioner) installPackage(ui packer.Ui, comm packer.Communicator, pkg string, version string) error {
ui.Message(fmt.Sprintf("Installing PowerShell package '%s'", pkg))

cmd := &packer.RemoteCmd{
Command: fmt.Sprintf(powershellTemplate, fmt.Sprintf("Install-Module -Name %s -RequiredVersion %s -Force", pkg, version)),
}

if err := cmd.StartWithUi(comm, ui); err != nil {
return err
}

if cmd.ExitStatus != 0 {
return fmt.Errorf("PowerShell module install exited with a non-zero exit status: %d", cmd.ExitStatus)
}

return nil
}

func (p *Provisioner) uploadDirectory(ui packer.Ui, comm packer.Communicator, dst string, src string) error {
if err := p.createDir(ui, comm, dst); err != nil {
return err
Expand Down

0 comments on commit f56982a

Please sign in to comment.