github.phpd.cn/hashicorp/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 }