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  }