1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
|
package source
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/containers/image/v5/oci/layout"
"github.com/containers/image/v5/types"
"github.com/opencontainers/go-digest"
specV1 "github.com/opencontainers/image-spec/specs-go/v1"
)
// MediaTypeSourceImageConfig specifies the media type of a source-image config.
const MediaTypeSourceImageConfig = "application/vnd.oci.source.image.config.v1+json"
// ImageConfig specifies the config of a source image.
type ImageConfig struct {
// Created is the combined date and time at which the layer was created, formatted as defined by RFC 3339, section 5.6.
Created *time.Time `json:"created,omitempty"`
// Author is the author of the source image.
Author string `json:"author,omitempty"`
}
// writeManifest writes the specified OCI `manifest` to the source image at
// `ociDest`.
func writeManifest(ctx context.Context, manifest *specV1.Manifest, ociDest types.ImageDestination) (*digest.Digest, int64, error) {
rawData, err := json.Marshal(&manifest)
if err != nil {
return nil, -1, fmt.Errorf("marshalling manifest: %w", err)
}
if err := ociDest.PutManifest(ctx, rawData, nil); err != nil {
return nil, -1, fmt.Errorf("writing manifest: %w", err)
}
manifestDigest := digest.FromBytes(rawData)
return &manifestDigest, int64(len(rawData)), nil
}
// readManifestFromImageSource reads the manifest from the specified image
// source. Note that the manifest is expected to be an OCI v1 manifest.
func readManifestFromImageSource(ctx context.Context, src types.ImageSource) (*specV1.Manifest, *digest.Digest, int64, error) {
rawData, mimeType, err := src.GetManifest(ctx, nil)
if err != nil {
return nil, nil, -1, err
}
if mimeType != specV1.MediaTypeImageManifest {
return nil, nil, -1, fmt.Errorf("image %q is of type %q (expected: %q)", strings.TrimPrefix(src.Reference().StringWithinTransport(), "//"), mimeType, specV1.MediaTypeImageManifest)
}
manifest := specV1.Manifest{}
if err := json.Unmarshal(rawData, &manifest); err != nil {
return nil, nil, -1, fmt.Errorf("reading manifest: %w", err)
}
manifestDigest := digest.FromBytes(rawData)
return &manifest, &manifestDigest, int64(len(rawData)), nil
}
// readManifestFromOCIPath returns the manifest of the specified source image
// at `sourcePath` along with its digest. The digest can later on be used to
// locate the manifest on the file system.
func readManifestFromOCIPath(ctx context.Context, sourcePath string) (*specV1.Manifest, *digest.Digest, int64, error) {
ociRef, err := layout.ParseReference(sourcePath)
if err != nil {
return nil, nil, -1, err
}
ociSource, err := ociRef.NewImageSource(ctx, &types.SystemContext{})
if err != nil {
return nil, nil, -1, err
}
defer ociSource.Close()
return readManifestFromImageSource(ctx, ociSource)
}
// openOrCreateSourceImage returns an OCI types.ImageDestination of the the
// specified `sourcePath`. Note that if the path doesn't exist, it'll be
// created along with the OCI directory layout.
func openOrCreateSourceImage(ctx context.Context, sourcePath string) (types.ImageDestination, error) {
ociRef, err := layout.ParseReference(sourcePath)
if err != nil {
return nil, err
}
// This will implicitly create an OCI directory layout at `path`.
return ociRef.NewImageDestination(ctx, &types.SystemContext{})
}
// addConfig adds `config` to `ociDest` and returns the corresponding blob
// info.
func addConfig(ctx context.Context, config *ImageConfig, ociDest types.ImageDestination) (*types.BlobInfo, error) {
rawData, err := json.Marshal(config)
if err != nil {
return nil, fmt.Errorf("marshalling config: %w", err)
}
info := types.BlobInfo{
Size: -1, // "unknown": we'll get that information *after* adding
}
addedBlob, err := ociDest.PutBlob(ctx, bytes.NewReader(rawData), info, nil, true)
if err != nil {
return nil, fmt.Errorf("adding config: %w", err)
}
return &addedBlob, nil
}
// removeBlob removes the specified `blob` from the source image at `sourcePath`.
func removeBlob(blob *digest.Digest, sourcePath string) error {
blobPath := filepath.Join(filepath.Join(sourcePath, "blobs/sha256"), blob.Encoded())
return os.Remove(blobPath)
}
|