package artifact import ( "fmt" "os" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" "github.com/containers/image/v5/docker/reference" "github.com/containers/podman/v5/cmd/podman/common" "github.com/containers/podman/v5/cmd/podman/registry" "github.com/containers/podman/v5/cmd/podman/validate" "github.com/containers/podman/v5/pkg/domain/entities" "github.com/docker/go-units" "github.com/spf13/cobra" ) var ( listCmd = &cobra.Command{ Use: "ls [options]", Aliases: []string{"list"}, Short: "List OCI artifacts", Long: "List OCI artifacts in local store", RunE: list, Args: validate.NoArgs, ValidArgsFunction: completion.AutocompleteNone, Example: `podman artifact ls`, Annotations: map[string]string{registry.EngineMode: registry.ABIMode}, } listFlag = listFlagType{} ) type listFlagType struct { format string noHeading bool noTrunc bool } type artifactListOutput struct { Digest string Repository string Size string Tag string } var ( defaultArtifactListOutputFormat = "{{range .}}{{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.Size}}\n{{end -}}" ) func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Command: listCmd, Parent: artifactCmd, }) flags := listCmd.Flags() formatFlagName := "format" flags.StringVar(&listFlag.format, formatFlagName, defaultArtifactListOutputFormat, "Format volume output using JSON or a Go template") _ = listCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&artifactListOutput{})) flags.BoolVarP(&listFlag.noHeading, "noheading", "n", false, "Do not print column headings") flags.BoolVar(&listFlag.noTrunc, "no-trunc", false, "Do not truncate output") } func list(cmd *cobra.Command, _ []string) error { reports, err := registry.ImageEngine().ArtifactList(registry.Context(), entities.ArtifactListOptions{}) if err != nil { return err } return outputTemplate(cmd, reports) } func outputTemplate(cmd *cobra.Command, lrs []*entities.ArtifactListReport) error { var err error artifacts := make([]artifactListOutput, 0) for _, lr := range lrs { var ( tag string ) artifactName, err := lr.Artifact.GetName() if err != nil { return err } repo, err := reference.Parse(artifactName) if err != nil { return err } named, ok := repo.(reference.Named) if !ok { return fmt.Errorf("%q is an invalid artifact name", artifactName) } if tagged, ok := named.(reference.Tagged); ok { tag = tagged.Tag() } // Note: Right now we only support things that are single manifests // We should certainly expand this support for things like arch, etc // as we move on artifactDigest, err := lr.Artifact.GetDigest() if err != nil { return err } artifactHash := artifactDigest.Encoded()[0:12] // If the user does not want truncated hashes if listFlag.noTrunc { artifactHash = artifactDigest.Encoded() } artifacts = append(artifacts, artifactListOutput{ Digest: artifactHash, Repository: named.Name(), Size: units.HumanSize(float64(lr.Artifact.TotalSizeBytes())), Tag: tag, }) } headers := report.Headers(artifactListOutput{}, map[string]string{ "REPOSITORY": "REPOSITORY", "Tag": "TAG", "Size": "SIZE", "Digest": "DIGEST", }) rpt := report.New(os.Stdout, cmd.Name()) defer rpt.Flush() switch { case cmd.Flag("format").Changed: rpt, err = rpt.Parse(report.OriginUser, listFlag.format) default: rpt, err = rpt.Parse(report.OriginPodman, listFlag.format) } if err != nil { return err } if rpt.RenderHeaders && !listFlag.noHeading { if err := rpt.Execute(headers); err != nil { return fmt.Errorf("failed to write report column headers: %w", err) } } return rpt.Execute(artifacts) }