github.com/mitchellh/packer@v1.3.2/builder/virtualbox/common/step_download_guest_additions.go (about)

     1  package common
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"log"
    10  	"os"
    11  	"strings"
    12  
    13  	"github.com/hashicorp/packer/common"
    14  	"github.com/hashicorp/packer/helper/multistep"
    15  	"github.com/hashicorp/packer/packer"
    16  	"github.com/hashicorp/packer/template/interpolate"
    17  )
    18  
    19  var additionsVersionMap = map[string]string{
    20  	"4.2.1":  "4.2.0",
    21  	"4.1.23": "4.1.22",
    22  }
    23  
    24  type guestAdditionsUrlTemplate struct {
    25  	Version string
    26  }
    27  
    28  // This step uploads a file containing the VirtualBox version, which
    29  // can be useful for various provisioning reasons.
    30  //
    31  // Produces:
    32  //   guest_additions_path string - Path to the guest additions.
    33  type StepDownloadGuestAdditions struct {
    34  	GuestAdditionsMode   string
    35  	GuestAdditionsURL    string
    36  	GuestAdditionsSHA256 string
    37  	Ctx                  interpolate.Context
    38  }
    39  
    40  func (s *StepDownloadGuestAdditions) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
    41  	var action multistep.StepAction
    42  	driver := state.Get("driver").(Driver)
    43  	ui := state.Get("ui").(packer.Ui)
    44  
    45  	// If we've disabled guest additions, don't download
    46  	if s.GuestAdditionsMode == GuestAdditionsModeDisable {
    47  		log.Println("Not downloading guest additions since it is disabled.")
    48  		return multistep.ActionContinue
    49  	}
    50  
    51  	// Get VBox version
    52  	version, err := driver.Version()
    53  	if err != nil {
    54  		state.Put("error", fmt.Errorf("Error reading version for guest additions download: %s", err))
    55  		return multistep.ActionHalt
    56  	}
    57  
    58  	if newVersion, ok := additionsVersionMap[version]; ok {
    59  		log.Printf("Rewriting guest additions version: %s to %s", version, newVersion)
    60  		version = newVersion
    61  	}
    62  
    63  	additionsName := fmt.Sprintf("VBoxGuestAdditions_%s.iso", version)
    64  
    65  	// Use provided version or get it from virtualbox.org
    66  	var checksum string
    67  
    68  	checksumType := "sha256"
    69  
    70  	// Grab the guest_additions_url as specified by the user.
    71  	url := s.GuestAdditionsURL
    72  
    73  	// Initialize the template context so we can interpolate some variables..
    74  	s.Ctx.Data = &guestAdditionsUrlTemplate{
    75  		Version: version,
    76  	}
    77  
    78  	// Interpolate any user-variables specified within the guest_additions_url
    79  	url, err = interpolate.Render(s.GuestAdditionsURL, &s.Ctx)
    80  	if err != nil {
    81  		err := fmt.Errorf("Error preparing guest additions url: %s", err)
    82  		state.Put("error", err)
    83  		ui.Error(err.Error())
    84  		return multistep.ActionHalt
    85  	}
    86  
    87  	// If this resulted in an empty url, then ask the driver about it.
    88  	if url == "" {
    89  		log.Printf("guest_additions_url is blank; querying driver for iso.")
    90  		url, err = driver.Iso()
    91  
    92  		if err == nil {
    93  			checksumType = "none"
    94  		} else {
    95  			ui.Error(err.Error())
    96  			url = fmt.Sprintf(
    97  				"https://download.virtualbox.org/virtualbox/%s/%s",
    98  				version,
    99  				additionsName)
   100  		}
   101  	}
   102  
   103  	// The driver couldn't even figure it out, so fail hard.
   104  	if url == "" {
   105  		err := fmt.Errorf("Couldn't detect guest additions URL.\n" +
   106  			"Please specify `guest_additions_url` manually.")
   107  		state.Put("error", err)
   108  		ui.Error(err.Error())
   109  		return multistep.ActionHalt
   110  	}
   111  
   112  	// Figure out a default checksum here
   113  	if checksumType != "none" {
   114  		if s.GuestAdditionsSHA256 != "" {
   115  			checksum = s.GuestAdditionsSHA256
   116  		} else {
   117  			checksum, action = s.downloadAdditionsSHA256(ctx, state, version, additionsName)
   118  			if action != multistep.ActionContinue {
   119  				return action
   120  			}
   121  		}
   122  	}
   123  
   124  	// Convert the file/url to an actual URL for step_download to process.
   125  	url, err = common.ValidatedURL(url)
   126  	if err != nil {
   127  		err := fmt.Errorf("Error preparing guest additions url: %s", err)
   128  		state.Put("error", err)
   129  		ui.Error(err.Error())
   130  		return multistep.ActionHalt
   131  	}
   132  
   133  	log.Printf("Guest additions URL: %s", url)
   134  
   135  	// We're good, so let's go ahead and download this thing..
   136  	downStep := &common.StepDownload{
   137  		Checksum:     checksum,
   138  		ChecksumType: checksumType,
   139  		Description:  "Guest additions",
   140  		ResultKey:    "guest_additions_path",
   141  		Url:          []string{url},
   142  	}
   143  
   144  	return downStep.Run(ctx, state)
   145  }
   146  
   147  func (s *StepDownloadGuestAdditions) Cleanup(state multistep.StateBag) {}
   148  
   149  func (s *StepDownloadGuestAdditions) downloadAdditionsSHA256(ctx context.Context, state multistep.StateBag, additionsVersion string, additionsName string) (string, multistep.StepAction) {
   150  	// First things first, we get the list of checksums for the files available
   151  	// for this version.
   152  	checksumsUrl := fmt.Sprintf(
   153  		"https://download.virtualbox.org/virtualbox/%s/SHA256SUMS",
   154  		additionsVersion)
   155  
   156  	checksumsFile, err := ioutil.TempFile("", "packer")
   157  	if err != nil {
   158  		state.Put("error", fmt.Errorf(
   159  			"Failed creating temporary file to store guest addition checksums: %s",
   160  			err))
   161  		return "", multistep.ActionHalt
   162  	}
   163  	defer os.Remove(checksumsFile.Name())
   164  	checksumsFile.Close()
   165  
   166  	downStep := &common.StepDownload{
   167  		Description: "Guest additions checksums",
   168  		ResultKey:   "guest_additions_checksums_path",
   169  		TargetPath:  checksumsFile.Name(),
   170  		Url:         []string{checksumsUrl},
   171  	}
   172  
   173  	action := downStep.Run(ctx, state)
   174  	if action == multistep.ActionHalt {
   175  		return "", action
   176  	}
   177  
   178  	// Next, we find the checksum for the file we're looking to download.
   179  	// It is an error if the checksum cannot be found.
   180  	checksumsF, err := os.Open(state.Get("guest_additions_checksums_path").(string))
   181  	if err != nil {
   182  		state.Put("error", fmt.Errorf("Error opening guest addition checksums: %s", err))
   183  		return "", multistep.ActionHalt
   184  	}
   185  	defer checksumsF.Close()
   186  
   187  	// We copy the contents of the file into memory. In general this file
   188  	// is quite small so that is okay. In the future, we probably want to
   189  	// use bufio and iterate line by line.
   190  	var contents bytes.Buffer
   191  	io.Copy(&contents, checksumsF)
   192  
   193  	checksum := ""
   194  	for _, line := range strings.Split(contents.String(), "\n") {
   195  		parts := strings.Fields(line)
   196  		log.Printf("Checksum file parts: %#v", parts)
   197  		if len(parts) != 2 {
   198  			// Bogus line
   199  			continue
   200  		}
   201  
   202  		if strings.HasSuffix(parts[1], additionsName) {
   203  			checksum = parts[0]
   204  			log.Printf("Guest additions checksum: %s", checksum)
   205  			break
   206  		}
   207  	}
   208  
   209  	if checksum == "" {
   210  		state.Put("error", fmt.Errorf(
   211  			"The checksum for the file '%s' could not be found.", additionsName))
   212  		return "", multistep.ActionHalt
   213  	}
   214  
   215  	return checksum, multistep.ActionContinue
   216  
   217  }