github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/resource/context/internal/content.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package internal
     5  
     6  // TODO(ericsnow) Move this file elsewhere?
     7  //  (e.g. top-level resource pkg, charm/resource)
     8  
     9  import (
    10  	"bytes"
    11  	"io"
    12  
    13  	"github.com/juju/errors"
    14  	charmresource "gopkg.in/juju/charm.v6/resource"
    15  
    16  	"github.com/juju/juju/resource"
    17  )
    18  
    19  // Content holds a reader for the content of a resource along
    20  // with details about that content.
    21  type Content struct {
    22  	// Data holds the resouce content, ready to be read (once).
    23  	Data io.Reader
    24  
    25  	// Size is the byte count of the data.
    26  	Size int64
    27  
    28  	// Fingerprint holds the checksum of the data.
    29  	Fingerprint charmresource.Fingerprint
    30  }
    31  
    32  // Verify ensures that the actual resource content details match
    33  // the expected ones.
    34  func (c Content) Verify(size int64, fp charmresource.Fingerprint) error {
    35  	if size != c.Size {
    36  		return errors.Errorf("resource size does not match expected (%d != %d)", size, c.Size)
    37  	}
    38  	// Only verify a finger print if it's set (i.e not for docker image details).
    39  	if c.Fingerprint.IsZero() {
    40  		return nil
    41  	}
    42  	if !bytes.Equal(fp.Bytes(), c.Fingerprint.Bytes()) {
    43  		return errors.Errorf("resource fingerprint does not match expected (%q != %q)", fp, c.Fingerprint)
    44  	}
    45  	return nil
    46  }
    47  
    48  // ContentSource represents the functionality of OpenedResource,
    49  // relative to Content.
    50  type ContentSource interface {
    51  	// Content returns the content for the opened resource.
    52  	Content() Content
    53  
    54  	// Info returns the info for the opened resource.
    55  	Info() resource.Resource
    56  }
    57  
    58  // TODO(ericsnow) Need a lockfile around create/write?
    59  
    60  // WriteContent writes the resource file to the target provided
    61  // by the deps.
    62  func WriteContent(target io.Writer, content Content, deps WriteContentDeps) error {
    63  	checker := deps.NewChecker(content)
    64  	source := checker.WrapReader(content.Data)
    65  
    66  	if err := deps.Copy(target, source); err != nil {
    67  		return errors.Annotate(err, "could not write resource to file")
    68  	}
    69  
    70  	if err := checker.Verify(); err != nil {
    71  		return errors.Trace(err)
    72  	}
    73  
    74  	return nil
    75  }
    76  
    77  // WriteContentDeps exposes the external functionality needed by WriteContent.
    78  type WriteContentDeps interface {
    79  	//NewChecker provides a content checker for the given content.
    80  	NewChecker(Content) ContentChecker
    81  
    82  	// Copy copies the data from the reader into the writer.
    83  	Copy(io.Writer, io.Reader) error
    84  }
    85  
    86  // ContentChecker exposes functionality for verifying the data read from a reader.
    87  type ContentChecker interface {
    88  	// WrapReader wraps the provided reader in another reader
    89  	// that tracks the read data.
    90  	WrapReader(io.Reader) io.Reader
    91  
    92  	// Verify fails if the tracked data does not match
    93  	// the expected data.
    94  	Verify() error
    95  }
    96  
    97  // Checker provides the functionality for verifying that read data
    98  // is correct.
    99  type Checker struct {
   100  	// Content holds the expected content values.
   101  	Content Content
   102  
   103  	// SizeTracker tracks the number of bytes read.
   104  	SizeTracker SizeTracker
   105  
   106  	// ChecksumWriter tracks the checksum of the read bytes.
   107  	ChecksumWriter ChecksumWriter
   108  }
   109  
   110  // NewContentChecker returns a Checker for the provided data.
   111  func NewContentChecker(content Content, sizeTracker SizeTracker, checksumWriter ChecksumWriter) *Checker {
   112  	return &Checker{
   113  		Content:        content,
   114  		SizeTracker:    sizeTracker,
   115  		ChecksumWriter: checksumWriter,
   116  	}
   117  }
   118  
   119  // WrapReader implements ContentChecker.
   120  func (c Checker) WrapReader(reader io.Reader) io.Reader {
   121  	hashingReader := io.TeeReader(reader, c.ChecksumWriter)
   122  	return io.TeeReader(hashingReader, c.SizeTracker)
   123  }
   124  
   125  // Verify implements ContentChecker.
   126  func (c Checker) Verify() error {
   127  	size := c.SizeTracker.Size()
   128  	fp := c.ChecksumWriter.Fingerprint()
   129  	if err := c.Content.Verify(size, fp); err != nil {
   130  		return errors.Trace(err)
   131  	}
   132  	return nil
   133  }
   134  
   135  // NopChecker is a ContentChecker that accepts all data.
   136  type NopChecker struct{}
   137  
   138  // WrapReader implements ContentChecker.
   139  func (NopChecker) WrapReader(reader io.Reader) io.Reader {
   140  	return reader
   141  }
   142  
   143  // Verify implements ContentChecker.
   144  func (NopChecker) Verify() error {
   145  	return nil
   146  }
   147  
   148  // SizeTracker tracks the number of bytes written.
   149  type SizeTracker interface {
   150  	io.Writer
   151  
   152  	// Size returns the number of bytes written.
   153  	Size() int64
   154  }
   155  
   156  // ChecksumWriter tracks the checksum of all written bytes.
   157  type ChecksumWriter interface {
   158  	io.Writer
   159  
   160  	// Fingerprint is the fingerprint for the tracked checksum.
   161  	Fingerprint() charmresource.Fingerprint
   162  }