Skip to content

Commit

Permalink
Add tape view command
Browse files Browse the repository at this point in the history
  • Loading branch information
errordeveloper committed Sep 26, 2023
1 parent f98e5c4 commit 0763544
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ Tape has the following commands:
- `tape images` - examine images referenced by a given set of manifests before packaging them
- `tape package` - package an artifcat and push it to a registry
- `tape pull` – downlowad and extract contents and attestations from an existing artifact
- `tape view` – inspect an existing artifact

### Example

Expand Down
5 changes: 3 additions & 2 deletions attest/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,9 @@ func (s Subjects) Export() []toto.Subject {
return subjects
}

func (s Subjects) MarshalJSON() ([]byte, error) { return json.Marshal(s.Export()) }
func (s *Subjects) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, s) }
func (s Subjects) MarshalJSON() ([]byte, error) { return json.Marshal(s.Export()) }

//func (s *Subjects) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, s) }

func MakePathCheckSummaryCollection(entries ...PathChecker) (*PathCheckSummaryCollection, error) {
numEntries := len(entries)
Expand Down
7 changes: 7 additions & 0 deletions tape/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ func Run() int {
OutputManifestDirOptions: OutputManifestDirOptions{},
},
},
{
name: "view",
short: "View an artefact",
options: &TapeViewCommand{
tape: tape,
},
},
}

for _, c := range commands {
Expand Down
154 changes: 154 additions & 0 deletions tape/app/view.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package app

import (
"bufio"
"bytes"
"compress/gzip"
"context"
"encoding/json"
"fmt"
"os"

toto "github.com/in-toto/in-toto-golang/in_toto"

//attestTypes "github.com/docker/labs-brown-tape/attest/types"

"github.com/docker/labs-brown-tape/oci"
)

type TapeViewCommand struct {
tape *TapeCommand
OutputFormatOptions

Image string `short:"I" long:"image" description:"Name of the image to view" required:"true"`
}

type artefactInfo struct {
RawManifests struct {
Index rawManifest[oci.IndexManifest] `json:"index"`
Content rawManifest[oci.Manifest] `json:"content"`
Attest rawManifest[oci.Manifest] `json:"attest"`
} `json:"rawManifests"`
Attestations []toto.Statement `json:"attestations"`
}

type rawManifest[T oci.Manifest | oci.IndexManifest] struct {
Digest string `json:"digest,omitempty"`
Manifest *T `json:"manifest,omitempty"`
}

func (c *TapeViewCommand) Execute(args []string) error {
ctx := context.WithValue(c.tape.ctx, "command", "view")
if len(args) != 0 {
return fmt.Errorf("unexpected arguments: %v", args)
}

if err := c.tape.Init(); err != nil {
return err
}

client := oci.NewClient(nil)

outputInfo, err := c.CollectInfo(ctx, client)
if err != nil {
return fmt.Errorf("failed to collect info about artifact: %w", err)
}

if err := c.PrintInfo(ctx, outputInfo); err != nil {
return fmt.Errorf("failed to print info about artifact: %w", err)
}

return nil
}

func (c *TapeViewCommand) CollectInfo(ctx context.Context, client *oci.Client) (*artefactInfo, error) {
artefactInfo := &artefactInfo{}

imageIndex, indexManifest, _, err := client.GetIndexOrImage(ctx, c.Image)
if err != nil {
return nil, err
}
if indexManifest == nil {
return nil, fmt.Errorf("no index manifest found for %q", c.Image)
}

imageIndexDigest, err := imageIndex.Digest()
if err != nil {
return nil, err
}

artefactInfo.RawManifests.Index = rawManifest[oci.IndexManifest]{
Digest: imageIndexDigest.String(),
Manifest: indexManifest,
}

imageInfo, manifests, err := client.FetchFromIndexOrImage(ctx, imageIndex, indexManifest, nil)
if err != nil {
return nil, err
}

if len(imageInfo) == 0 {
return nil, fmt.Errorf("no images found in index %q", c.Image)
}

for i := range imageInfo {
info := imageInfo[i]
switch info.MediaType {
case oci.ContentMediaType:
case oci.AttestMediaType:
gr, err := gzip.NewReader(info)
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(gr)
for scanner.Scan() {
statement := toto.Statement{} // attestTypes.GenericStatement[any]{}
if err := json.NewDecoder(bytes.NewBuffer(scanner.Bytes())).Decode(&statement); err != nil {
return nil, err
}
artefactInfo.Attestations = append(artefactInfo.Attestations, statement)
}
if err := scanner.Err(); err != nil {
return nil, err
}
if err := gr.Close(); err != nil {
return nil, err
}
}
}

for digest := range manifests {
m := rawManifest[oci.Manifest]{
Digest: digest.String(),
Manifest: manifests[digest],
}
switch m.Manifest.Config.MediaType {
case oci.ContentMediaType:
artefactInfo.RawManifests.Content = m
case oci.AttestMediaType:
artefactInfo.RawManifests.Attest = m
}
}
return artefactInfo, nil
}

func (c *TapeViewCommand) PrintInfo(ctx context.Context, outputInfo *artefactInfo) error {
stdj := json.NewEncoder(os.Stdout)
switch c.OutputFormat {
case OutputFormatDirectJSON:
stdj.SetIndent("", " ")
if err := stdj.Encode(outputInfo); err != nil {
return fmt.Errorf("failed to marshal output: %w", err)
}
case OutputFormatText, OutputFormatDetailedText:
fmt.Printf("%s\n", c.Image)
fmt.Printf(" Digest: %s\n", outputInfo.RawManifests.Index.Digest)
fmt.Printf(" OCI Manifests:\n")
fmt.Printf(" %s %s %d \n", outputInfo.RawManifests.Content.Digest,
outputInfo.RawManifests.Content.Manifest.Config.MediaType,
outputInfo.RawManifests.Content.Manifest.Config.Size)
fmt.Printf(" %s\n", outputInfo.RawManifests.Attest.Digest)

}
return nil
}

0 comments on commit 0763544

Please sign in to comment.