package iocage import ( "context" "errors" "fmt" "os" "path/filepath" "strings" "time" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" ) const BuilderID = "packer.iocage" // Builder implements packer.Builder and builds the actual VirtualBox // images. type Builder struct { config Config runner multistep.Runner } func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &b.config.ctx, InterpolateFilter: &interpolate.RenderFilter{ Exclude: []string{ "boot_command", }, }, }, raws...) if err != nil { return nil, nil, err } // Accumulate any errors and warnings var errs *packer.MultiError warnings := make([]string, 0) if b.config.OutputDir == "" { b.config.OutputDir = fmt.Sprintf("output-%s", b.config.PackerBuildName) } if b.config.Comm.SSHTimeout == 0 { b.config.Comm.SSHTimeout = 10 * time.Minute } if b.config.Comm.Type != "ssh" { errs = packer.MultiErrorAppend(errs, fmt.Errorf(`The Vagrant builder currently only supports the ssh communicator"`)) } // The box isn't a namespace like you'd pull from vagrant cloud if b.config.BoxName == "" { b.config.BoxName = fmt.Sprintf("packer_%s", b.config.PackerBuildName) } if b.config.SourceBox == "" { if b.config.GlobalID == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("source_path is required unless you have set global_id")) } } else { if b.config.GlobalID != "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("You may either set global_id or source_path but not both")) } if strings.HasSuffix(b.config.SourceBox, ".box") { if _, err := os.Stat(b.config.SourceBox); err != nil { packer.MultiErrorAppend(errs, fmt.Errorf("Source box '%s' needs to exist at time of config validation! %v", b.config.SourceBox, err)) } } } if b.config.OutputVagrantfile != "" { b.config.OutputVagrantfile, err = filepath.Abs(b.config.OutputVagrantfile) if err != nil { packer.MultiErrorAppend(errs, fmt.Errorf("unable to determine absolute path for output vagrantfile: %s", err)) } } if b.config.TeardownMethod == "" { // If we're using a box that's already opened on the system, don't // automatically destroy it. If we open the box ourselves, then go ahead // and kill it by default. if b.config.GlobalID != "" { b.config.TeardownMethod = "halt" } else { b.config.TeardownMethod = "destroy" } } else { matches := false for _, name := range []string{"halt", "suspend", "destroy"} { if strings.ToLower(b.config.TeardownMethod) == name { matches = true } } if !matches { errs = packer.MultiErrorAppend(errs, fmt.Errorf(`TeardownMethod must be "halt", "suspend", or "destroy"`)) } } if errs != nil && len(errs.Errors) > 0 { return nil, warnings, errs } return nil, warnings, nil } // Run executes a Packer build and returns a packer.Artifact representing // a VirtualBox appliance. func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { // Create the driver that we'll use to communicate with VirtualBox VagrantCWD, err := filepath.Abs(b.config.OutputDir) if err != nil { return nil, err } driver, err := NewDriver(VagrantCWD) if err != nil { return nil, fmt.Errorf("Failed creating VirtualBox driver: %s", err) } // Set up the state. state := new(multistep.BasicStateBag) state.Put("config", &b.config) state.Put("debug", b.config.PackerDebug) state.Put("driver", driver) state.Put("hook", hook) state.Put("ui", ui) // Build the steps. steps := []multistep.Step{} // Download if source box isn't from vagrant cloud. if strings.HasSuffix(b.config.SourceBox, ".box") { steps = append(steps, &common.StepDownload{ Checksum: b.config.Checksum, ChecksumType: b.config.ChecksumType, Description: "Box", Extension: "box", ResultKey: "box_path", Url: []string{b.config.SourceBox}, }) } steps = append(steps, &common.StepOutputDir{ Force: b.config.PackerForce, Path: b.config.OutputDir, }, &StepCreateVagrantfile{ Template: b.config.Template, SyncedFolder: b.config.SyncedFolder, SourceBox: b.config.SourceBox, BoxName: b.config.BoxName, OutputDir: b.config.OutputDir, GlobalID: b.config.GlobalID, InsertKey: b.config.InsertKey, }, &StepAddBox{ BoxVersion: b.config.BoxVersion, CACert: b.config.AddCACert, CAPath: b.config.AddCAPath, DownloadCert: b.config.AddCert, Clean: b.config.AddClean, Force: b.config.AddForce, Insecure: b.config.AddInsecure, Provider: b.config.Provider, SourceBox: b.config.SourceBox, BoxName: b.config.BoxName, GlobalID: b.config.GlobalID, SkipAdd: b.config.SkipAdd, }, &StepUp{ TeardownMethod: b.config.TeardownMethod, Provider: b.config.Provider, GlobalID: b.config.GlobalID, }, &StepSSHConfig{ b.config.GlobalID, }, &communicator.StepConnect{ Config: &b.config.Comm, Host: CommHost(), SSHConfig: b.config.Comm.SSHConfigFunc(), }, &StepSetupJail{ }, new(common.StepProvision), &StepPackage{ SkipPackage: b.config.SkipPackage, Include: b.config.PackageInclude, Vagrantfile: b.config.OutputVagrantfile, GlobalID: b.config.GlobalID, }) // Run the steps. b.runner = common.NewRunnerWithPauseFn(steps, b.config.PackerConfig, ui, state) b.runner.Run(ctx, state) // Report any errors. if rawErr, ok := state.GetOk("error"); ok { return nil, rawErr.(error) } // If we were interrupted or cancelled, then just exit. if _, ok := state.GetOk(multistep.StateCancelled); ok { return nil, errors.New("Build was cancelled.") } if _, ok := state.GetOk(multistep.StateHalted); ok { return nil, errors.New("Build was halted.") } return NewArtifact(b.config.Provider, b.config.OutputDir), nil } func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() } // Cancel.