Files
podman/pkg/libartifact/artifact.go
Brent Baude d7553fabc7 podman artifact
the podman artifact verb is used to manage OCI artifacts.  the following
verbs were added to `podman artifact`:

* add
* inspect
* ls
* pull
* push
* rm

Notable items with this PR:

* all artifact commands and their output are subject to change. i.e.
  consider all of this tech preview
* there is no way to add a file to an artifact that already exists in
  the store.  you would need to delete and recreate the artifact.
* all references to artifacts names should be fully qualified names in
  the form of repo/name:tag (i.e. quay.io/artifact/foobar:latest)
* i understand that we will likely want to be able to attribute things
  like arch, etc to artifact files.  this function is not available yet.

Many thanks to Paul Holzinger for autocompletion PRs and review PRs that
fixed issues early on.

Also fix up some Args function to specify the correct number of args.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
Signed-off-by: Brent Baude <bbaude@redhat.com>
2025-01-21 12:47:30 -06:00

90 lines
2.4 KiB
Go

package libartifact
import (
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/containers/image/v5/manifest"
"github.com/opencontainers/go-digest"
)
type Artifact struct {
Manifests []manifest.OCI1
Name string
}
// TotalSizeBytes returns the total bytes of the all the artifact layers
func (a *Artifact) TotalSizeBytes() int64 {
var s int64
for _, artifact := range a.Manifests {
for _, layer := range artifact.Layers {
s += layer.Size
}
}
return s
}
// GetName returns the "name" or "image reference" of the artifact
func (a *Artifact) GetName() (string, error) {
if a.Name != "" {
return a.Name, nil
}
// We don't have a concept of None for artifacts yet, but if we do,
// then we should probably not error but return `None`
return "", errors.New("artifact is unnamed")
}
// SetName is a accessor for setting the artifact name
// Note: long term this may not be needed, and we would
// be comfortable with simply using the exported field
// called Name
func (a *Artifact) SetName(name string) {
a.Name = name
}
func (a *Artifact) GetDigest() (*digest.Digest, error) {
if len(a.Manifests) > 1 {
return nil, fmt.Errorf("not supported: multiple manifests found in artifact")
}
if len(a.Manifests) < 1 {
return nil, fmt.Errorf("not supported: no manifests found in artifact")
}
b, err := json.Marshal(a.Manifests[0])
if err != nil {
return nil, err
}
artifactDigest := digest.FromBytes(b)
return &artifactDigest, nil
}
type ArtifactList []*Artifact
// GetByNameOrDigest returns an artifact, if present, by a given name
// Returns an error if not found
func (al ArtifactList) GetByNameOrDigest(nameOrDigest string) (*Artifact, bool, error) {
// This is the hot route through
for _, artifact := range al {
if artifact.Name == nameOrDigest {
return artifact, false, nil
}
}
// Before giving up, check by digest
for _, artifact := range al {
// TODO Here we have to assume only a single manifest for the artifact; this will
// need to evolve
if len(artifact.Manifests) > 0 {
artifactDigest, err := artifact.GetDigest()
if err != nil {
return nil, false, err
}
// If the artifact's digest matches or is a prefix of ...
if artifactDigest.Encoded() == nameOrDigest || strings.HasPrefix(artifactDigest.Encoded(), nameOrDigest) {
return artifact, true, nil
}
}
}
return nil, false, fmt.Errorf("no artifact found with name or digest of %s", nameOrDigest)
}