Files
attest/oci/types.go
James Carnegie b4e6767cc6 feature!: support for setting HTTP User-Agent header (#157)
* feature!: support for setting HTTP User-Agent header

* fix lint

* fix e2e

* refactor: move http.go to internal/util/useragent package and rename functions to Get and Set

* Move packages and use attest version
2024-09-09 14:22:17 +01:00

219 lines
5.0 KiB
Go

package oci
import (
"bytes"
"context"
"encoding/json"
"fmt"
"strings"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/layout"
"github.com/google/go-containerregistry/pkg/v1/remote"
)
const (
OCIReferenceTarget = "org.opencontainers.image.ref.name"
LocalPrefix = "oci://"
RegistryPrefix = "docker://"
OCI SourceType = "OCI"
Docker SourceType = "Docker"
)
type (
SourceType string
NamedIndex struct {
Index v1.ImageIndex
Name string
}
)
type ImageSpecOption func(*ImageSpec) error
type ImageSpec struct {
// OCI or Docker
Type SourceType
// without oci:// or docker:// (name or path)
Identifier string
Platform *v1.Platform
}
func IndexFromPath(path string) (*NamedIndex, error) {
wrapperIdx, err := layout.ImageIndexFromPath(path)
if err != nil {
return nil, fmt.Errorf("failed to load image index: %w", err)
}
idxm, err := wrapperIdx.IndexManifest()
if err != nil {
return nil, fmt.Errorf("failed to get digest: %w", err)
}
imageName := idxm.Manifests[0].Annotations[OCIReferenceTarget]
idxDigest := idxm.Manifests[0].Digest
idx, err := wrapperIdx.ImageIndex(idxDigest)
if err != nil {
return nil, fmt.Errorf("failed to extract ImageIndex for digest %s: %w", idxDigest.String(), err)
}
return &NamedIndex{
Index: idx,
Name: imageName,
}, nil
}
func IndexFromRemote(ctx context.Context, image string) (*NamedIndex, error) {
ref, err := name.ParseReference(image)
if err != nil {
return nil, fmt.Errorf("failed to parse image reference %s: %w", image, err)
}
// Pull the image from the registry
idx, err := remote.Index(ref, WithOptions(ctx, nil)...)
if err != nil {
return nil, fmt.Errorf("failed to pull image %s: %w", image, err)
}
return &NamedIndex{
Index: idx,
Name: image,
}, nil
}
func LoadIndex(ctx context.Context, input *ImageSpec) (*NamedIndex, error) {
if input.Type == OCI {
return IndexFromPath(input.Identifier)
}
return IndexFromRemote(ctx, input.Identifier)
}
func (i *ImageSpec) ForPlatforms(platform string) ([]*ImageSpec, error) {
platforms := strings.Split(platform, ",")
var specs []*ImageSpec
for _, pStr := range platforms {
p, err := ParsePlatform(pStr)
if err != nil {
return nil, err
}
spec := &ImageSpec{
Type: i.Type,
Identifier: i.Identifier,
Platform: p,
}
specs = append(specs, spec)
}
return specs, nil
}
func ParseImageSpec(img string, options ...ImageSpecOption) (*ImageSpec, error) {
img = strings.TrimSpace(img)
if strings.Contains(img, ",") {
return nil, fmt.Errorf("only one image is supported")
}
withoutPrefix := strings.TrimPrefix(strings.TrimPrefix(img, LocalPrefix), RegistryPrefix)
src := &ImageSpec{
Identifier: withoutPrefix,
}
if strings.HasPrefix(img, LocalPrefix) {
src.Type = OCI
} else {
src.Type = Docker
}
for _, option := range options {
err := option(src)
if err != nil {
return nil, err
}
}
if src.Platform == nil {
platform, err := ParsePlatform("")
if err != nil {
return nil, err
}
src.Platform = platform
}
return src, nil
}
func WithPlatform(platform string) ImageSpecOption {
return func(i *ImageSpec) error {
if strings.Contains(platform, ",") {
return fmt.Errorf("only one platform is supported")
}
p, err := ParsePlatform(platform)
if err != nil {
return err
}
i.Platform = p
return nil
}
}
func ParseImageSpecs(img string) ([]*ImageSpec, error) {
outputs := strings.Split(img, ",")
var sources []*ImageSpec
for _, output := range outputs {
src, err := ParseImageSpec(output)
if err != nil {
return nil, err
}
sources = append(sources, src)
}
return sources, nil
}
func WithoutTag(image string) (string, error) {
if strings.HasPrefix(image, LocalPrefix) {
return image, nil
}
prefix := ""
if strings.HasPrefix(image, RegistryPrefix) {
image = strings.TrimPrefix(image, RegistryPrefix)
prefix = RegistryPrefix
}
ref, err := name.ParseReference(image)
if err != nil {
return "", err
}
repo := ref.Context().Name()
return prefix + repo, nil
}
type EmptyConfigImage struct {
v1.Image
}
func (i *EmptyConfigImage) RawConfigFile() ([]byte, error) {
return []byte("{}"), nil
}
func (i *EmptyConfigImage) Manifest() (*v1.Manifest, error) {
mf, err := i.Image.Manifest()
if err != nil {
return nil, fmt.Errorf("failed to get manifest: %w", err)
}
mf.Config = v1.Descriptor{
MediaType: "application/vnd.oci.empty.v1+json",
Size: 2,
Digest: v1.Hash{Algorithm: "sha256", Hex: "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"},
Data: []byte("{}"),
}
return mf, nil
}
func (i *EmptyConfigImage) RawManifest() ([]byte, error) {
mf, err := i.Manifest()
if err != nil {
return nil, fmt.Errorf("failed to get manifest: %w", err)
}
return json.Marshal(mf)
}
func (i *EmptyConfigImage) Digest() (v1.Hash, error) {
mb, err := i.RawManifest()
if err != nil {
return v1.Hash{}, err
}
digest, _, err := v1.SHA256(bytes.NewReader(mb))
return digest, err
}