github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/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/coreos/rkt/rkt/config"
    25  	"github.com/coreos/rkt/store"
    26  	"github.com/hashicorp/errwrap"
    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                     *store.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  	log.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 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  	ensureLogger(o.Debug)
    90  	aciFile, err := getTmpROC(o.S, u.String())
    91  	if err != nil {
    92  		return nil, nil, err
    93  	}
    94  	defer func() { maybeClose(aciFile) }()
    95  
    96  	session := o.getSession(u, aciFile.File, "ACI", etag)
    97  	dl := o.getDownloader(session)
    98  	if err := dl.Download(u, aciFile.File); err != nil {
    99  		return nil, nil, errwrap.Wrap(errors.New("error downloading ACI"), err)
   100  	}
   101  	if session.Cd.UseCached {
   102  		return nil, session.Cd, nil
   103  	}
   104  	retAciFile := aciFile
   105  	aciFile = nil
   106  	return retAciFile, session.Cd, nil
   107  }
   108  
   109  // GetAscRemoteFetcher provides a remoteAscFetcher for asc.
   110  func (o *httpOps) GetAscRemoteFetcher() *remoteAscFetcher {
   111  	ensureLogger(o.Debug)
   112  	f := func(u *url.URL, file *os.File) error {
   113  		switch u.Scheme {
   114  		case "http", "https":
   115  		default:
   116  			return fmt.Errorf("invalid signature location: expected %q scheme, got %q", "http(s)", u.Scheme)
   117  		}
   118  		session := o.getSession(u, file, "signature", "")
   119  		dl := o.getDownloader(session)
   120  		err := dl.Download(u, file)
   121  		if err != nil {
   122  			return err
   123  		}
   124  		if session.Cd.UseCached {
   125  			return fmt.Errorf("unexpected cache reuse request for signature %q", u.String())
   126  		}
   127  		return nil
   128  	}
   129  	return &remoteAscFetcher{
   130  		F: f,
   131  		S: o.S,
   132  	}
   133  }
   134  
   135  func (o *httpOps) getSession(u *url.URL, file *os.File, label, etag string) *resumableSession {
   136  	eTagFilePath := fmt.Sprintf("%s.etag", file.Name())
   137  	return &resumableSession{
   138  		InsecureSkipTLSVerify: o.InsecureSkipTLSVerify,
   139  		Headers:               o.getHeaders(u, etag),
   140  		File:                  file,
   141  		ETagFilePath:          eTagFilePath,
   142  		Label:                 label,
   143  	}
   144  }
   145  
   146  func (o *httpOps) getDownloader(session downloadSession) *downloader {
   147  	return &downloader{
   148  		Session: session,
   149  	}
   150  }
   151  
   152  func (o *httpOps) getHeaders(u *url.URL, etag string) http.Header {
   153  	options := o.getHeadersForURL(u)
   154  	if etag != "" {
   155  		options.Add("If-None-Match", etag)
   156  	}
   157  	return options
   158  }
   159  
   160  func (o *httpOps) getHeadersForURL(u *url.URL) http.Header {
   161  	// Send credentials only over secure channel
   162  	// TODO(krnowak): This could be controlled with another
   163  	// insecure flag.
   164  	if u.Scheme == "https" {
   165  		if hostOpts, ok := o.Headers[u.Host]; ok {
   166  			return hostOpts.Header()
   167  		}
   168  	}
   169  
   170  	return make(http.Header)
   171  }