github.com/cloudfoundry/libcfbuildpack@v1.91.23/layers/download_layer.go (about) 1 /* 2 * Copyright 2018-2020 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * https://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package layers 18 19 import ( 20 "context" 21 "crypto/sha256" 22 "encoding/hex" 23 "fmt" 24 "io" 25 "net/http" 26 "net/url" 27 "os" 28 "path/filepath" 29 "strings" 30 31 "github.com/cloudfoundry/libcfbuildpack/buildpack" 32 "github.com/cloudfoundry/libcfbuildpack/helper" 33 "github.com/cloudfoundry/libcfbuildpack/logger" 34 "github.com/fatih/color" 35 "golang.org/x/oauth2" 36 "golang.org/x/oauth2/google" 37 ) 38 39 // DownloadLayer is an extension to Layer that is unique to a dependency download. 40 type DownloadLayer struct { 41 Layer 42 43 cacheLayer Layer 44 dependency buildpack.Dependency 45 info buildpack.Info 46 logger logger.Logger 47 } 48 49 // Artifact returns the path to an artifact cached in the layer. If the artifact has already been downloaded, the cache 50 // will be validated and used directly. If the artifact is out of date, the layer is left untouched and the contributor 51 // is responsible for cleaning the layer if necessary. 52 func (l DownloadLayer) Artifact() (string, error) { 53 l.Touch() 54 55 matches, err := l.cacheLayer.MetadataMatches(l.dependency) 56 if err != nil { 57 return "", err 58 } 59 60 artifact := filepath.Join(l.cacheLayer.Root, filepath.Base(l.dependency.URI)) 61 if matches { 62 l.logger.Body("%s cached download from buildpack", color.GreenString("Reusing")) 63 return artifact, nil 64 } 65 66 matches, err = l.MetadataMatches(l.dependency) 67 if err != nil { 68 return "", err 69 } 70 71 artifact = filepath.Join(l.Root, filepath.Base(l.dependency.URI)) 72 if matches { 73 l.logger.Body("%s cached download from previous build", color.GreenString("Reusing")) 74 return artifact, nil 75 } 76 77 if err := os.RemoveAll(l.Root); err != nil { 78 return "", err 79 } 80 81 l.logger.Body("%s from %s", color.YellowString("Downloading"), strings.ReplaceAll(l.dependency.URI, "%", "%%")) 82 if err := l.download(artifact); err != nil { 83 return "", err 84 } 85 86 l.logger.Body("Verifying checksum") 87 if err := l.verify(artifact); err != nil { 88 return "", err 89 } 90 91 if err := l.WriteMetadata(l.dependency, Cache); err != nil { 92 return "", err 93 } 94 95 return artifact, nil 96 } 97 98 func (l DownloadLayer) client(uri string) (http.Client, error) { 99 t := &http.Transport{Proxy: http.ProxyFromEnvironment} 100 t.RegisterProtocol("file", http.NewFileTransport(http.Dir("/"))) 101 102 u, err := url.ParseRequestURI(uri) 103 if err != nil { 104 return http.Client{}, err 105 } 106 107 g, ok := os.LookupEnv("GOOGLE_APPLICATION_CREDENTIALS") 108 if !ok || u.Host != "storage.googleapis.com" { 109 l.logger.Debug("Using standard HTTP Client") 110 return http.Client{Transport: t}, nil 111 } 112 113 l.logger.Debug("Using GCP HTTP Client") 114 115 c, err := google.CredentialsFromJSON(context.Background(), []byte(g), "https://www.googleapis.com/auth/cloud-platform") 116 if err != nil { 117 return http.Client{}, err 118 } 119 120 return http.Client{ 121 Transport: &oauth2.Transport{ 122 Base: t, 123 Source: c.TokenSource, 124 }, 125 }, nil 126 } 127 128 func (l DownloadLayer) download(file string) error { 129 req, err := http.NewRequest("GET", l.dependency.URI, nil) 130 if err != nil { 131 return err 132 } 133 134 req.Header.Set("User-Agent", fmt.Sprintf("%s/%s", l.info.ID, l.info.Version)) 135 136 client, err := l.client(l.dependency.URI) 137 if err != nil { 138 return err 139 } 140 141 resp, err := client.Do(req) 142 if err != nil { 143 return err 144 } 145 defer resp.Body.Close() 146 147 if resp.StatusCode < 200 || resp.StatusCode > 299 { 148 return fmt.Errorf("could not download: %d", resp.StatusCode) 149 } 150 151 return helper.WriteFileFromReader(file, 0644, resp.Body) 152 } 153 154 func (l DownloadLayer) verify(file string) error { 155 s := sha256.New() 156 157 f, err := os.Open(file) 158 if err != nil { 159 return err 160 } 161 defer f.Close() 162 163 _, err = io.Copy(s, f) 164 if err != nil { 165 return err 166 } 167 168 actualSha256 := hex.EncodeToString(s.Sum(nil)) 169 170 if actualSha256 != l.dependency.SHA256 { 171 return fmt.Errorf("dependency sha256 mismatch: expected sha256 %s, actual sha256 %s", 172 l.dependency.SHA256, actualSha256) 173 } 174 return nil 175 }