Implement cd_files option. The Addfile code is modified from the floppy_files

tooling. Tested on OSX, `Windows, and Linux. Documented command line dependencies.
Added acceptance tests (behind testacc flag, since there are dependencies).
This option is only implemented for the vmware and hyperv iso builders. It will be
implemented for others in later PRs in order to make review easier.
This commit is contained in:
Megan Marsh 2020-08-19 14:09:08 -07:00
parent dfe8aa51b0
commit d2dd158fe2
18 changed files with 622 additions and 8 deletions

View File

@ -37,6 +37,7 @@ const (
type CommonConfig struct { type CommonConfig struct {
common.FloppyConfig `mapstructure:",squash"` common.FloppyConfig `mapstructure:",squash"`
common.CDConfig `mapstructure:",squash"`
// The block size of the VHD to be created. // The block size of the VHD to be created.
// Recommended disk block size for Linux hyper-v guests is 1 MiB. This // Recommended disk block size for Linux hyper-v guests is 1 MiB. This
// defaults to "32" MiB. // defaults to "32" MiB.
@ -210,8 +211,8 @@ func (c *CommonConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig
} }
// Errors // Errors
floppyerrs := c.FloppyConfig.Prepare(ctx) errs = append(errs, c.FloppyConfig.Prepare(ctx)...)
errs = append(errs, floppyerrs...) errs = append(errs, c.CDConfig.Prepare(ctx)...)
if c.GuestAdditionsMode == "" { if c.GuestAdditionsMode == "" {
if c.GuestAdditionsPath != "" { if c.GuestAdditionsPath != "" {
c.GuestAdditionsMode = "attach" c.GuestAdditionsMode = "attach"

View File

@ -34,7 +34,17 @@ func (s *StepMountSecondaryDvdImages) Run(ctx context.Context, state multistep.S
// For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1)
var dvdProperties []DvdControllerProperties var dvdProperties []DvdControllerProperties
for _, isoPath := range s.IsoPaths { isoPaths := s.IsoPaths
// Add our custom CD, if it exists
cd_path, ok := state.Get("cd_path").(string)
if ok {
if cd_path != "" {
isoPaths = append(isoPaths, cd_path)
}
}
for _, isoPath := range isoPaths {
var properties DvdControllerProperties var properties DvdControllerProperties
controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, isoPath, s.Generation) controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, isoPath, s.Generation)

View File

@ -254,7 +254,10 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
GuestAdditionsPath: b.config.GuestAdditionsPath, GuestAdditionsPath: b.config.GuestAdditionsPath,
Generation: b.config.Generation, Generation: b.config.Generation,
}, },
&common.StepCreateCD{
Files: b.config.CDConfig.CDFiles,
Label: b.config.CDConfig.CDLabel,
},
&hypervcommon.StepMountSecondaryDvdImages{ &hypervcommon.StepMountSecondaryDvdImages{
IsoPaths: b.config.SecondaryDvdImages, IsoPaths: b.config.SecondaryDvdImages,
Generation: b.config.Generation, Generation: b.config.Generation,

View File

@ -79,6 +79,8 @@ type FlatConfig struct {
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"` FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"` FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"` FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
CDFiles []string `mapstructure:"cd_files" cty:"cd_files" hcl:"cd_files"`
CDLabel *string `mapstructure:"cd_label" cty:"cd_label" hcl:"cd_label"`
DiskBlockSize *uint `mapstructure:"disk_block_size" required:"false" cty:"disk_block_size" hcl:"disk_block_size"` DiskBlockSize *uint `mapstructure:"disk_block_size" required:"false" cty:"disk_block_size" hcl:"disk_block_size"`
RamSize *uint `mapstructure:"memory" required:"false" cty:"memory" hcl:"memory"` RamSize *uint `mapstructure:"memory" required:"false" cty:"memory" hcl:"memory"`
SecondaryDvdImages []string `mapstructure:"secondary_iso_images" required:"false" cty:"secondary_iso_images" hcl:"secondary_iso_images"` SecondaryDvdImages []string `mapstructure:"secondary_iso_images" required:"false" cty:"secondary_iso_images" hcl:"secondary_iso_images"`
@ -195,6 +197,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false}, "floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false}, "floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false}, "floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
"cd_files": &hcldec.AttrSpec{Name: "cd_files", Type: cty.List(cty.String), Required: false},
"cd_label": &hcldec.AttrSpec{Name: "cd_label", Type: cty.String, Required: false},
"disk_block_size": &hcldec.AttrSpec{Name: "disk_block_size", Type: cty.Number, Required: false}, "disk_block_size": &hcldec.AttrSpec{Name: "disk_block_size", Type: cty.Number, Required: false},
"memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false}, "memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false},
"secondary_iso_images": &hcldec.AttrSpec{Name: "secondary_iso_images", Type: cty.List(cty.String), Required: false}, "secondary_iso_images": &hcldec.AttrSpec{Name: "secondary_iso_images", Type: cty.List(cty.String), Required: false},

View File

@ -79,6 +79,8 @@ type FlatConfig struct {
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"` FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"` FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"` FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
CDFiles []string `mapstructure:"cd_files" cty:"cd_files" hcl:"cd_files"`
CDLabel *string `mapstructure:"cd_label" cty:"cd_label" hcl:"cd_label"`
DiskBlockSize *uint `mapstructure:"disk_block_size" required:"false" cty:"disk_block_size" hcl:"disk_block_size"` DiskBlockSize *uint `mapstructure:"disk_block_size" required:"false" cty:"disk_block_size" hcl:"disk_block_size"`
RamSize *uint `mapstructure:"memory" required:"false" cty:"memory" hcl:"memory"` RamSize *uint `mapstructure:"memory" required:"false" cty:"memory" hcl:"memory"`
SecondaryDvdImages []string `mapstructure:"secondary_iso_images" required:"false" cty:"secondary_iso_images" hcl:"secondary_iso_images"` SecondaryDvdImages []string `mapstructure:"secondary_iso_images" required:"false" cty:"secondary_iso_images" hcl:"secondary_iso_images"`
@ -197,6 +199,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false}, "floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false}, "floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false}, "floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
"cd_files": &hcldec.AttrSpec{Name: "cd_files", Type: cty.List(cty.String), Required: false},
"cd_label": &hcldec.AttrSpec{Name: "cd_label", Type: cty.String, Required: false},
"disk_block_size": &hcldec.AttrSpec{Name: "disk_block_size", Type: cty.Number, Required: false}, "disk_block_size": &hcldec.AttrSpec{Name: "disk_block_size", Type: cty.Number, Required: false},
"memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false}, "memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false},
"secondary_iso_images": &hcldec.AttrSpec{Name: "secondary_iso_images", Type: cty.List(cty.String), Required: false}, "secondary_iso_images": &hcldec.AttrSpec{Name: "secondary_iso_images", Type: cty.List(cty.String), Required: false},

View File

@ -76,12 +76,22 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
Directories: b.config.FloppyConfig.FloppyDirectories, Directories: b.config.FloppyConfig.FloppyDirectories,
Label: b.config.FloppyConfig.FloppyLabel, Label: b.config.FloppyConfig.FloppyLabel,
}, },
&common.StepCreateCD{
Files: b.config.CDConfig.CDFiles,
Label: b.config.CDConfig.CDLabel,
},
&vmwcommon.StepRemoteUpload{ &vmwcommon.StepRemoteUpload{
Key: "floppy_path", Key: "floppy_path",
Message: "Uploading Floppy to remote machine...", Message: "Uploading Floppy to remote machine...",
DoCleanup: true, DoCleanup: true,
Checksum: "none", Checksum: "none",
}, },
&vmwcommon.StepRemoteUpload{
Key: "cd_path",
Message: "Uploading CD to remote machine...",
DoCleanup: true,
Checksum: "none",
},
&vmwcommon.StepRemoteUpload{ &vmwcommon.StepRemoteUpload{
Key: "iso_path", Key: "iso_path",
Message: "Uploading ISO to remote machine...", Message: "Uploading ISO to remote machine...",

View File

@ -23,6 +23,7 @@ type Config struct {
common.HTTPConfig `mapstructure:",squash"` common.HTTPConfig `mapstructure:",squash"`
common.ISOConfig `mapstructure:",squash"` common.ISOConfig `mapstructure:",squash"`
common.FloppyConfig `mapstructure:",squash"` common.FloppyConfig `mapstructure:",squash"`
common.CDConfig `mapstructure:",squash"`
bootcommand.VNCConfig `mapstructure:",squash"` bootcommand.VNCConfig `mapstructure:",squash"`
vmwcommon.DriverConfig `mapstructure:",squash"` vmwcommon.DriverConfig `mapstructure:",squash"`
vmwcommon.HWConfig `mapstructure:",squash"` vmwcommon.HWConfig `mapstructure:",squash"`
@ -110,6 +111,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
errs = packer.MultiErrorAppend(errs, c.ToolsConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.ToolsConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.VMXConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.VMXConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.FloppyConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.CDConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.VNCConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.ExportConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.ExportConfig.Prepare(&c.ctx)...)
errs = packer.MultiErrorAppend(errs, c.DiskConfig.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.DiskConfig.Prepare(&c.ctx)...)

View File

@ -28,6 +28,8 @@ type FlatConfig struct {
FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"` FloppyFiles []string `mapstructure:"floppy_files" cty:"floppy_files" hcl:"floppy_files"`
FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"` FloppyDirectories []string `mapstructure:"floppy_dirs" cty:"floppy_dirs" hcl:"floppy_dirs"`
FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"` FloppyLabel *string `mapstructure:"floppy_label" cty:"floppy_label" hcl:"floppy_label"`
CDFiles []string `mapstructure:"cd_files" cty:"cd_files" hcl:"cd_files"`
CDLabel *string `mapstructure:"cd_label" cty:"cd_label" hcl:"cd_label"`
BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"` BootGroupInterval *string `mapstructure:"boot_keygroup_interval" cty:"boot_keygroup_interval" hcl:"boot_keygroup_interval"`
BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"` BootWait *string `mapstructure:"boot_wait" cty:"boot_wait" hcl:"boot_wait"`
BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"` BootCommand []string `mapstructure:"boot_command" cty:"boot_command" hcl:"boot_command"`
@ -167,6 +169,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
"floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false}, "floppy_files": &hcldec.AttrSpec{Name: "floppy_files", Type: cty.List(cty.String), Required: false},
"floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false}, "floppy_dirs": &hcldec.AttrSpec{Name: "floppy_dirs", Type: cty.List(cty.String), Required: false},
"floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false}, "floppy_label": &hcldec.AttrSpec{Name: "floppy_label", Type: cty.String, Required: false},
"cd_files": &hcldec.AttrSpec{Name: "cd_files", Type: cty.List(cty.String), Required: false},
"cd_label": &hcldec.AttrSpec{Name: "cd_label", Type: cty.String, Required: false},
"boot_keygroup_interval": &hcldec.AttrSpec{Name: "boot_keygroup_interval", Type: cty.String, Required: false}, "boot_keygroup_interval": &hcldec.AttrSpec{Name: "boot_keygroup_interval", Type: cty.String, Required: false},
"boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false}, "boot_wait": &hcldec.AttrSpec{Name: "boot_wait", Type: cty.String, Required: false},
"boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false}, "boot_command": &hcldec.AttrSpec{Name: "boot_command", Type: cty.List(cty.String), Required: false},

View File

@ -34,6 +34,7 @@ type vmxTemplateData struct {
DiskType string DiskType string
CDROMType string CDROMType string
CDROMType_PrimarySecondary string CDROMType_PrimarySecondary string
CDROM_PATH string
Network_Type string Network_Type string
Network_Device string Network_Device string
@ -112,6 +113,7 @@ func (s *stepCreateVMX) Run(ctx context.Context, state multistep.StateBag) multi
ictx := config.ctx ictx := config.ctx
// Mount extra vmdks we created earlier.
if len(config.AdditionalDiskSize) > 0 { if len(config.AdditionalDiskSize) > 0 {
for i := range config.AdditionalDiskSize { for i := range config.AdditionalDiskSize {
ictx.Data = &additionalDiskTemplateData{ ictx.Data = &additionalDiskTemplateData{
@ -238,6 +240,15 @@ func (s *stepCreateVMX) Run(ctx context.Context, state multistep.StateBag) multi
return multistep.ActionHalt return multistep.ActionHalt
} }
// Add our custom CD, if it exists
cd_path, ok := state.Get("cd_path").(string)
if ok {
if cd_path != "" {
vmxTemplate += ExtraCDRomTemplate
templateData.CDROM_PATH = cd_path
}
}
/// Now that we figured out the CDROM device to add, store it /// Now that we figured out the CDROM device to add, store it
/// to the list of temporary build devices in our statebag /// to the list of temporary build devices in our statebag
tmpBuildDevices := state.Get("temporaryDevices").([]string) tmpBuildDevices := state.Get("temporaryDevices").([]string)
@ -596,3 +607,9 @@ scsi0:{{ .DiskNumber }}.fileName = "{{ .DiskName}}-{{ .DiskNumber }}.vmdk"
scsi0:{{ .DiskNumber }}.present = "TRUE" scsi0:{{ .DiskNumber }}.present = "TRUE"
scsi0:{{ .DiskNumber }}.redo = "" scsi0:{{ .DiskNumber }}.redo = ""
` `
const ExtraCDRomTemplate = `
{{ .CDROMType }}1:{{ .CDROMType_PrimarySecondary }}.present = "TRUE"
{{ .CDROMType }}1:{{ .CDROMType_PrimarySecondary }}.fileName = "{{ .CDROM_PATH }}"
{{ .CDROMType }}1:{{ .CDROMType_PrimarySecondary }}.deviceType = "cdrom-image"
`

View File

@ -0,0 +1,98 @@
//go:generate struct-markdown
package common
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/hashicorp/packer/template/interpolate"
)
// An iso (CD) containing custom files can be made available for your build.
//
// By default, no extra CD will be attached. All files listed in this setting
// get placed into the root directory of the CD and the CD is attached as the
// second CD device.
//
// This config exists to work around modern operating systems that have no
// way to mount floppy disks, which was our previous go-to for adding files at
// boot time.
type CDConfig struct {
// A list of files to place onto a CD that is attached when the VM is
// booted. This can include either files or directories; any directories
// will be copied onto the CD recursively, preserving directory structure
// hierarchy. Symlinks will have the link's target copied into the directory
// tree on the CD where the symlink was. File globbing is allowed.
//
// Usage example (JSON):
//
// ```json
// "cd_files": ["./somedirectory/meta-data", "./somedirectory/user-data"],
// "cd_label": "cidata",
// ```
//
// Usage example (HCL):
//
// ```hcl
// cd_files = ["./somedirectory/meta-data", "./somedirectory/user-data"]
// cd_label = "cidata"
// ```
//
// The above will create a CD with two files, user-data and meta-data in the
// CD root. This specific example is how you would create a CD that can be
// used for an Ubuntu 20.04 autoinstall.
//
// Since globbing is also supported,
//
// ```hcl
// cd_files = ["./somedirectory/*"]
// cd_label = "cidata"
// ```
//
// Would also be an acceptable way to define the above cd. The difference
// between providing the directory with or without the glob is whether the
// directory itself or its contents will be at the CD root.
//
// Use of this option assums that you have a command line tool isntalled
// that can handle the iso creation. If you are running Packer from an OSX host,
// the required tool is is hdiutil which comes preinstalled.
// On linux hosts, you need to have mkisofs.
// On Windows, you must have oscdimg.exe. oscdimg.exe is part of the
// Windows ADK tooks, downloadable from
// https://docs.microsoft.com/en-us/windows-hardware/get-started/adk-install#winADK
CDFiles []string `mapstructure:"cd_files"`
CDLabel string `mapstructure:"cd_label"`
}
func (c *CDConfig) Prepare(ctx *interpolate.Context) []error {
var errs []error
var err error
if c.CDFiles == nil {
c.CDFiles = make([]string, 0)
}
// Create new file list based on globbing.
var files []string
for _, path := range c.CDFiles {
if strings.ContainsAny(path, "*?[") {
var globbedFiles []string
globbedFiles, err = filepath.Glob(path)
if len(globbedFiles) > 0 {
files = append(files, globbedFiles...)
}
} else {
_, err = os.Stat(path)
files = append(files, path)
}
if err != nil {
errs = append(errs, fmt.Errorf("Bad CD disk file '%s': %s", path, err))
}
c.CDFiles = files
}
return errs
}

View File

@ -0,0 +1,73 @@
package common
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCDPrepare(t *testing.T) {
type testCases struct {
CDConfig CDConfig
ErrExpected bool
Reason string
ExpectedCDFiles []string
}
tcs := []testCases{
{
CDConfig: CDConfig{},
ErrExpected: false,
Reason: "TestNilCD: nil CD array should not fail",
ExpectedCDFiles: []string{},
},
{
CDConfig: CDConfig{CDFiles: make([]string, 0)},
ErrExpected: false,
Reason: "TestEmptyArrayCD: empty CD array should never fail",
ExpectedCDFiles: []string{},
},
{
CDConfig: CDConfig{CDFiles: []string{"extra_iso_config.go"}},
ErrExpected: false,
Reason: "TestExistingCDFile: array with existing CD should not fail",
ExpectedCDFiles: []string{"extra_iso_config.go"},
},
{
CDConfig: CDConfig{CDFiles: []string{"does_not_exist.foo"}},
ErrExpected: true,
Reason: "TestNonExistingCDFile: array with non existing CD should return errors",
ExpectedCDFiles: []string{"does_not_exist.foo"},
},
{
CDConfig: CDConfig{CDFiles: []string{"extra_iso_config*"}},
ErrExpected: false,
Reason: "TestGlobbingCDFile: Glob should work",
ExpectedCDFiles: []string{"extra_iso_config.go", "extra_iso_config_test.go"},
},
}
for _, tc := range tcs {
c := tc.CDConfig
errs := c.Prepare(nil)
if (len(errs) != 0) != tc.ErrExpected {
t.Fatal(tc.Reason)
}
assert.Equal(t, c.CDFiles, tc.ExpectedCDFiles)
}
}
func TestMultiErrorCDFiles(t *testing.T) {
c := CDConfig{
CDFiles: []string{"extra_iso_config.foo", "extra_iso_config.go",
"extra_iso_config.bar", "extra_iso_config_test.go", "extra_iso_config.baz"},
}
errs := c.Prepare(nil)
if len(errs) == 0 {
t.Fatal("array with non existing CD should return errors")
}
expectedErrors := 3
if count := len(errs); count != expectedErrors {
t.Fatalf("array with %v non existing CD should return %v errors but it is returning %v", expectedErrors, expectedErrors, count)
}
}

201
common/step_create_cdrom.go Normal file
View File

@ -0,0 +1,201 @@
package common
import (
"context"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"github.com/hashicorp/packer/helper/builder/localexec"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/packer/tmp"
)
// StepCreateCD will create a CD disk with the given files.
type StepCreateCD struct {
// Files can be either files or directories. Any files provided here will
// be written to the root of the CD. Directories will be written to the
// root of the CD as well, but will retain their subdirectory structure.
Files []string
Label string
CDPath string
filesAdded map[string]bool
}
func (s *StepCreateCD) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
if len(s.Files) == 0 {
log.Println("No CD files specified. CD disk will not be made.")
return multistep.ActionContinue
}
ui := state.Get("ui").(packer.Ui)
ui.Say("Creating CD disk...")
if s.Label == "" {
s.Label = "packer"
} else {
log.Printf("CD label is set to %s", s.Label)
}
// Track what files are added. Used for testing step.
s.filesAdded = make(map[string]bool)
// Create a temporary file to be our CD drive
CDF, err := tmp.File("packer*.iso")
// Set the path so we can remove it later
CDPath := CDF.Name()
CDF.Close()
os.Remove(CDPath)
if err != nil {
state.Put("error",
fmt.Errorf("Error creating temporary file for CD: %s", err))
return multistep.ActionHalt
}
log.Printf("CD path: %s", CDPath)
s.CDPath = CDPath
// Consolidate all files provided into a single directory to become our
// "root" directory.
rootFolder, err := tmp.Dir("packer_to_cdrom")
if err != nil {
state.Put("error",
fmt.Errorf("Error creating temporary file for CD: %s", err))
return multistep.ActionHalt
}
for _, toAdd := range s.Files {
err = s.AddFile(rootFolder, toAdd)
if err != nil {
state.Put("error",
fmt.Errorf("Error creating temporary file for CD: %s", err))
return multistep.ActionHalt
}
}
cmd := retrieveCommandForOS(s.Label, rootFolder, CDPath)
err = localexec.RunAndStream(cmd, ui, []string{})
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
ui.Message("Done copying paths from CD_dirs")
// Set the path to the CD so it can be used later
state.Put("cd_path", CDPath)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}
return multistep.ActionContinue
}
func (s *StepCreateCD) Cleanup(multistep.StateBag) {
if s.CDPath != "" {
log.Printf("Deleting CD disk: %s", s.CDPath)
os.Remove(s.CDPath)
}
}
func retrieveCommandForOS(label string, source string, dest string) *exec.Cmd {
switch runtime.GOOS {
case "windows":
cmd := exec.Command("oscdimg")
args := []string{"-j1", "-o", "-m", "-l" + label, source, dest}
cmd.Args = append(cmd.Args, args...)
return cmd
case "darwin":
cmd := exec.Command("hdiutil")
args := []string{"makehybrid", "-o", dest, "-hfs",
"-joliet", "-iso", "-default-volume-name", label, source}
cmd.Args = append(cmd.Args, args...)
return cmd
default:
cmd := exec.Command("mkisofs")
args := []string{"-joliet", "-volid", label, "-o", dest, source}
cmd.Args = append(cmd.Args, args...)
return cmd
}
}
func (s *StepCreateCD) AddFile(dst, src string) error {
finfo, err := os.Stat(src)
if err != nil {
return fmt.Errorf("Error adding path to CD: %s", err)
}
// add a file
if !finfo.IsDir() {
inputF, err := os.Open(src)
if err != nil {
return err
}
defer inputF.Close()
// Create a new file in the root directory
dest, err := os.Create(filepath.Join(dst, finfo.Name()))
if err != nil {
return fmt.Errorf("Error opening file for copy %s to CD root", src)
}
defer dest.Close()
nBytes, err := io.Copy(dest, inputF)
if err != nil {
return fmt.Errorf("Error copying %s to CD root", src)
}
s.filesAdded[src] = true
log.Printf("Wrote %d bytes to %s", nBytes, finfo.Name())
return err
}
// Add a directory and its subdirectories
visit := func(pathname string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
// add a file
if !fi.IsDir() {
inputF, err := os.Open(pathname)
if err != nil {
return err
}
defer inputF.Close()
fileDst, err := os.Create(filepath.Join(dst, pathname))
if err != nil {
return fmt.Errorf("Error opening file %s on CD", src)
}
defer fileDst.Close()
nBytes, err := io.Copy(fileDst, inputF)
if err != nil {
return fmt.Errorf("Error copying %s to CD", src)
}
s.filesAdded[pathname] = true
log.Printf("Wrote %d bytes to %s", nBytes, pathname)
return err
}
if fi.Mode().IsDir() {
// create the directory on the CD, continue walk.
err := os.Mkdir(filepath.Join(dst, pathname), fi.Mode())
if err != nil {
err = fmt.Errorf("error creating new directory %s: %s",
filepath.Join(dst, pathname), err)
}
return err
}
return err
}
return filepath.Walk(src, visit)
}

View File

@ -0,0 +1,119 @@
package common
import (
"bytes"
"context"
"io/ioutil"
"os"
"path"
"testing"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
func TestStepCreateCD_Impl(t *testing.T) {
var raw interface{}
raw = new(StepCreateCD)
if _, ok := raw.(multistep.Step); !ok {
t.Fatalf("StepCreateCD should be a step")
}
}
func testStepCreateCDState(t *testing.T) multistep.StateBag {
state := new(multistep.BasicStateBag)
state.Put("ui", &packer.BasicUi{
Reader: new(bytes.Buffer),
Writer: new(bytes.Buffer),
})
return state
}
func TestStepCreateCD(t *testing.T) {
if os.Getenv("PACKER_ACC") == "" {
t.Skip("This test is only run with PACKER_ACC=1 due to the requirement of access to the disk management binaries.")
}
state := testStepCreateCDState(t)
step := new(StepCreateCD)
dir, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(dir)
files := make([]string, 3)
tempFileNames := []string{"test_cd_roms.tmp", "test cd files.tmp",
"Test-Test-Test5.tmp"}
for i, fname := range tempFileNames {
files[i] = path.Join(dir, fname)
_, err := os.Create(files[i])
if err != nil {
t.Fatalf("err: %s", err)
}
}
step.Files = files
action := step.Run(context.Background(), state)
if err, ok := state.GetOk("error"); ok {
t.Fatalf("state should be ok for %v: %s", step.Files, err)
}
if action != multistep.ActionContinue {
t.Fatalf("bad action: %#v for %v", action, step.Files)
}
CD_path := state.Get("cd_path").(string)
if _, err := os.Stat(CD_path); err != nil {
t.Fatalf("file not found: %s for %v", CD_path, step.Files)
}
if len(step.filesAdded) != 3 {
t.Fatalf("expected 3 files, found %d for %v", len(step.filesAdded), step.Files)
}
step.Cleanup(state)
if _, err := os.Stat(CD_path); err == nil {
t.Fatalf("file found: %s for %v", CD_path, step.Files)
}
}
func TestStepCreateCD_missing(t *testing.T) {
if os.Getenv("PACKER_ACC") == "" {
t.Skip("This test is only run with PACKER_ACC=1 due to the requirement of access to the disk management binaries.")
}
state := testStepCreateCDState(t)
step := new(StepCreateCD)
dir, err := ioutil.TempDir("", "packer")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(dir)
expected := 0
step.Files = []string{"missing file.tmp"}
if action := step.Run(context.Background(), state); action != multistep.ActionHalt {
t.Fatalf("bad action: %#v for %v", action, step.Files)
}
if _, ok := state.GetOk("error"); !ok {
t.Fatalf("state should not be ok for %v", step.Files)
}
CD_path := state.Get("cd_path")
if CD_path != nil {
t.Fatalf("CD_path is not nil for %v", step.Files)
}
if len(step.filesAdded) != expected {
t.Fatalf("expected %d, found %d for %v", expected, len(step.filesAdded), step.Files)
}
}

7
go.sum
View File

@ -159,6 +159,8 @@ github.com/digitalocean/godo v1.11.1 h1:OsTh37YFKk+g6DnAOrkXJ9oDArTkRx5UTkBJ2EWA
github.com/digitalocean/godo v1.11.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU= github.com/digitalocean/godo v1.11.1/go.mod h1:h6faOIcZ8lWIwNQ+DN7b3CgX4Kwby5T+nbpNqkUIozU=
github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/diskfs/go-diskfs v1.1.1 h1:rMjLpaydtXGVZb7mdkRGK1+//30i76nKAit89zUzeaI=
github.com/diskfs/go-diskfs v1.1.1/go.mod h1:afUPxxu+x1snp4aCY2bKR0CoZ/YFJewV3X2UEr2nPZE=
github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/docker v0.0.0-20180422163414-57142e89befe h1:VW8TnWi0CZgg7oCv0wH6evNwkzcJg/emnw4HrVIWws4= github.com/docker/docker v0.0.0-20180422163414-57142e89befe h1:VW8TnWi0CZgg7oCv0wH6evNwkzcJg/emnw4HrVIWws4=
@ -362,8 +364,6 @@ github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.3.0 h1:iRly8YaMwTBAKhn1Ybk7VSdzbnopghktCD031P8ggUE=
github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8=
github.com/hashicorp/hcl/v2 v2.6.0 h1:3krZOfGY6SziUXa6H9PJU6TyohHn7I+ARYnhbeNBz+o= github.com/hashicorp/hcl/v2 v2.6.0 h1:3krZOfGY6SziUXa6H9PJU6TyohHn7I+ARYnhbeNBz+o=
github.com/hashicorp/hcl/v2 v2.6.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= github.com/hashicorp/hcl/v2 v2.6.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
@ -761,6 +761,7 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -881,6 +882,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.27 h1:kJdccidYzt3CaHD1crCFTS1hxyhSi059NhOFUf03YFo= gopkg.in/cheggaaa/pb.v1 v1.0.27 h1:kJdccidYzt3CaHD1crCFTS1hxyhSi059NhOFUf03YFo=
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/djherbis/times.v1 v1.2.0 h1:UCvDKl1L/fmBygl2Y7hubXCnY7t4Yj46ZrBFNUipFbM=
gopkg.in/djherbis/times.v1 v1.2.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=

View File

@ -105,6 +105,10 @@ builder.
@include 'common/FloppyConfig-not-required.mdx' @include 'common/FloppyConfig-not-required.mdx'
### CD configuration reference
@include 'common/CDConfig-not-required.mdx'
## Communicator configuration reference ## Communicator configuration reference
### Optional common fields: ### Optional common fields:
@ -136,8 +140,8 @@ for the version of Hyper-V that is running.
Floppy drives are no longer supported by generation 2 machines. This requires Floppy drives are no longer supported by generation 2 machines. This requires
you to take another approach when dealing with preseed or answer files. Two you to take another approach when dealing with preseed or answer files. Two
possible options are using virtual DVD drives or using Packers built in web possible options are using your own virtual DVD drives, or the cd_files option,
server. or using Packer's built in web server.
When dealing with Windows you need to enable UEFI drives for generation 2 When dealing with Windows you need to enable UEFI drives for generation 2
virtual machines. virtual machines.

View File

@ -78,6 +78,7 @@ the items listed here, you will want to look at the general configuration
references for [ISO](#iso-configuration), references for [ISO](#iso-configuration),
[HTTP](#http-directory-configuration), [HTTP](#http-directory-configuration),
[Floppy](#floppy-configuration), [Floppy](#floppy-configuration),
[CD](#cd-configuration),
[Boot](#boot-configuration), [Boot](#boot-configuration),
[Driver](#driver-configuration), [Driver](#driver-configuration),
[Hardware](#hardware-configuration), [Hardware](#hardware-configuration),
@ -129,6 +130,9 @@ necessary for this build to succeed and can be found further down the page.
@include 'common/FloppyConfig-not-required.mdx' @include 'common/FloppyConfig-not-required.mdx'
### CD configuration
@include 'common/CDConfig-not-required.mdx'
### Shutdown configuration ### Shutdown configuration
#### Optional: #### Optional:

View File

@ -0,0 +1,46 @@
<!-- Code generated from the comments of the CDConfig struct in common/extra_iso_config.go; DO NOT EDIT MANUALLY -->
- `cd_files` ([]string) - A list of files to place onto a CD that is attached when the VM is
booted. This can include either files or directories; any directories
will be copied onto the CD recursively, preserving directory structure
hierarchy. Symlinks will have the link's target copied into the directory
tree on the CD where the symlink was. File globbing is allowed.
Usage example (JSON):
```json
"cd_files": ["./somedirectory/meta-data", "./somedirectory/user-data"],
"cd_label": "cidata",
```
Usage example (HCL):
```hcl
cd_files = ["./somedirectory/meta-data", "./somedirectory/user-data"]
cd_label = "cidata"
```
The above will create a CD with two files, user-data and meta-data in the
CD root. This specific example is how you would create a CD that can be
used for an Ubuntu 20.04 autoinstall.
Since globbing is also supported,
```hcl
cd_files = ["./somedirectory/*"]
cd_label = "cidata"
```
Would also be an acceptable way to define the above cd. The difference
between providing the directory with or without the glob is whether the
directory itself or its contents will be at the CD root.
Use of this option assums that you have a command line tool isntalled
that can handle the iso creation. If you are running Packer from an OSX host,
the required tool is is hdiutil which comes preinstalled.
On linux hosts, you need to have mkisofs.
On Windows, you must have oscdimg.exe. oscdimg.exe is part of the
Windows ADK tooks, downloadable from
https://docs.microsoft.com/en-us/windows-hardware/get-started/adk-install#winADK
- `cd_label` (string) - CD Label

View File

@ -0,0 +1,11 @@
<!-- Code generated from the comments of the CDConfig struct in common/extra_iso_config.go; DO NOT EDIT MANUALLY -->
An iso (CD) containing custom files can be made available for your build.
By default, no extra CD will be attached. All files listed in this setting
get placed into the root directory of the CD and the CD is attached as the
second CD device.
This config exists to work around modern operating systems that have no
way to mount floppy disks, which was our previous go-to for adding files at
boot time.