package main import ( "errors" "fmt" "io" "io/ioutil" "os" "os/exec" contentapi "github.com/containerd/containerd/api/services/content" "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 ( validate = context.String("validate") object = context.Args().First() ) ctx, cancel := appContext(context) defer cancel() 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 } content := content.NewStoreFromClient(contentapi.NewContentClient(conn)) rc, err := content.Reader(ctx, dgst) if err != nil { return err } defer rc.Close() nrc, err := edit(rc) if err != nil { return err } defer nrc.Close() wr, err := content.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 onCloser{ReadCloser: tmp, onClose: func() error { return os.RemoveAll(tmp.Name()) }}, nil } type onCloser struct { io.ReadCloser onClose func() error } func (oc onCloser) Close() error { var err error if err1 := oc.ReadCloser.Close(); err1 != nil { err = err1 } if oc.onClose != nil { err1 := oc.onClose() if err == nil { err = err1 } } return err }