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