1 /* 2 Copyright The ORAS Authors. 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 16 package content 17 18 import ( 19 "context" 20 "io" 21 "io/ioutil" 22 23 "github.com/containerd/containerd/content" 24 "github.com/opencontainers/go-digest" 25 ) 26 27 // IoContentWriter writer that wraps an io.Writer, so the results can be streamed to 28 // an open io.Writer. For example, can be used to pull a layer and write it to a file, or device. 29 type IoContentWriter struct { 30 writer io.Writer 31 digester digest.Digester 32 size int64 33 hash *digest.Digest 34 } 35 36 // NewIoContentWriter create a new IoContentWriter. 37 // 38 // By default, it calculates the hash when writing. If the option `skipHash` is true, 39 // it will skip doing the hash. Skipping the hash is intended to be used only 40 // if you are confident about the validity of the data being passed to the writer, 41 // and wish to save on the hashing time. 42 func NewIoContentWriter(writer io.Writer, opts ...WriterOpt) content.Writer { 43 w := writer 44 if w == nil { 45 w = ioutil.Discard 46 } 47 // process opts for default 48 wOpts := DefaultWriterOpts() 49 for _, opt := range opts { 50 if err := opt(&wOpts); err != nil { 51 return nil 52 } 53 } 54 ioc := &IoContentWriter{ 55 writer: w, 56 digester: digest.Canonical.Digester(), 57 // we take the OutputHash, since the InputHash goes to the passthrough writer, 58 // which then passes the processed output to us 59 hash: wOpts.OutputHash, 60 } 61 return NewPassthroughWriter(ioc, func(r io.Reader, w io.Writer, done chan<- error) { 62 // write out the data to the io writer 63 var ( 64 err error 65 ) 66 // we could use io.Copy, but calling it with the default blocksize is identical to 67 // io.CopyBuffer. Otherwise, we would need some way to let the user flag "I want to use 68 // io.Copy", when it should not matter to them 69 b := make([]byte, wOpts.Blocksize, wOpts.Blocksize) 70 _, err = io.CopyBuffer(w, r, b) 71 done <- err 72 }, opts...) 73 } 74 75 func (w *IoContentWriter) Write(p []byte) (n int, err error) { 76 n, err = w.writer.Write(p) 77 if err != nil { 78 return 0, err 79 } 80 w.size += int64(n) 81 if w.hash == nil { 82 w.digester.Hash().Write(p[:n]) 83 } 84 return 85 } 86 87 func (w *IoContentWriter) Close() error { 88 return nil 89 } 90 91 // Digest may return empty digest or panics until committed. 92 func (w *IoContentWriter) Digest() digest.Digest { 93 return w.digester.Digest() 94 } 95 96 // Commit commits the blob (but no roll-back is guaranteed on an error). 97 // size and expected can be zero-value when unknown. 98 // Commit always closes the writer, even on error. 99 // ErrAlreadyExists aborts the writer. 100 func (w *IoContentWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error { 101 return nil 102 } 103 104 // Status returns the current state of write 105 func (w *IoContentWriter) Status() (content.Status, error) { 106 return content.Status{}, nil 107 } 108 109 // Truncate updates the size of the target blob 110 func (w *IoContentWriter) Truncate(size int64) error { 111 return nil 112 } 113