diff --git a/README.md b/README.md index 05a695e..d00e7e8 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/tape/app/app.go b/tape/app/app.go index 945f10f..c14b58c 100644 --- a/tape/app/app.go +++ b/tape/app/app.go @@ -85,6 +85,13 @@ func Run() int { OutputManifestDirOptions: OutputManifestDirOptions{}, }, }, + { + name: "view", + short: "View an artefact", + options: &TapeViewCommand{ + tape: tape, + }, + }, } for _, c := range commands { diff --git a/tape/app/view.go b/tape/app/view.go new file mode 100644 index 0000000..86c0112 --- /dev/null +++ b/tape/app/view.go @@ -0,0 +1,97 @@ +package app + +import ( + "context" + "fmt" + + "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"` + Attestation rawManifest[oci.Manifest] `json:"attestation"` + } `json:"rawManifests"` +} + +type rawManifest[T oci.Manifest | oci.IndexManifest] struct { + Digest string + Manifest *T +} + +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) + } + _ = outputInfo + + return nil +} + +func (c *TapeViewCommand) CollectInfo(ctx context.Context, client *oci.Client) (*artefactInfo, error) { + outputInfo := &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 + } + + outputInfo.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 + } + + for i := range imageInfo { + info := imageInfo[i] + switch info.MediaType { + case oci.ContentMediaType: + case oci.AttestMediaType: + } + } + for digest := range manifests { + m := rawManifest[oci.Manifest]{ + Digest: digest.String(), + Manifest: manifests[digest], + } + switch m.Manifest.MediaType { + case oci.ContentMediaType: + outputInfo.RawManifests.Content = m + case oci.AttestMediaType: + outputInfo.RawManifests.Attestation = m + } + } + return nil, nil +}