github.com/containerd/Containerd@v1.4.13/content/proxy/content_writer.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package proxy 18 19 import ( 20 "context" 21 "io" 22 23 contentapi "github.com/containerd/containerd/api/services/content/v1" 24 "github.com/containerd/containerd/content" 25 "github.com/containerd/containerd/errdefs" 26 digest "github.com/opencontainers/go-digest" 27 "github.com/pkg/errors" 28 ) 29 30 type remoteWriter struct { 31 ref string 32 client contentapi.Content_WriteClient 33 offset int64 34 digest digest.Digest 35 } 36 37 // send performs a synchronous req-resp cycle on the client. 38 func (rw *remoteWriter) send(req *contentapi.WriteContentRequest) (*contentapi.WriteContentResponse, error) { 39 if err := rw.client.Send(req); err != nil { 40 return nil, err 41 } 42 43 resp, err := rw.client.Recv() 44 45 if err == nil { 46 // try to keep these in sync 47 if resp.Digest != "" { 48 rw.digest = resp.Digest 49 } 50 } 51 52 return resp, err 53 } 54 55 func (rw *remoteWriter) Status() (content.Status, error) { 56 resp, err := rw.send(&contentapi.WriteContentRequest{ 57 Action: contentapi.WriteActionStat, 58 }) 59 if err != nil { 60 return content.Status{}, errors.Wrap(errdefs.FromGRPC(err), "error getting writer status") 61 } 62 63 return content.Status{ 64 Ref: rw.ref, 65 Offset: resp.Offset, 66 Total: resp.Total, 67 StartedAt: resp.StartedAt, 68 UpdatedAt: resp.UpdatedAt, 69 }, nil 70 } 71 72 func (rw *remoteWriter) Digest() digest.Digest { 73 return rw.digest 74 } 75 76 func (rw *remoteWriter) Write(p []byte) (n int, err error) { 77 offset := rw.offset 78 79 resp, err := rw.send(&contentapi.WriteContentRequest{ 80 Action: contentapi.WriteActionWrite, 81 Offset: offset, 82 Data: p, 83 }) 84 if err != nil { 85 return 0, errors.Wrap(errdefs.FromGRPC(err), "failed to send write") 86 } 87 88 n = int(resp.Offset - offset) 89 if n < len(p) { 90 err = io.ErrShortWrite 91 } 92 93 rw.offset += int64(n) 94 if resp.Digest != "" { 95 rw.digest = resp.Digest 96 } 97 return 98 } 99 100 func (rw *remoteWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error { 101 var base content.Info 102 for _, opt := range opts { 103 if err := opt(&base); err != nil { 104 return err 105 } 106 } 107 resp, err := rw.send(&contentapi.WriteContentRequest{ 108 Action: contentapi.WriteActionCommit, 109 Total: size, 110 Offset: rw.offset, 111 Expected: expected, 112 Labels: base.Labels, 113 }) 114 if err != nil { 115 return errors.Wrap(errdefs.FromGRPC(err), "commit failed") 116 } 117 118 if size != 0 && resp.Offset != size { 119 return errors.Errorf("unexpected size: %v != %v", resp.Offset, size) 120 } 121 122 if expected != "" && resp.Digest != expected { 123 return errors.Errorf("unexpected digest: %v != %v", resp.Digest, expected) 124 } 125 126 rw.digest = resp.Digest 127 rw.offset = resp.Offset 128 return nil 129 } 130 131 func (rw *remoteWriter) Truncate(size int64) error { 132 // This truncation won't actually be validated until a write is issued. 133 rw.offset = size 134 return nil 135 } 136 137 func (rw *remoteWriter) Close() error { 138 return rw.client.CloseSend() 139 }