diff --git a/commands/apply.go b/commands/apply.go index 9807480d..174062ac 100644 --- a/commands/apply.go +++ b/commands/apply.go @@ -1,7 +1,6 @@ package commands import ( - "io/ioutil" "log" "github.com/containerd/continuity" @@ -14,14 +13,9 @@ var ApplyCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { root, path := args[0], args[1] - p, err := ioutil.ReadFile(path) + m, err := readManifest(path) if err != nil { - log.Fatalf("error reading manifest: %v", err) - } - - m, err := continuity.Unmarshal(p) - if err != nil { - log.Fatalf("error unmarshaling manifest: %v", err) + log.Fatal(err) } ctx, err := continuity.NewContext(root) diff --git a/commands/build.go b/commands/build.go index 58bd826c..90c176eb 100644 --- a/commands/build.go +++ b/commands/build.go @@ -1,6 +1,7 @@ package commands import ( + "fmt" "log" "os" @@ -13,6 +14,13 @@ var ( format string } + marshalers = map[string]func(*continuity.Manifest) ([]byte, error){ + "pb": continuity.Marshal, + continuity.MediaTypeManifestV0Protobuf: continuity.Marshal, + "json": continuity.MarshalJSON, + continuity.MediaTypeManifestV0JSON: continuity.MarshalJSON, + } + BuildCmd = &cobra.Command{ Use: "build ", Short: "Build a manifest for the provided root", @@ -31,9 +39,15 @@ var ( log.Fatalf("error generating manifest: %v", err) } - p, err := continuity.Marshal(m) + marshaler, ok := marshalers[buildCmdConfig.format] + if !ok { + log.Fatalf("unknown format %s", buildCmdConfig.format) + } + + p, err := marshaler(m) if err != nil { - log.Fatalf("error marshaling manifest: %v", err) + log.Fatalf("error marshalling manifest as %s: %v", + buildCmdConfig.format, err) } if _, err := os.Stdout.Write(p); err != nil { @@ -44,5 +58,7 @@ var ( ) func init() { - BuildCmd.Flags().StringVar(&buildCmdConfig.format, "format", "pb", "specify the output format of the manifest") + BuildCmd.Flags().StringVar(&buildCmdConfig.format, "format", "pb", + fmt.Sprintf("specify the output format of the manifest (\"pb\"|%q|\"json\"|%q)", + continuity.MediaTypeManifestV0Protobuf, continuity.MediaTypeManifestV0JSON)) } diff --git a/commands/commandsutil.go b/commands/commandsutil.go new file mode 100644 index 00000000..b26712c6 --- /dev/null +++ b/commands/commandsutil.go @@ -0,0 +1,22 @@ +package commands + +import ( + "fmt" + "io/ioutil" + "path/filepath" + "strings" + + "github.com/containerd/continuity" +) + +func readManifest(path string) (*continuity.Manifest, error) { + p, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("error reading manifest: %v", err) + } + ext := strings.ToLower(filepath.Ext(path)) + if ext == ".json" { + return continuity.UnmarshalJSON(p) + } + return continuity.Unmarshal(p) +} diff --git a/commands/mount.go b/commands/mount.go index 1ce6a467..d6c903ba 100644 --- a/commands/mount.go +++ b/commands/mount.go @@ -1,7 +1,6 @@ package commands import ( - "io/ioutil" "log" "os" "os/signal" @@ -30,14 +29,9 @@ var MountCmd = &cobra.Command{ manifestName := filepath.Base(manifest) - p, err := ioutil.ReadFile(manifest) + m, err := readManifest(manifest) if err != nil { - log.Fatalf("error reading manifest: %v", err) - } - - m, err := continuity.Unmarshal(p) - if err != nil { - log.Fatalf("error unmarshaling manifest: %v", err) + log.Fatal(err) } driver, err := continuity.NewSystemDriver() diff --git a/commands/verify.go b/commands/verify.go index 38a811d8..0e055f84 100644 --- a/commands/verify.go +++ b/commands/verify.go @@ -1,7 +1,6 @@ package commands import ( - "io/ioutil" "log" "github.com/containerd/continuity" @@ -18,14 +17,9 @@ var VerifyCmd = &cobra.Command{ root, path := args[0], args[1] - p, err := ioutil.ReadFile(path) + m, err := readManifest(path) if err != nil { - log.Fatalf("error reading manifest: %v", err) - } - - m, err := continuity.Unmarshal(p) - if err != nil { - log.Fatalf("error unmarshaling manifest: %v", err) + log.Fatal(err) } ctx, err := continuity.NewContext(root) diff --git a/manifest.go b/manifest.go index 20706f35..6298727e 100644 --- a/manifest.go +++ b/manifest.go @@ -1,6 +1,7 @@ package continuity import ( + "bytes" "fmt" "io" "log" @@ -8,9 +9,21 @@ import ( "sort" pb "github.com/containerd/continuity/proto" + "github.com/golang/protobuf/jsonpb" "github.com/golang/protobuf/proto" ) +const ( + // MediaTypeManifestV0Protobuf is the media type for manifest formatted as protobuf. + // The format is unstable during v0. + MediaTypeManifestV0Protobuf = "application/vnd.continuity.manifest.v0+pb" + // MediaTypeManifestV0JSON is the media type for manifest formatted as JSON. + // JSON is marshalled from protobuf using jsonpb.Marshaler + // ({EnumAsInts = false, EmitDefaults = false, OrigName = false}) + // The format is unstable during v0. + MediaTypeManifestV0JSON = "application/vnd.continuity.manifest.v0+json" +) + // Manifest provides the contents of a manifest. Users of this struct should // not typically modify any fields directly. type Manifest struct { @@ -18,13 +31,7 @@ type Manifest struct { Resources []Resource } -func Unmarshal(p []byte) (*Manifest, error) { - var bm pb.Manifest - - if err := proto.Unmarshal(p, &bm); err != nil { - return nil, err - } - +func manifestFromProto(bm *pb.Manifest) (*Manifest, error) { var m Manifest for _, b := range bm.Resource { r, err := fromProto(b) @@ -34,26 +41,48 @@ func Unmarshal(p []byte) (*Manifest, error) { m.Resources = append(m.Resources, r) } - return &m, nil } -func Marshal(m *Manifest) ([]byte, error) { +func Unmarshal(p []byte) (*Manifest, error) { var bm pb.Manifest - for _, resource := range m.Resources { - bm.Resource = append(bm.Resource, toProto(resource)) + if err := proto.Unmarshal(p, &bm); err != nil { + return nil, err } + return manifestFromProto(&bm) +} - return proto.Marshal(&bm) +func UnmarshalJSON(p []byte) (*Manifest, error) { + var bm pb.Manifest + if err := jsonpb.Unmarshal(bytes.NewReader(p), &bm); err != nil { + return nil, err + } + return manifestFromProto(&bm) } -func MarshalText(w io.Writer, m *Manifest) error { +func manifestToProto(m *Manifest) *pb.Manifest { var bm pb.Manifest for _, resource := range m.Resources { bm.Resource = append(bm.Resource, toProto(resource)) } + return &bm +} - return proto.MarshalText(w, &bm) +func Marshal(m *Manifest) ([]byte, error) { + return proto.Marshal(manifestToProto(m)) +} + +func MarshalText(w io.Writer, m *Manifest) error { + return proto.MarshalText(w, manifestToProto(m)) +} + +func MarshalJSON(m *Manifest) ([]byte, error) { + var b bytes.Buffer + marshaler := &jsonpb.Marshaler{} + if err := marshaler.Marshal(&b, manifestToProto(m)); err != nil { + return nil, err + } + return b.Bytes(), nil } // BuildManifest creates the manifest for the given context