diff --git a/cmd/dist/edit.go b/cmd/dist/edit.go new file mode 100644 index 000000000..c7f655e20 --- /dev/null +++ b/cmd/dist/edit.go @@ -0,0 +1,113 @@ +package main + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + + contentapi "github.com/containerd/containerd/api/services/content" + contentservice "github.com/containerd/containerd/services/content" + digest "github.com/opencontainers/go-digest" + "github.com/urfave/cli" +) + +var editCommand = cli.Command{ + Name: "edit", + Usage: "edit a blob and return a new digest.", + ArgsUsage: "[flags] ", + Description: `Edit a blob and return a new digest.`, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "validate", + Usage: "validate the result against a format (json, mediatype, etc.)", + }, + }, + Action: func(context *cli.Context) error { + var ( + ctx = background + validate = context.String("validate") + object = context.Args().First() + ) + + if validate != "" { + return errors.New("validating the edit result not supported") + } + + // TODO(stevvooe): Support looking up objects by a reference through + // the image metadata storage. + + dgst, err := digest.Parse(object) + if err != nil { + return err + } + + conn, err := connectGRPC(context) + if err != nil { + return err + } + + provider := contentservice.NewProviderFromClient(contentapi.NewContentClient(conn)) + ingester := contentservice.NewIngesterFromClient(contentapi.NewContentClient(conn)) + + rc, err := provider.Reader(ctx, dgst) + if err != nil { + return err + } + defer rc.Close() + + nrc, err := edit(rc) + if err != nil { + return err + } + + wr, err := ingester.Writer(ctx, "edit-"+object, 0, "") // TODO(stevvooe): Choose a better key? + if err != nil { + return err + } + + if _, err := io.Copy(wr, nrc); err != nil { + return err + } + + if err := wr.Commit(0, wr.Digest()); err != nil { + return err + } + + fmt.Println(wr.Digest()) + return nil + }, +} + +func edit(rd io.Reader) (io.ReadCloser, error) { + tmp, err := ioutil.TempFile("", "edit-") + if err != nil { + return nil, err + } + + if _, err := io.Copy(tmp, rd); err != nil { + tmp.Close() + return nil, err + } + + cmd := exec.Command("sh", "-c", "$EDITOR "+tmp.Name()) + + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = os.Environ() + + if err := cmd.Run(); err != nil { + tmp.Close() + return nil, err + } + + if _, err := tmp.Seek(0, io.SeekStart); err != nil { + tmp.Close() + return nil, err + } + + return tmp, nil +} diff --git a/cmd/dist/main.go b/cmd/dist/main.go index f9acfc859..0d1f946c4 100644 --- a/cmd/dist/main.go +++ b/cmd/dist/main.go @@ -106,6 +106,7 @@ var contentCommand = cli.Command{ ingestCommand, activeCommand, getCommand, + editCommand, deleteCommand, }, } diff --git a/services/content/ingester.go b/services/content/ingester.go index 191626bdf..a4182cc4f 100644 --- a/services/content/ingester.go +++ b/services/content/ingester.go @@ -127,6 +127,9 @@ func (rw *remoteWriter) Write(p []byte) (n int, err error) { } rw.offset += int64(n) + if resp.Digest != "" { + rw.digest = resp.Digest + } return } @@ -149,6 +152,8 @@ func (rw *remoteWriter) Commit(size int64, expected digest.Digest) error { return errors.Errorf("unexpected digest: %v != %v", resp.Digest, expected) } + rw.digest = resp.Digest + rw.offset = resp.Offset return nil } diff --git a/services/content/service.go b/services/content/service.go index 128104fa0..c138184ff 100644 --- a/services/content/service.go +++ b/services/content/service.go @@ -304,9 +304,9 @@ func (s *Service) Write(session api.Content_WriteServer) (err error) { if err := wr.Commit(total, expected); err != nil { return err } - - msg.Digest = wr.Digest() } + + msg.Digest = wr.Digest() case api.WriteActionAbort: return s.store.Abort(ref) }