github.com/coreos/rocket@v1.30.1-0.20200224141603-171c416fac02/rkt/image/httpops.go (about)

     1  // Copyright 2015 The rkt Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package image
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"net/http"
    21  	"net/url"
    22  	"os"
    23  
    24  	"github.com/hashicorp/errwrap"
    25  	"github.com/rkt/rkt/rkt/config"
    26  	"github.com/rkt/rkt/store/imagestore"
    27  )
    28  
    29  // httpOps is a kind of facade around a downloader and a
    30  // resumableSession. It provides some higher-level functions for
    31  // fetching images and signature keys. It also is a provider of a
    32  // remote fetcher for asc.
    33  type httpOps struct {
    34  	InsecureSkipTLSVerify bool
    35  	S                     *imagestore.Store
    36  	Headers               map[string]config.Headerer
    37  	Debug                 bool
    38  }
    39  
    40  // DownloadSignature takes an asc instance and tries to get the
    41  // signature. If the remote server asked to to defer the download,
    42  // this function will return true and no error and no file.
    43  func (o *httpOps) DownloadSignature(a *asc) (readSeekCloser, bool, error) {
    44  	ensureLogger(o.Debug)
    45  	diag.Printf("downloading signature from %v", a.Location)
    46  	ascFile, err := a.Get()
    47  	if err == nil {
    48  		return ascFile, false, nil
    49  	}
    50  	if _, ok := err.(*statusAcceptedError); ok {
    51  		log.Printf("server requested deferring the signature download")
    52  		return NopReadSeekCloser(nil), true, nil
    53  	}
    54  	return nil, false, errwrap.Wrap(errors.New("error downloading the signature file"), err)
    55  }
    56  
    57  // DownloadSignatureAgain does a similar thing to DownloadSignature,
    58  // but it expects the signature to be actually provided, that is - no
    59  // deferring this time.
    60  func (o *httpOps) DownloadSignatureAgain(a *asc) (readSeekCloser, error) {
    61  	ensureLogger(o.Debug)
    62  	ascFile, retry, err := o.DownloadSignature(a)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	if retry {
    67  		return nil, fmt.Errorf("error downloading the signature file: server asked to defer the download again")
    68  	}
    69  	return ascFile, nil
    70  }
    71  
    72  // DownloadImage download the image, duh. It expects to actually
    73  // receive the file, instead of being asked to use the cached version.
    74  func (o *httpOps) DownloadImage(u *url.URL) (readSeekCloser, *cacheData, error) {
    75  	ensureLogger(o.Debug)
    76  	image, cd, err := o.DownloadImageWithETag(u, "")
    77  	if err != nil {
    78  		return nil, nil, err
    79  	}
    80  	if cd.UseCached {
    81  		return nil, nil, fmt.Errorf("asked to use cached image even if not asked for that")
    82  	}
    83  	return image, cd, nil
    84  }
    85  
    86  // DownloadImageWithETag might download an image or tell you to use
    87  // the cached image. In the latter case the returned file will be nil.
    88  func (o *httpOps) DownloadImageWithETag(u *url.URL, etag string) (readSeekCloser, *cacheData, error) {
    89  	var aciFile *removeOnClose // closed on error
    90  	var errClose error         // error signaling to close aciFile
    91  
    92  	ensureLogger(o.Debug)
    93  	aciFile, err := getTmpROC(o.S, u.String())
    94  	if err != nil {
    95  		return nil, nil, err
    96  	}
    97  
    98  	defer func() {
    99  		if errClose != nil {
   100  			aciFile.Close()
   101  		}
   102  	}()
   103  
   104  	session := o.getSession(u, aciFile.File, "ACI", etag)
   105  	dl := o.getDownloader(session)
   106  	errClose = dl.Download(u, aciFile.File)
   107  	if errClose != nil {
   108  		return nil, nil, errwrap.Wrap(errors.New("error downloading ACI"), errClose)
   109  	}
   110  
   111  	if session.Cd.UseCached {
   112  		aciFile.Close()
   113  		return NopReadSeekCloser(nil), session.Cd, nil
   114  	}
   115  
   116  	return aciFile, session.Cd, nil
   117  }
   118  
   119  // AscRemoteFetcher provides a remoteAscFetcher for asc.
   120  func (o *httpOps) AscRemoteFetcher() *remoteAscFetcher {
   121  	ensureLogger(o.Debug)
   122  	f := func(u *url.URL, file *os.File) error {
   123  		switch u.Scheme {
   124  		case "http", "https":
   125  		default:
   126  			return fmt.Errorf("invalid signature location: expected %q scheme, got %q", "http(s)", u.Scheme)
   127  		}
   128  		session := o.getSession(u, file, "signature", "")
   129  		dl := o.getDownloader(session)
   130  		err := dl.Download(u, file)
   131  		if err != nil {
   132  			return err
   133  		}
   134  		if session.Cd.UseCached {
   135  			return fmt.Errorf("unexpected cache reuse request for signature %q", u.String())
   136  		}
   137  		return nil
   138  	}
   139  	return &remoteAscFetcher{
   140  		F: f,
   141  		S: o.S,
   142  	}
   143  }
   144  
   145  func (o *httpOps) getSession(u *url.URL, file *os.File, label, etag string) *resumableSession {
   146  	eTagFilePath := fmt.Sprintf("%s.etag", file.Name())
   147  	return &resumableSession{
   148  		InsecureSkipTLSVerify: o.InsecureSkipTLSVerify,
   149  		Headers:               o.getHeaders(u, etag),
   150  		Headerers:             o.Headers,
   151  		File:                  file,
   152  		ETagFilePath:          eTagFilePath,
   153  		Label:                 label,
   154  	}
   155  }
   156  
   157  func (o *httpOps) getDownloader(session downloadSession) *downloader {
   158  	return &downloader{
   159  		Session: session,
   160  	}
   161  }
   162  
   163  func (o *httpOps) getHeaders(u *url.URL, etag string) http.Header {
   164  	options := o.getHeadersForURL(u, etag)
   165  	if etag != "" {
   166  		options.Add("If-None-Match", etag)
   167  	}
   168  	return options
   169  }
   170  
   171  func (o *httpOps) getHeadersForURL(u *url.URL, etag string) http.Header {
   172  	return make(http.Header)
   173  }