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 }