github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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-unstable/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  	if !bytes.Equal(fp.Bytes(), c.Fingerprint.Bytes()) {
    39  		return errors.Errorf("resource fingerprint does not match expected (%q != %q)", fp, c.Fingerprint)
    40  	}
    41  	return nil
    42  }
    43  
    44  // ContentSource represents the functionality of OpenedResource,
    45  // relative to Content.
    46  type ContentSource interface {
    47  	// Content returns the content for the opened resource.
    48  	Content() Content
    49  
    50  	// Info returns the info for the opened resource.
    51  	Info() resource.Resource
    52  }
    53  
    54  // TODO(ericsnow) Need a lockfile around create/write?
    55  
    56  // WriteContent writes the resource file to the target provided
    57  // by the deps.
    58  func WriteContent(target io.Writer, content Content, deps WriteContentDeps) error {
    59  	checker := deps.NewChecker(content)
    60  	source := checker.WrapReader(content.Data)
    61  
    62  	if err := deps.Copy(target, source); err != nil {
    63  		return errors.Annotate(err, "could not write resource to file")
    64  	}
    65  
    66  	if err := checker.Verify(); err != nil {
    67  		return errors.Trace(err)
    68  	}
    69  
    70  	return nil
    71  }
    72  
    73  // WriteContentDeps exposes the external functionality needed by WriteContent.
    74  type WriteContentDeps interface {
    75  	//NewChecker provides a content checker for the given content.
    76  	NewChecker(Content) ContentChecker
    77  
    78  	// Copy copies the data from the reader into the writer.
    79  	Copy(io.Writer, io.Reader) error
    80  }
    81  
    82  // ContentChecker exposes functionality for verifying the data read from a reader.
    83  type ContentChecker interface {
    84  	// WrapReader wraps the provided reader in another reader
    85  	// that tracks the read data.
    86  	WrapReader(io.Reader) io.Reader
    87  
    88  	// Verify fails if the tracked data does not match
    89  	// the expected data.
    90  	Verify() error
    91  }
    92  
    93  // Checker provides the functionality for verifying that read data
    94  // is correct.
    95  type Checker struct {
    96  	// Content holds the expected content values.
    97  	Content Content
    98  
    99  	// SizeTracker tracks the number of bytes read.
   100  	SizeTracker SizeTracker
   101  
   102  	// ChecksumWriter tracks the checksum of the read bytes.
   103  	ChecksumWriter ChecksumWriter
   104  }
   105  
   106  // NewContentChecker returns a Checker for the provided data.
   107  func NewContentChecker(content Content, sizeTracker SizeTracker, checksumWriter ChecksumWriter) *Checker {
   108  	return &Checker{
   109  		Content:        content,
   110  		SizeTracker:    sizeTracker,
   111  		ChecksumWriter: checksumWriter,
   112  	}
   113  }
   114  
   115  // WrapReader implements ContentChecker.
   116  func (c Checker) WrapReader(reader io.Reader) io.Reader {
   117  	hashingReader := io.TeeReader(reader, c.ChecksumWriter)
   118  	return io.TeeReader(hashingReader, c.SizeTracker)
   119  }
   120  
   121  // Verify implements ContentChecker.
   122  func (c Checker) Verify() error {
   123  	size := c.SizeTracker.Size()
   124  	fp := c.ChecksumWriter.Fingerprint()
   125  	if err := c.Content.Verify(size, fp); err != nil {
   126  		return errors.Trace(err)
   127  	}
   128  	return nil
   129  }
   130  
   131  // NopChecker is a ContentChecker that accepts all data.
   132  type NopChecker struct{}
   133  
   134  // WrapReader implements ContentChecker.
   135  func (NopChecker) WrapReader(reader io.Reader) io.Reader {
   136  	return reader
   137  }
   138  
   139  // Verify implements ContentChecker.
   140  func (NopChecker) Verify() error {
   141  	return nil
   142  }
   143  
   144  // SizeTracker tracks the number of bytes written.
   145  type SizeTracker interface {
   146  	io.Writer
   147  
   148  	// Size returns the number of bytes written.
   149  	Size() int64
   150  }
   151  
   152  // ChecksumWriter tracks the checksum of all written bytes.
   153  type ChecksumWriter interface {
   154  	io.Writer
   155  
   156  	// Fingerprint is the fingerprint for the tracked checksum.
   157  	Fingerprint() charmresource.Fingerprint
   158  }