2017-02-11 00:48:15 +08:00
|
|
|
package buildah
|
2017-01-27 00:58:00 +08:00
|
|
|
|
|
|
|
import (
|
2020-08-11 17:28:41 +08:00
|
|
|
"archive/tar"
|
2017-01-27 00:58:00 +08:00
|
|
|
"bytes"
|
2017-10-10 03:05:56 +08:00
|
|
|
"context"
|
2017-01-27 00:58:00 +08:00
|
|
|
"encoding/json"
|
2022-07-06 17:14:06 +08:00
|
|
|
"errors"
|
2018-05-22 05:02:50 +08:00
|
|
|
"fmt"
|
2017-01-27 00:58:00 +08:00
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2019-03-23 05:06:56 +08:00
|
|
|
"strings"
|
2017-01-27 22:38:32 +08:00
|
|
|
"time"
|
2017-01-27 00:58:00 +08:00
|
|
|
|
2019-07-25 22:10:03 +08:00
|
|
|
"github.com/containers/buildah/copier"
|
2021-02-07 06:49:40 +08:00
|
|
|
"github.com/containers/buildah/define"
|
2018-09-18 03:20:16 +08:00
|
|
|
"github.com/containers/buildah/docker"
|
2023-11-01 22:18:40 +08:00
|
|
|
"github.com/containers/buildah/internal/config"
|
2023-07-18 04:27:19 +08:00
|
|
|
"github.com/containers/buildah/internal/mkcw"
|
2023-10-11 14:44:20 +08:00
|
|
|
"github.com/containers/buildah/internal/tmpdir"
|
2019-10-26 05:19:30 +08:00
|
|
|
"github.com/containers/image/v5/docker/reference"
|
|
|
|
"github.com/containers/image/v5/image"
|
|
|
|
"github.com/containers/image/v5/manifest"
|
|
|
|
is "github.com/containers/image/v5/storage"
|
|
|
|
"github.com/containers/image/v5/types"
|
2017-05-17 23:53:28 +08:00
|
|
|
"github.com/containers/storage"
|
2017-01-27 00:58:00 +08:00
|
|
|
"github.com/containers/storage/pkg/archive"
|
2019-07-25 22:10:03 +08:00
|
|
|
"github.com/containers/storage/pkg/idtools"
|
2017-01-27 00:58:00 +08:00
|
|
|
"github.com/containers/storage/pkg/ioutils"
|
|
|
|
digest "github.com/opencontainers/go-digest"
|
|
|
|
specs "github.com/opencontainers/image-spec/specs-go"
|
2019-07-18 16:42:09 +08:00
|
|
|
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
2017-10-10 03:05:56 +08:00
|
|
|
"github.com/sirupsen/logrus"
|
2017-01-27 00:58:00 +08:00
|
|
|
)
|
|
|
|
|
2017-05-18 05:02:40 +08:00
|
|
|
const (
|
|
|
|
// OCIv1ImageManifest is the MIME type of an OCIv1 image manifest,
|
|
|
|
// suitable for specifying as a value of the PreferredManifestType
|
|
|
|
// member of a CommitOptions structure. It is also the default.
|
2021-03-02 02:07:58 +08:00
|
|
|
OCIv1ImageManifest = define.OCIv1ImageManifest
|
2017-05-18 05:02:40 +08:00
|
|
|
// Dockerv2ImageManifest is the MIME type of a Docker v2s2 image
|
|
|
|
// manifest, suitable for specifying as a value of the
|
|
|
|
// PreferredManifestType member of a CommitOptions structure.
|
2021-03-02 02:07:58 +08:00
|
|
|
Dockerv2ImageManifest = define.Dockerv2ImageManifest
|
2017-05-18 05:02:40 +08:00
|
|
|
)
|
|
|
|
|
2023-12-13 03:54:42 +08:00
|
|
|
// ExtractRootfsOptions is consumed by ExtractRootfs() which allows users to
|
|
|
|
// control whether various information like the like setuid and setgid bits and
|
|
|
|
// xattrs are preserved when extracting file system objects.
|
2022-04-29 21:39:42 +08:00
|
|
|
type ExtractRootfsOptions struct {
|
|
|
|
StripSetuidBit bool // strip the setuid bit off of items being extracted.
|
|
|
|
StripSetgidBit bool // strip the setgid bit off of items being extracted.
|
|
|
|
StripXattrs bool // don't record extended attributes of items being extracted.
|
|
|
|
}
|
|
|
|
|
2017-01-27 00:58:00 +08:00
|
|
|
type containerImageRef struct {
|
2020-08-08 01:11:31 +08:00
|
|
|
fromImageName string
|
|
|
|
fromImageID string
|
2017-05-18 05:02:40 +08:00
|
|
|
store storage.Store
|
|
|
|
compression archive.Compression
|
|
|
|
name reference.Named
|
2017-06-02 00:09:23 +08:00
|
|
|
names []string
|
2018-05-22 05:02:50 +08:00
|
|
|
containerID string
|
|
|
|
mountLabel string
|
2017-06-02 00:09:23 +08:00
|
|
|
layerID string
|
2017-05-18 05:02:40 +08:00
|
|
|
oconfig []byte
|
|
|
|
dconfig []byte
|
2020-08-27 04:56:57 +08:00
|
|
|
created *time.Time
|
2017-05-18 05:02:40 +08:00
|
|
|
createdBy string
|
2018-04-27 22:59:03 +08:00
|
|
|
historyComment string
|
2017-05-18 05:02:40 +08:00
|
|
|
annotations map[string]string
|
|
|
|
preferredManifestType string
|
2018-05-22 05:02:50 +08:00
|
|
|
squash bool
|
2023-07-18 04:27:19 +08:00
|
|
|
confidentialWorkload ConfidentialWorkloadOptions
|
2022-06-01 15:59:52 +08:00
|
|
|
omitHistory bool
|
2019-04-09 10:59:52 +08:00
|
|
|
emptyLayer bool
|
2021-02-07 06:49:40 +08:00
|
|
|
idMappingOptions *define.IDMappingOptions
|
2018-06-09 00:55:46 +08:00
|
|
|
parent string
|
2018-10-18 06:06:16 +08:00
|
|
|
blobDirectory string
|
2019-01-19 04:39:58 +08:00
|
|
|
preEmptyLayers []v1.History
|
|
|
|
postEmptyLayers []v1.History
|
2023-11-01 22:18:40 +08:00
|
|
|
overrideChanges []string
|
|
|
|
overrideConfig *manifest.Schema2Config
|
2023-12-13 03:54:42 +08:00
|
|
|
extraImageContent map[string]string
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
|
2021-05-26 04:34:36 +08:00
|
|
|
type blobLayerInfo struct {
|
|
|
|
ID string
|
|
|
|
Size int64
|
|
|
|
}
|
|
|
|
|
2017-01-27 00:58:00 +08:00
|
|
|
type containerImageSource struct {
|
2018-10-18 06:06:16 +08:00
|
|
|
path string
|
|
|
|
ref *containerImageRef
|
|
|
|
store storage.Store
|
|
|
|
containerID string
|
|
|
|
mountLabel string
|
|
|
|
layerID string
|
|
|
|
names []string
|
|
|
|
compression archive.Compression
|
|
|
|
config []byte
|
|
|
|
configDigest digest.Digest
|
|
|
|
manifest []byte
|
|
|
|
manifestType string
|
|
|
|
blobDirectory string
|
2021-05-26 04:34:36 +08:00
|
|
|
blobLayers map[digest.Digest]blobLayerInfo
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
|
2018-04-12 22:20:36 +08:00
|
|
|
func (i *containerImageRef) NewImage(ctx context.Context, sc *types.SystemContext) (types.ImageCloser, error) {
|
|
|
|
src, err := i.NewImageSource(ctx, sc)
|
2017-01-27 00:58:00 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-04-12 22:20:36 +08:00
|
|
|
return image.FromSource(ctx, sc, src)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
|
2018-01-05 07:05:40 +08:00
|
|
|
func expectedOCIDiffIDs(image v1.Image) int {
|
|
|
|
expected := 0
|
|
|
|
for _, history := range image.History {
|
|
|
|
if !history.EmptyLayer {
|
|
|
|
expected = expected + 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return expected
|
|
|
|
}
|
|
|
|
|
|
|
|
func expectedDockerDiffIDs(image docker.V2Image) int {
|
|
|
|
expected := 0
|
|
|
|
for _, history := range image.History {
|
|
|
|
if !history.EmptyLayer {
|
|
|
|
expected = expected + 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return expected
|
|
|
|
}
|
|
|
|
|
2018-05-22 05:02:50 +08:00
|
|
|
// Compute the media types which we need to attach to a layer, given the type of
|
|
|
|
// compression that we'll be applying.
|
2018-10-18 06:06:16 +08:00
|
|
|
func computeLayerMIMEType(what string, layerCompression archive.Compression) (omediaType, dmediaType string, err error) {
|
2018-05-22 05:02:50 +08:00
|
|
|
omediaType = v1.MediaTypeImageLayer
|
|
|
|
dmediaType = docker.V2S2MediaTypeUncompressedLayer
|
2018-10-18 06:06:16 +08:00
|
|
|
if layerCompression != archive.Uncompressed {
|
|
|
|
switch layerCompression {
|
2018-05-22 05:02:50 +08:00
|
|
|
case archive.Gzip:
|
|
|
|
omediaType = v1.MediaTypeImageLayerGzip
|
2018-07-19 07:49:56 +08:00
|
|
|
dmediaType = manifest.DockerV2Schema2LayerMediaType
|
2018-05-22 05:02:50 +08:00
|
|
|
logrus.Debugf("compressing %s with gzip", what)
|
|
|
|
case archive.Bzip2:
|
|
|
|
// Until the image specs define a media type for bzip2-compressed layers, even if we know
|
|
|
|
// how to decompress them, we can't try to compress layers with bzip2.
|
|
|
|
return "", "", errors.New("media type for bzip2-compressed layers is not defined")
|
|
|
|
case archive.Xz:
|
|
|
|
// Until the image specs define a media type for xz-compressed layers, even if we know
|
|
|
|
// how to decompress them, we can't try to compress layers with xz.
|
|
|
|
return "", "", errors.New("media type for xz-compressed layers is not defined")
|
2019-07-09 05:50:33 +08:00
|
|
|
case archive.Zstd:
|
|
|
|
// Until the image specs define a media type for zstd-compressed layers, even if we know
|
|
|
|
// how to decompress them, we can't try to compress layers with zstd.
|
|
|
|
return "", "", errors.New("media type for zstd-compressed layers is not defined")
|
2018-05-22 05:02:50 +08:00
|
|
|
default:
|
|
|
|
logrus.Debugf("compressing %s with unknown compressor(?)", what)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return omediaType, dmediaType, nil
|
|
|
|
}
|
|
|
|
|
2023-07-18 04:27:19 +08:00
|
|
|
// Extract the container's whole filesystem as a filesystem image, wrapped
|
|
|
|
// in LUKS-compatible encryption.
|
|
|
|
func (i *containerImageRef) extractConfidentialWorkloadFS(options ConfidentialWorkloadOptions) (io.ReadCloser, error) {
|
|
|
|
var image v1.Image
|
|
|
|
if err := json.Unmarshal(i.oconfig, &image); err != nil {
|
|
|
|
return nil, fmt.Errorf("recreating OCI configuration for %q: %w", i.containerID, err)
|
|
|
|
}
|
2023-12-13 01:18:20 +08:00
|
|
|
if options.TempDir == "" {
|
|
|
|
cdir, err := i.store.ContainerDirectory(i.containerID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("getting the per-container data directory for %q: %w", i.containerID, err)
|
|
|
|
}
|
|
|
|
tempdir, err := os.MkdirTemp(cdir, "buildah-rootfs")
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("creating a temporary data directory to hold a rootfs image for %q: %w", i.containerID, err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err := os.RemoveAll(tempdir); err != nil {
|
|
|
|
logrus.Warnf("removing temporary directory %q: %v", tempdir, err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
options.TempDir = tempdir
|
|
|
|
}
|
2023-07-18 04:27:19 +08:00
|
|
|
mountPoint, err := i.store.Mount(i.containerID, i.mountLabel)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("mounting container %q: %w", i.containerID, err)
|
|
|
|
}
|
|
|
|
archiveOptions := mkcw.ArchiveOptions{
|
|
|
|
AttestationURL: options.AttestationURL,
|
|
|
|
CPUs: options.CPUs,
|
|
|
|
Memory: options.Memory,
|
|
|
|
TempDir: options.TempDir,
|
|
|
|
TeeType: options.TeeType,
|
|
|
|
IgnoreAttestationErrors: options.IgnoreAttestationErrors,
|
|
|
|
WorkloadID: options.WorkloadID,
|
|
|
|
DiskEncryptionPassphrase: options.DiskEncryptionPassphrase,
|
|
|
|
Slop: options.Slop,
|
|
|
|
FirmwareLibrary: options.FirmwareLibrary,
|
2023-12-13 01:18:20 +08:00
|
|
|
GraphOptions: i.store.GraphOptions(),
|
2023-07-18 04:27:19 +08:00
|
|
|
}
|
2023-12-13 03:54:42 +08:00
|
|
|
if len(i.extraImageContent) > 0 {
|
|
|
|
logrus.Warnf("ignoring extra requested content %v, not implemented (yet)", i.extraImageContent)
|
|
|
|
}
|
2023-07-18 04:27:19 +08:00
|
|
|
rc, _, err := mkcw.Archive(mountPoint, &image, archiveOptions)
|
|
|
|
if err != nil {
|
|
|
|
if _, err2 := i.store.Unmount(i.containerID, false); err2 != nil {
|
|
|
|
logrus.Debugf("unmounting container %q: %v", i.containerID, err2)
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("converting rootfs %q: %w", i.containerID, err)
|
|
|
|
}
|
|
|
|
return ioutils.NewReadCloserWrapper(rc, func() error {
|
|
|
|
if err = rc.Close(); err != nil {
|
|
|
|
err = fmt.Errorf("closing tar archive of container %q: %w", i.containerID, err)
|
|
|
|
}
|
|
|
|
if _, err2 := i.store.Unmount(i.containerID, false); err == nil {
|
|
|
|
if err2 != nil {
|
|
|
|
err2 = fmt.Errorf("unmounting container %q: %w", i.containerID, err2)
|
|
|
|
}
|
|
|
|
err = err2
|
|
|
|
} else {
|
|
|
|
logrus.Debugf("unmounting container %q: %v", i.containerID, err2)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}), nil
|
|
|
|
}
|
|
|
|
|
2018-05-22 05:02:50 +08:00
|
|
|
// Extract the container's whole filesystem as if it were a single layer.
|
2023-12-13 03:54:42 +08:00
|
|
|
// The ExtractRootfsOptions control whether or not to preserve setuid and
|
|
|
|
// setgid bits and extended attributes on contents.
|
2022-04-29 21:39:42 +08:00
|
|
|
func (i *containerImageRef) extractRootfs(opts ExtractRootfsOptions) (io.ReadCloser, chan error, error) {
|
2019-07-25 22:10:03 +08:00
|
|
|
var uidMap, gidMap []idtools.IDMap
|
2018-05-22 05:02:50 +08:00
|
|
|
mountPoint, err := i.store.Mount(i.containerID, i.mountLabel)
|
|
|
|
if err != nil {
|
2022-09-18 18:36:08 +08:00
|
|
|
return nil, nil, fmt.Errorf("mounting container %q: %w", i.containerID, err)
|
2018-05-22 05:02:50 +08:00
|
|
|
}
|
2019-07-25 22:10:03 +08:00
|
|
|
pipeReader, pipeWriter := io.Pipe()
|
2021-03-12 16:15:41 +08:00
|
|
|
errChan := make(chan error, 1)
|
2019-07-25 22:10:03 +08:00
|
|
|
go func() {
|
2021-03-12 16:15:41 +08:00
|
|
|
defer close(errChan)
|
2023-12-13 03:54:42 +08:00
|
|
|
if len(i.extraImageContent) > 0 {
|
|
|
|
// Abuse the tar format and _prepend_ the synthesized
|
|
|
|
// data items to the archive we'll get from
|
|
|
|
// copier.Get(), in a way that looks right to a reader
|
|
|
|
// as long as we DON'T Close() the tar Writer.
|
|
|
|
filename, _, _, err := i.makeExtraImageContentDiff(false)
|
|
|
|
if err != nil {
|
|
|
|
errChan <- err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
file, err := os.Open(filename)
|
|
|
|
if err != nil {
|
|
|
|
errChan <- err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
if _, err = io.Copy(pipeWriter, file); err != nil {
|
|
|
|
errChan <- err
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2019-07-25 22:10:03 +08:00
|
|
|
if i.idMappingOptions != nil {
|
|
|
|
uidMap, gidMap = convertRuntimeIDMaps(i.idMappingOptions.UIDMap, i.idMappingOptions.GIDMap)
|
|
|
|
}
|
|
|
|
copierOptions := copier.GetOptions{
|
2022-04-29 21:39:42 +08:00
|
|
|
UIDMap: uidMap,
|
|
|
|
GIDMap: gidMap,
|
|
|
|
StripSetuidBit: opts.StripSetuidBit,
|
|
|
|
StripSetgidBit: opts.StripSetgidBit,
|
|
|
|
StripXattrs: opts.StripXattrs,
|
2019-07-25 22:10:03 +08:00
|
|
|
}
|
|
|
|
err = copier.Get(mountPoint, mountPoint, copierOptions, []string{"."}, pipeWriter)
|
2021-03-12 16:15:41 +08:00
|
|
|
errChan <- err
|
2019-07-25 22:10:03 +08:00
|
|
|
pipeWriter.Close()
|
2021-03-12 16:15:41 +08:00
|
|
|
|
2019-07-25 22:10:03 +08:00
|
|
|
}()
|
|
|
|
return ioutils.NewReadCloserWrapper(pipeReader, func() error {
|
|
|
|
if err = pipeReader.Close(); err != nil {
|
2022-09-18 18:36:08 +08:00
|
|
|
err = fmt.Errorf("closing tar archive of container %q: %w", i.containerID, err)
|
2018-05-22 05:02:50 +08:00
|
|
|
}
|
2018-07-18 23:49:09 +08:00
|
|
|
if _, err2 := i.store.Unmount(i.containerID, false); err == nil {
|
2018-05-22 05:02:50 +08:00
|
|
|
if err2 != nil {
|
2022-09-18 18:36:08 +08:00
|
|
|
err2 = fmt.Errorf("unmounting container %q: %w", i.containerID, err2)
|
2018-05-22 05:02:50 +08:00
|
|
|
}
|
|
|
|
err = err2
|
|
|
|
}
|
|
|
|
return err
|
2021-03-12 16:15:41 +08:00
|
|
|
}), errChan, nil
|
2018-05-22 05:02:50 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Build fresh copies of the container configuration structures so that we can edit them
|
|
|
|
// without making unintended changes to the original Builder.
|
|
|
|
func (i *containerImageRef) createConfigsAndManifests() (v1.Image, v1.Manifest, docker.V2Image, docker.V2S2Manifest, error) {
|
2020-09-01 05:09:10 +08:00
|
|
|
created := time.Now().UTC()
|
2020-08-27 04:56:57 +08:00
|
|
|
if i.created != nil {
|
|
|
|
created = *i.created
|
|
|
|
}
|
2018-05-22 05:02:50 +08:00
|
|
|
|
|
|
|
// Build an empty image, and then decode over it.
|
|
|
|
oimage := v1.Image{}
|
|
|
|
if err := json.Unmarshal(i.oconfig, &oimage); err != nil {
|
|
|
|
return v1.Image{}, v1.Manifest{}, docker.V2Image{}, docker.V2S2Manifest{}, err
|
|
|
|
}
|
|
|
|
// Always replace this value, since we're newer than our base image.
|
|
|
|
oimage.Created = &created
|
|
|
|
// Clear the list of diffIDs, since we always repopulate it.
|
|
|
|
oimage.RootFS.Type = docker.TypeLayers
|
|
|
|
oimage.RootFS.DiffIDs = []digest.Digest{}
|
|
|
|
// Only clear the history if we're squashing, otherwise leave it be so that we can append
|
|
|
|
// entries to it.
|
2023-07-18 04:27:19 +08:00
|
|
|
if i.confidentialWorkload.Convert || i.squash || i.omitHistory {
|
2018-05-22 05:02:50 +08:00
|
|
|
oimage.History = []v1.History{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build an empty image, and then decode over it.
|
|
|
|
dimage := docker.V2Image{}
|
|
|
|
if err := json.Unmarshal(i.dconfig, &dimage); err != nil {
|
|
|
|
return v1.Image{}, v1.Manifest{}, docker.V2Image{}, docker.V2S2Manifest{}, err
|
|
|
|
}
|
2019-04-15 22:02:05 +08:00
|
|
|
dimage.Parent = docker.ID(i.parent)
|
2019-11-20 04:23:14 +08:00
|
|
|
dimage.Container = i.containerID
|
|
|
|
if dimage.Config != nil {
|
|
|
|
dimage.ContainerConfig = *dimage.Config
|
|
|
|
}
|
2018-05-22 05:02:50 +08:00
|
|
|
// Always replace this value, since we're newer than our base image.
|
|
|
|
dimage.Created = created
|
2023-11-08 06:32:25 +08:00
|
|
|
// Clear the list of diffIDs, since we always repopulate it.
|
|
|
|
dimage.RootFS = &docker.V2S2RootFS{}
|
|
|
|
dimage.RootFS.Type = docker.TypeLayers
|
|
|
|
dimage.RootFS.DiffIDs = []digest.Digest{}
|
|
|
|
// Only clear the history if we're squashing, otherwise leave it be so
|
2023-12-13 03:54:42 +08:00
|
|
|
// that we can append entries to it. Clear the parent, too, to reflect
|
|
|
|
// that we no longer include its layers and history.
|
2023-11-08 06:32:25 +08:00
|
|
|
if i.confidentialWorkload.Convert || i.squash || i.omitHistory {
|
|
|
|
dimage.Parent = ""
|
|
|
|
dimage.History = []docker.V2S2History{}
|
|
|
|
}
|
|
|
|
|
2023-11-01 22:18:40 +08:00
|
|
|
// If we were supplied with a configuration, copy fields from it to
|
|
|
|
// matching fields in both formats.
|
|
|
|
if err := config.Override(dimage.Config, &oimage.Config, i.overrideChanges, i.overrideConfig); err != nil {
|
|
|
|
return v1.Image{}, v1.Manifest{}, docker.V2Image{}, docker.V2S2Manifest{}, fmt.Errorf("applying changes: %w", err)
|
|
|
|
}
|
|
|
|
|
2023-07-18 04:27:19 +08:00
|
|
|
// If we're producing a confidential workload, override the command and
|
|
|
|
// assorted other settings that aren't expected to work correctly.
|
|
|
|
if i.confidentialWorkload.Convert {
|
|
|
|
dimage.Config.Entrypoint = []string{"/entrypoint"}
|
|
|
|
oimage.Config.Entrypoint = []string{"/entrypoint"}
|
|
|
|
dimage.Config.Cmd = nil
|
|
|
|
oimage.Config.Cmd = nil
|
|
|
|
dimage.Config.User = ""
|
|
|
|
oimage.Config.User = ""
|
|
|
|
dimage.Config.WorkingDir = ""
|
|
|
|
oimage.Config.WorkingDir = ""
|
|
|
|
dimage.Config.Healthcheck = nil
|
|
|
|
dimage.Config.Shell = nil
|
|
|
|
dimage.Config.Volumes = nil
|
|
|
|
oimage.Config.Volumes = nil
|
|
|
|
dimage.Config.ExposedPorts = nil
|
|
|
|
oimage.Config.ExposedPorts = nil
|
|
|
|
}
|
2018-05-22 05:02:50 +08:00
|
|
|
|
|
|
|
// Build empty manifests. The Layers lists will be populated later.
|
|
|
|
omanifest := v1.Manifest{
|
|
|
|
Versioned: specs.Versioned{
|
|
|
|
SchemaVersion: 2,
|
|
|
|
},
|
2021-11-29 23:14:47 +08:00
|
|
|
MediaType: v1.MediaTypeImageManifest,
|
2018-05-22 05:02:50 +08:00
|
|
|
Config: v1.Descriptor{
|
|
|
|
MediaType: v1.MediaTypeImageConfig,
|
|
|
|
},
|
|
|
|
Layers: []v1.Descriptor{},
|
|
|
|
Annotations: i.annotations,
|
|
|
|
}
|
|
|
|
|
|
|
|
dmanifest := docker.V2S2Manifest{
|
|
|
|
V2Versioned: docker.V2Versioned{
|
|
|
|
SchemaVersion: 2,
|
2018-07-19 07:49:56 +08:00
|
|
|
MediaType: manifest.DockerV2Schema2MediaType,
|
2018-05-22 05:02:50 +08:00
|
|
|
},
|
|
|
|
Config: docker.V2S2Descriptor{
|
2018-07-19 07:49:56 +08:00
|
|
|
MediaType: manifest.DockerV2Schema2ConfigMediaType,
|
2018-05-22 05:02:50 +08:00
|
|
|
},
|
|
|
|
Layers: []docker.V2S2Descriptor{},
|
|
|
|
}
|
|
|
|
|
|
|
|
return oimage, omanifest, dimage, dmanifest, nil
|
|
|
|
}
|
|
|
|
|
2018-04-12 22:20:36 +08:00
|
|
|
func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.SystemContext) (src types.ImageSource, err error) {
|
2017-06-02 00:09:23 +08:00
|
|
|
// Decide which type of manifest and configuration output we're going to provide.
|
2017-10-10 03:05:56 +08:00
|
|
|
manifestType := i.preferredManifestType
|
2017-05-18 05:01:06 +08:00
|
|
|
// If it's not a format we support, return an error.
|
2018-07-19 07:49:56 +08:00
|
|
|
if manifestType != v1.MediaTypeImageManifest && manifestType != manifest.DockerV2Schema2MediaType {
|
2022-07-06 17:14:06 +08:00
|
|
|
return nil, fmt.Errorf("no supported manifest types (attempted to use %q, only know %q and %q)",
|
2018-07-19 07:49:56 +08:00
|
|
|
manifestType, v1.MediaTypeImageManifest, manifest.DockerV2Schema2MediaType)
|
2017-05-18 05:01:06 +08:00
|
|
|
}
|
2017-06-01 01:44:41 +08:00
|
|
|
// Start building the list of layers using the read-write layer.
|
2017-01-27 00:58:00 +08:00
|
|
|
layers := []string{}
|
2017-06-02 00:09:23 +08:00
|
|
|
layerID := i.layerID
|
2017-05-17 23:53:28 +08:00
|
|
|
layer, err := i.store.Layer(layerID)
|
2017-01-27 00:58:00 +08:00
|
|
|
if err != nil {
|
2022-07-06 17:14:06 +08:00
|
|
|
return nil, fmt.Errorf("unable to read layer %q: %w", layerID, err)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2023-12-13 03:54:42 +08:00
|
|
|
// Walk the list of parent layers, prepending each as we go. If we're squashing
|
|
|
|
// or making a confidential workload, we're only producing one layer, so stop at
|
|
|
|
// the layer ID of the top layer, which we won't really be using anyway.
|
2017-01-27 00:58:00 +08:00
|
|
|
for layer != nil {
|
|
|
|
layers = append(append([]string{}, layerID), layers...)
|
|
|
|
layerID = layer.Parent
|
2023-07-18 04:27:19 +08:00
|
|
|
if layerID == "" || i.confidentialWorkload.Convert || i.squash {
|
2017-01-27 00:58:00 +08:00
|
|
|
err = nil
|
|
|
|
break
|
|
|
|
}
|
2017-05-17 23:53:28 +08:00
|
|
|
layer, err = i.store.Layer(layerID)
|
2017-01-27 00:58:00 +08:00
|
|
|
if err != nil {
|
2022-07-06 17:14:06 +08:00
|
|
|
return nil, fmt.Errorf("unable to read layer %q: %w", layerID, err)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
}
|
2023-12-13 03:54:42 +08:00
|
|
|
layer = nil
|
|
|
|
|
|
|
|
// If we're slipping in a synthesized layer, we need to add a placeholder for it
|
|
|
|
// to the list.
|
|
|
|
const synthesizedLayerID = "(synthesized layer)"
|
|
|
|
if len(i.extraImageContent) > 0 && !i.confidentialWorkload.Convert && !i.squash {
|
|
|
|
layers = append(layers, synthesizedLayerID)
|
|
|
|
}
|
2017-01-27 22:38:32 +08:00
|
|
|
logrus.Debugf("layer list: %q", layers)
|
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Make a temporary directory to hold blobs.
|
2023-10-11 14:44:20 +08:00
|
|
|
path, err := os.MkdirTemp(tmpdir.GetTempDir(), define.Package)
|
2017-01-27 00:58:00 +08:00
|
|
|
if err != nil {
|
2022-09-18 18:36:08 +08:00
|
|
|
return nil, fmt.Errorf("creating temporary directory to hold layer blobs: %w", err)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2017-01-28 15:18:02 +08:00
|
|
|
logrus.Debugf("using %q to hold temporary data", path)
|
2017-01-27 00:58:00 +08:00
|
|
|
defer func() {
|
|
|
|
if src == nil {
|
|
|
|
err2 := os.RemoveAll(path)
|
|
|
|
if err2 != nil {
|
2021-03-27 17:17:12 +08:00
|
|
|
logrus.Errorf("error removing layer blob directory: %v", err)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2018-05-22 05:02:50 +08:00
|
|
|
// Build fresh copies of the configurations and manifest so that we don't mess with any
|
|
|
|
// values in the Builder object itself.
|
|
|
|
oimage, omanifest, dimage, dmanifest, err := i.createConfigsAndManifests()
|
2017-05-18 05:01:06 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-01-27 22:38:32 +08:00
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Extract each layer and compute its digests, both compressed (if requested) and uncompressed.
|
2023-12-13 03:54:42 +08:00
|
|
|
var extraImageContentDiff string
|
|
|
|
var extraImageContentDiffDigest digest.Digest
|
2021-05-26 04:34:36 +08:00
|
|
|
blobLayers := make(map[digest.Digest]blobLayerInfo)
|
2017-01-27 00:58:00 +08:00
|
|
|
for _, layerID := range layers {
|
2018-05-22 05:02:50 +08:00
|
|
|
what := fmt.Sprintf("layer %q", layerID)
|
2023-07-18 04:27:19 +08:00
|
|
|
if i.confidentialWorkload.Convert || i.squash {
|
2018-05-22 05:02:50 +08:00
|
|
|
what = fmt.Sprintf("container %q", i.containerID)
|
|
|
|
}
|
2017-06-29 05:07:58 +08:00
|
|
|
// The default layer media type assumes no compression.
|
Take a shortcut when writing to local storage
When writing to local storage, take a couple of shortcuts: instead of
recompressing layers to ensure that the values we store in the image
manifest will be correct for content-addressibility, just pretend that
the layer ID is a blob hash value, and that it's a valid layer diffID.
Local storage doesn't generally care if these values are correct, and we
already have to recompute these values when exporting an image, but this
saves us quite a bit of time.
The image library's Copy() routine actually cares about and
sanity-checks these things, so if we're going to take advantage of the
shortcuts, we need to use its higher-level APIs to write a layer, write
the configuration, and write the manifest, then move those items that it
writes to an image with the right set of layers.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #141
Approved by: rhatdan
2017-06-01 01:56:25 +08:00
|
|
|
omediaType := v1.MediaTypeImageLayer
|
|
|
|
dmediaType := docker.V2S2MediaTypeUncompressedLayer
|
2018-10-18 06:06:16 +08:00
|
|
|
// Look up this layer.
|
2023-12-13 03:54:42 +08:00
|
|
|
var layerUncompressedDigest digest.Digest
|
|
|
|
var layerUncompressedSize int64
|
|
|
|
if layerID != synthesizedLayerID {
|
|
|
|
layer, err := i.store.Layer(layerID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to locate layer %q: %w", layerID, err)
|
|
|
|
}
|
|
|
|
layerID = layer.ID
|
|
|
|
layerUncompressedDigest = layer.UncompressedDigest
|
|
|
|
layerUncompressedSize = layer.UncompressedSize
|
|
|
|
} else {
|
|
|
|
diffFilename, digest, size, err := i.makeExtraImageContentDiff(true)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to generate layer for additional content: %w", err)
|
|
|
|
}
|
|
|
|
extraImageContentDiff = diffFilename
|
|
|
|
extraImageContentDiffDigest = digest
|
|
|
|
layerUncompressedDigest = digest
|
|
|
|
layerUncompressedSize = size
|
2018-10-18 06:06:16 +08:00
|
|
|
}
|
2021-05-26 04:34:36 +08:00
|
|
|
// If we already know the digest of the contents of parent
|
|
|
|
// layers, reuse their blobsums, diff IDs, and sizes.
|
2023-12-13 03:54:42 +08:00
|
|
|
if !i.confidentialWorkload.Convert && !i.squash && layerID != i.layerID && layerID != synthesizedLayerID && layerUncompressedDigest != "" {
|
|
|
|
layerBlobSum := layerUncompressedDigest
|
|
|
|
layerBlobSize := layerUncompressedSize
|
|
|
|
diffID := layerUncompressedDigest
|
2018-10-18 06:06:16 +08:00
|
|
|
// Note this layer in the manifest, using the appropriate blobsum.
|
2017-06-29 05:07:58 +08:00
|
|
|
olayerDescriptor := v1.Descriptor{
|
|
|
|
MediaType: omediaType,
|
|
|
|
Digest: layerBlobSum,
|
|
|
|
Size: layerBlobSize,
|
|
|
|
}
|
|
|
|
omanifest.Layers = append(omanifest.Layers, olayerDescriptor)
|
|
|
|
dlayerDescriptor := docker.V2S2Descriptor{
|
|
|
|
MediaType: dmediaType,
|
|
|
|
Digest: layerBlobSum,
|
|
|
|
Size: layerBlobSize,
|
|
|
|
}
|
|
|
|
dmanifest.Layers = append(dmanifest.Layers, dlayerDescriptor)
|
2018-10-18 06:06:16 +08:00
|
|
|
// Note this layer in the list of diffIDs, again using the uncompressed digest.
|
|
|
|
oimage.RootFS.DiffIDs = append(oimage.RootFS.DiffIDs, diffID)
|
|
|
|
dimage.RootFS.DiffIDs = append(dimage.RootFS.DiffIDs, diffID)
|
2021-05-26 04:34:36 +08:00
|
|
|
blobLayers[diffID] = blobLayerInfo{
|
2023-12-13 03:54:42 +08:00
|
|
|
ID: layerID,
|
2021-05-26 04:34:36 +08:00
|
|
|
Size: layerBlobSize,
|
|
|
|
}
|
2017-06-29 05:07:58 +08:00
|
|
|
continue
|
|
|
|
}
|
2018-10-18 06:06:16 +08:00
|
|
|
// Figure out if we need to change the media type, in case we've changed the compression.
|
|
|
|
omediaType, dmediaType, err = computeLayerMIMEType(what, i.compression)
|
2018-05-22 05:02:50 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
Take a shortcut when writing to local storage
When writing to local storage, take a couple of shortcuts: instead of
recompressing layers to ensure that the values we store in the image
manifest will be correct for content-addressibility, just pretend that
the layer ID is a blob hash value, and that it's a valid layer diffID.
Local storage doesn't generally care if these values are correct, and we
already have to recompute these values when exporting an image, but this
saves us quite a bit of time.
The image library's Copy() routine actually cares about and
sanity-checks these things, so if we're going to take advantage of the
shortcuts, we need to use its higher-level APIs to write a layer, write
the configuration, and write the manifest, then move those items that it
writes to an image with the right set of layers.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #141
Approved by: rhatdan
2017-06-01 01:56:25 +08:00
|
|
|
}
|
2018-05-22 05:02:50 +08:00
|
|
|
// Start reading either the layer or the whole container rootfs.
|
2017-06-29 05:07:58 +08:00
|
|
|
noCompression := archive.Uncompressed
|
|
|
|
diffOptions := &storage.DiffOptions{
|
|
|
|
Compression: &noCompression,
|
|
|
|
}
|
2018-05-22 05:02:50 +08:00
|
|
|
var rc io.ReadCloser
|
2021-03-12 16:15:41 +08:00
|
|
|
var errChan chan error
|
2023-07-18 04:27:19 +08:00
|
|
|
if i.confidentialWorkload.Convert {
|
|
|
|
// Convert the root filesystem into an encrypted disk image.
|
|
|
|
rc, err = i.extractConfidentialWorkloadFS(i.confidentialWorkload)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else if i.squash {
|
2018-05-22 05:02:50 +08:00
|
|
|
// Extract the root filesystem as a single layer.
|
2022-04-29 21:39:42 +08:00
|
|
|
rc, errChan, err = i.extractRootfs(ExtractRootfsOptions{})
|
2018-05-22 05:02:50 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
2023-12-13 03:54:42 +08:00
|
|
|
if layerID != synthesizedLayerID {
|
|
|
|
// If we're up to the final layer, but we don't want to
|
|
|
|
// include a diff for it, we're done.
|
|
|
|
if i.emptyLayer && layerID == i.layerID {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// Extract this layer, one of possibly many.
|
|
|
|
rc, err = i.store.Diff("", layerID, diffOptions)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("extracting %s: %w", what, err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Slip in additional content as an additional layer.
|
|
|
|
if rc, err = os.Open(extraImageContentDiff); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-05-22 05:02:50 +08:00
|
|
|
}
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2017-01-28 15:18:02 +08:00
|
|
|
srcHasher := digest.Canonical.Digester()
|
2017-06-01 01:44:41 +08:00
|
|
|
// Set up to write the possibly-recompressed blob.
|
2017-01-27 00:58:00 +08:00
|
|
|
layerFile, err := os.OpenFile(filepath.Join(path, "layer"), os.O_CREATE|os.O_WRONLY, 0600)
|
|
|
|
if err != nil {
|
2018-09-18 03:04:48 +08:00
|
|
|
rc.Close()
|
2022-09-18 18:36:08 +08:00
|
|
|
return nil, fmt.Errorf("opening file for %s: %w", what, err)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2021-08-28 05:11:44 +08:00
|
|
|
|
2017-01-28 15:18:02 +08:00
|
|
|
counter := ioutils.NewWriteCounter(layerFile)
|
2021-08-28 05:11:44 +08:00
|
|
|
var destHasher digest.Digester
|
|
|
|
var multiWriter io.Writer
|
|
|
|
// Avoid rehashing when we do not compress.
|
|
|
|
if i.compression != archive.Uncompressed {
|
|
|
|
destHasher = digest.Canonical.Digester()
|
|
|
|
multiWriter = io.MultiWriter(counter, destHasher.Hash())
|
|
|
|
} else {
|
|
|
|
destHasher = srcHasher
|
|
|
|
multiWriter = counter
|
|
|
|
}
|
2017-06-29 05:07:58 +08:00
|
|
|
// Compress the layer, if we're recompressing it.
|
2020-08-11 17:28:41 +08:00
|
|
|
writeCloser, err := archive.CompressStream(multiWriter, i.compression)
|
2017-01-28 15:18:02 +08:00
|
|
|
if err != nil {
|
2018-09-18 03:04:48 +08:00
|
|
|
layerFile.Close()
|
|
|
|
rc.Close()
|
2022-09-18 18:36:08 +08:00
|
|
|
return nil, fmt.Errorf("compressing %s: %w", what, err)
|
2017-01-28 15:18:02 +08:00
|
|
|
}
|
2020-08-11 17:28:41 +08:00
|
|
|
writer := io.MultiWriter(writeCloser, srcHasher.Hash())
|
2022-08-11 02:19:19 +08:00
|
|
|
// Scrub any local user names that might correspond to UIDs or GIDs of
|
|
|
|
// files in this layer.
|
|
|
|
{
|
|
|
|
nestedWriteCloser := ioutils.NewWriteCloserWrapper(writer, writeCloser.Close)
|
|
|
|
writeCloser = newTarFilterer(nestedWriteCloser, func(hdr *tar.Header) (bool, bool, io.Reader) {
|
|
|
|
hdr.Uname, hdr.Gname = "", ""
|
|
|
|
return false, false, nil
|
|
|
|
})
|
|
|
|
writer = io.Writer(writeCloser)
|
|
|
|
}
|
2020-08-27 04:56:57 +08:00
|
|
|
// Use specified timestamps in the layer, if we're doing that for
|
2020-08-11 17:28:41 +08:00
|
|
|
// history entries.
|
2020-08-27 04:56:57 +08:00
|
|
|
if i.created != nil {
|
2020-08-11 17:28:41 +08:00
|
|
|
nestedWriteCloser := ioutils.NewWriteCloserWrapper(writer, writeCloser.Close)
|
|
|
|
writeCloser = newTarFilterer(nestedWriteCloser, func(hdr *tar.Header) (bool, bool, io.Reader) {
|
|
|
|
// Changing a zeroed field to a non-zero field
|
|
|
|
// can affect the format that the library uses
|
|
|
|
// for writing the header, so only change
|
|
|
|
// fields that are already set to avoid
|
|
|
|
// changing the format (and as a result,
|
|
|
|
// changing the length) of the header that we
|
|
|
|
// write.
|
|
|
|
if !hdr.ModTime.IsZero() {
|
2020-08-27 04:56:57 +08:00
|
|
|
hdr.ModTime = *i.created
|
2020-08-11 17:28:41 +08:00
|
|
|
}
|
|
|
|
if !hdr.AccessTime.IsZero() {
|
2020-08-27 04:56:57 +08:00
|
|
|
hdr.AccessTime = *i.created
|
2020-08-11 17:28:41 +08:00
|
|
|
}
|
|
|
|
if !hdr.ChangeTime.IsZero() {
|
2020-08-27 04:56:57 +08:00
|
|
|
hdr.ChangeTime = *i.created
|
2020-08-11 17:28:41 +08:00
|
|
|
}
|
|
|
|
return false, false, nil
|
|
|
|
})
|
|
|
|
writer = io.Writer(writeCloser)
|
|
|
|
}
|
|
|
|
size, err := io.Copy(writer, rc)
|
|
|
|
writeCloser.Close()
|
2018-09-18 03:04:48 +08:00
|
|
|
layerFile.Close()
|
|
|
|
rc.Close()
|
2021-03-12 16:15:41 +08:00
|
|
|
|
|
|
|
if errChan != nil {
|
|
|
|
err = <-errChan
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-27 00:58:00 +08:00
|
|
|
if err != nil {
|
2022-09-18 18:36:08 +08:00
|
|
|
return nil, fmt.Errorf("storing %s to file: %w", what, err)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2017-01-28 15:18:02 +08:00
|
|
|
if i.compression == archive.Uncompressed {
|
|
|
|
if size != counter.Count {
|
2022-09-18 18:36:08 +08:00
|
|
|
return nil, fmt.Errorf("storing %s to file: inconsistent layer size (copied %d, wrote %d)", what, size, counter.Count)
|
2017-01-28 15:18:02 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
size = counter.Count
|
|
|
|
}
|
2019-07-25 22:10:03 +08:00
|
|
|
logrus.Debugf("%s size is %d bytes, uncompressed digest %s, possibly-compressed digest %s", what, size, srcHasher.Digest().String(), destHasher.Digest().String())
|
2017-06-01 01:44:41 +08:00
|
|
|
// Rename the layer so that we can more easily find it by digest later.
|
2018-10-18 06:06:16 +08:00
|
|
|
finalBlobName := filepath.Join(path, destHasher.Digest().String())
|
|
|
|
if err = os.Rename(filepath.Join(path, "layer"), finalBlobName); err != nil {
|
2022-09-18 18:36:08 +08:00
|
|
|
return nil, fmt.Errorf("storing %s to file while renaming %q to %q: %w", what, filepath.Join(path, "layer"), finalBlobName, err)
|
2017-03-07 23:41:25 +08:00
|
|
|
}
|
2017-06-01 01:44:41 +08:00
|
|
|
// Add a note in the manifest about the layer. The blobs are identified by their possibly-
|
|
|
|
// compressed blob digests.
|
2017-05-18 05:01:06 +08:00
|
|
|
olayerDescriptor := v1.Descriptor{
|
|
|
|
MediaType: omediaType,
|
|
|
|
Digest: destHasher.Digest(),
|
|
|
|
Size: size,
|
|
|
|
}
|
|
|
|
omanifest.Layers = append(omanifest.Layers, olayerDescriptor)
|
|
|
|
dlayerDescriptor := docker.V2S2Descriptor{
|
|
|
|
MediaType: dmediaType,
|
2017-02-14 00:44:47 +08:00
|
|
|
Digest: destHasher.Digest(),
|
2017-01-27 00:58:00 +08:00
|
|
|
Size: size,
|
|
|
|
}
|
2017-05-18 05:01:06 +08:00
|
|
|
dmanifest.Layers = append(dmanifest.Layers, dlayerDescriptor)
|
2017-06-29 05:07:58 +08:00
|
|
|
// Add a note about the diffID, which is always the layer's uncompressed digest.
|
2017-06-28 23:40:28 +08:00
|
|
|
oimage.RootFS.DiffIDs = append(oimage.RootFS.DiffIDs, srcHasher.Digest())
|
2017-05-18 05:01:06 +08:00
|
|
|
dimage.RootFS.DiffIDs = append(dimage.RootFS.DiffIDs, srcHasher.Digest())
|
2017-01-27 22:38:32 +08:00
|
|
|
}
|
|
|
|
|
2017-06-29 05:07:58 +08:00
|
|
|
// Build history notes in the image configurations.
|
2019-01-19 04:39:58 +08:00
|
|
|
appendHistory := func(history []v1.History) {
|
|
|
|
for i := range history {
|
|
|
|
var created *time.Time
|
|
|
|
if history[i].Created != nil {
|
|
|
|
copiedTimestamp := *history[i].Created
|
|
|
|
created = &copiedTimestamp
|
|
|
|
}
|
|
|
|
onews := v1.History{
|
|
|
|
Created: created,
|
|
|
|
CreatedBy: history[i].CreatedBy,
|
|
|
|
Author: history[i].Author,
|
|
|
|
Comment: history[i].Comment,
|
|
|
|
EmptyLayer: true,
|
|
|
|
}
|
|
|
|
oimage.History = append(oimage.History, onews)
|
|
|
|
if created == nil {
|
|
|
|
created = &time.Time{}
|
|
|
|
}
|
|
|
|
dnews := docker.V2S2History{
|
|
|
|
Created: *created,
|
|
|
|
CreatedBy: history[i].CreatedBy,
|
|
|
|
Author: history[i].Author,
|
|
|
|
Comment: history[i].Comment,
|
|
|
|
EmptyLayer: true,
|
|
|
|
}
|
|
|
|
dimage.History = append(dimage.History, dnews)
|
|
|
|
}
|
|
|
|
}
|
2022-06-01 18:00:56 +08:00
|
|
|
|
2022-06-01 15:59:52 +08:00
|
|
|
// Only attempt to append history if history was not disabled explicitly.
|
|
|
|
if !i.omitHistory {
|
2023-12-13 03:54:42 +08:00
|
|
|
// Keep track of how many entries the base image's history had
|
|
|
|
// before we started adding to it.
|
|
|
|
baseImageHistoryLen := len(oimage.History)
|
2022-06-01 15:59:52 +08:00
|
|
|
appendHistory(i.preEmptyLayers)
|
|
|
|
created := time.Now().UTC()
|
|
|
|
if i.created != nil {
|
|
|
|
created = (*i.created).UTC()
|
|
|
|
}
|
|
|
|
comment := i.historyComment
|
2023-12-13 03:54:42 +08:00
|
|
|
// Add a comment indicating which base image was used, if it wasn't
|
|
|
|
// just an image ID.
|
2022-06-01 15:59:52 +08:00
|
|
|
if strings.Contains(i.parent, i.fromImageID) && i.fromImageName != i.fromImageID {
|
|
|
|
comment += "FROM " + i.fromImageName
|
|
|
|
}
|
|
|
|
onews := v1.History{
|
|
|
|
Created: &created,
|
|
|
|
CreatedBy: i.createdBy,
|
|
|
|
Author: oimage.Author,
|
|
|
|
Comment: comment,
|
|
|
|
EmptyLayer: i.emptyLayer,
|
|
|
|
}
|
|
|
|
oimage.History = append(oimage.History, onews)
|
|
|
|
dnews := docker.V2S2History{
|
|
|
|
Created: created,
|
|
|
|
CreatedBy: i.createdBy,
|
|
|
|
Author: dimage.Author,
|
|
|
|
Comment: comment,
|
|
|
|
EmptyLayer: i.emptyLayer,
|
|
|
|
}
|
|
|
|
dimage.History = append(dimage.History, dnews)
|
|
|
|
appendHistory(i.postEmptyLayers)
|
|
|
|
|
2023-12-13 03:54:42 +08:00
|
|
|
// Add a history entry for the extra image content if we added a layer for it.
|
|
|
|
if extraImageContentDiff != "" {
|
|
|
|
createdBy := fmt.Sprintf(`/bin/sh -c #(nop) ADD dir:%s in /",`, extraImageContentDiffDigest.Encoded())
|
|
|
|
onews := v1.History{
|
|
|
|
Created: &created,
|
|
|
|
CreatedBy: createdBy,
|
|
|
|
}
|
|
|
|
oimage.History = append(oimage.History, onews)
|
|
|
|
dnews := docker.V2S2History{
|
|
|
|
Created: created,
|
|
|
|
CreatedBy: createdBy,
|
|
|
|
}
|
|
|
|
dimage.History = append(dimage.History, dnews)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Confidence check that we didn't just create a mismatch between non-empty layers in the
|
|
|
|
// history and the number of diffIDs. Only applicable if the base image (if there was
|
|
|
|
// one) provided us at least one entry to use as a starting point.
|
2022-06-01 18:00:56 +08:00
|
|
|
if baseImageHistoryLen != 0 {
|
|
|
|
expectedDiffIDs := expectedOCIDiffIDs(oimage)
|
|
|
|
if len(oimage.RootFS.DiffIDs) != expectedDiffIDs {
|
2022-07-06 17:14:06 +08:00
|
|
|
return nil, fmt.Errorf("internal error: history lists %d non-empty layers, but we have %d layers on disk", expectedDiffIDs, len(oimage.RootFS.DiffIDs))
|
2022-06-01 18:00:56 +08:00
|
|
|
}
|
|
|
|
expectedDiffIDs = expectedDockerDiffIDs(dimage)
|
|
|
|
if len(dimage.RootFS.DiffIDs) != expectedDiffIDs {
|
2022-07-06 17:14:06 +08:00
|
|
|
return nil, fmt.Errorf("internal error: history lists %d non-empty layers, but we have %d layers on disk", expectedDiffIDs, len(dimage.RootFS.DiffIDs))
|
2022-06-01 18:00:56 +08:00
|
|
|
}
|
2022-06-01 15:59:52 +08:00
|
|
|
}
|
2018-01-05 07:05:40 +08:00
|
|
|
}
|
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Encode the image configuration blob.
|
2017-05-18 05:01:06 +08:00
|
|
|
oconfig, err := json.Marshal(&oimage)
|
2017-01-27 22:38:32 +08:00
|
|
|
if err != nil {
|
2022-09-18 18:36:08 +08:00
|
|
|
return nil, fmt.Errorf("encoding %#v as json: %w", oimage, err)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2017-05-18 05:01:06 +08:00
|
|
|
logrus.Debugf("OCIv1 config = %s", oconfig)
|
2017-01-27 22:38:32 +08:00
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Add the configuration blob to the manifest.
|
|
|
|
omanifest.Config.Digest = digest.Canonical.FromBytes(oconfig)
|
2017-05-18 05:01:06 +08:00
|
|
|
omanifest.Config.Size = int64(len(oconfig))
|
|
|
|
omanifest.Config.MediaType = v1.MediaTypeImageConfig
|
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Encode the manifest.
|
2017-05-18 05:01:06 +08:00
|
|
|
omanifestbytes, err := json.Marshal(&omanifest)
|
|
|
|
if err != nil {
|
2022-09-18 18:36:08 +08:00
|
|
|
return nil, fmt.Errorf("encoding %#v as json: %w", omanifest, err)
|
2017-05-18 05:01:06 +08:00
|
|
|
}
|
|
|
|
logrus.Debugf("OCIv1 manifest = %s", omanifestbytes)
|
2017-01-27 00:58:00 +08:00
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Encode the image configuration blob.
|
2017-05-18 05:01:06 +08:00
|
|
|
dconfig, err := json.Marshal(&dimage)
|
2017-01-27 00:58:00 +08:00
|
|
|
if err != nil {
|
2022-09-18 18:36:08 +08:00
|
|
|
return nil, fmt.Errorf("encoding %#v as json: %w", dimage, err)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2017-05-18 05:01:06 +08:00
|
|
|
logrus.Debugf("Docker v2s2 config = %s", dconfig)
|
2017-01-27 00:58:00 +08:00
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Add the configuration blob to the manifest.
|
|
|
|
dmanifest.Config.Digest = digest.Canonical.FromBytes(dconfig)
|
2017-05-18 05:01:06 +08:00
|
|
|
dmanifest.Config.Size = int64(len(dconfig))
|
2018-07-19 07:49:56 +08:00
|
|
|
dmanifest.Config.MediaType = manifest.DockerV2Schema2ConfigMediaType
|
2017-05-18 05:01:06 +08:00
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Encode the manifest.
|
2017-05-18 05:01:06 +08:00
|
|
|
dmanifestbytes, err := json.Marshal(&dmanifest)
|
|
|
|
if err != nil {
|
2022-09-18 18:36:08 +08:00
|
|
|
return nil, fmt.Errorf("encoding %#v as json: %w", dmanifest, err)
|
2017-05-18 05:01:06 +08:00
|
|
|
}
|
|
|
|
logrus.Debugf("Docker v2s2 manifest = %s", dmanifestbytes)
|
|
|
|
|
2017-06-01 01:44:41 +08:00
|
|
|
// Decide which manifest and configuration blobs we'll actually output.
|
2017-05-18 05:01:06 +08:00
|
|
|
var config []byte
|
2018-07-19 07:49:56 +08:00
|
|
|
var imageManifest []byte
|
2017-05-18 05:01:06 +08:00
|
|
|
switch manifestType {
|
|
|
|
case v1.MediaTypeImageManifest:
|
2018-07-19 07:49:56 +08:00
|
|
|
imageManifest = omanifestbytes
|
2017-06-01 01:44:41 +08:00
|
|
|
config = oconfig
|
2018-07-19 07:49:56 +08:00
|
|
|
case manifest.DockerV2Schema2MediaType:
|
|
|
|
imageManifest = dmanifestbytes
|
2017-06-01 01:44:41 +08:00
|
|
|
config = dconfig
|
2017-05-18 05:01:06 +08:00
|
|
|
default:
|
|
|
|
panic("unreachable code: unsupported manifest type")
|
|
|
|
}
|
2017-01-27 00:58:00 +08:00
|
|
|
src = &containerImageSource{
|
2018-10-18 06:06:16 +08:00
|
|
|
path: path,
|
|
|
|
ref: i,
|
|
|
|
store: i.store,
|
|
|
|
containerID: i.containerID,
|
|
|
|
mountLabel: i.mountLabel,
|
|
|
|
layerID: i.layerID,
|
|
|
|
names: i.names,
|
|
|
|
compression: i.compression,
|
|
|
|
config: config,
|
|
|
|
configDigest: digest.Canonical.FromBytes(config),
|
|
|
|
manifest: imageManifest,
|
|
|
|
manifestType: manifestType,
|
|
|
|
blobDirectory: i.blobDirectory,
|
2021-05-26 04:34:36 +08:00
|
|
|
blobLayers: blobLayers,
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
return src, nil
|
|
|
|
}
|
|
|
|
|
2018-04-12 22:20:36 +08:00
|
|
|
func (i *containerImageRef) NewImageDestination(ctx context.Context, sc *types.SystemContext) (types.ImageDestination, error) {
|
2022-07-06 17:14:06 +08:00
|
|
|
return nil, errors.New("can't write to a container")
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) DockerReference() reference.Named {
|
|
|
|
return i.name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) StringWithinTransport() string {
|
2017-06-02 00:09:23 +08:00
|
|
|
if len(i.names) > 0 {
|
|
|
|
return i.names[0]
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2018-04-12 22:20:36 +08:00
|
|
|
func (i *containerImageRef) DeleteImage(context.Context, *types.SystemContext) error {
|
2017-01-27 00:58:00 +08:00
|
|
|
// we were never here
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) PolicyConfigurationIdentity() string {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) PolicyConfigurationNamespaces() []string {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageRef) Transport() types.ImageTransport {
|
|
|
|
return is.Transport
|
|
|
|
}
|
|
|
|
|
2017-03-22 04:38:50 +08:00
|
|
|
func (i *containerImageSource) Close() error {
|
2017-01-27 00:58:00 +08:00
|
|
|
err := os.RemoveAll(i.path)
|
|
|
|
if err != nil {
|
2022-09-18 18:36:08 +08:00
|
|
|
return fmt.Errorf("removing layer blob directory: %w", err)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2018-10-03 22:05:46 +08:00
|
|
|
return nil
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func (i *containerImageSource) Reference() types.ImageReference {
|
|
|
|
return i.ref
|
|
|
|
}
|
|
|
|
|
2017-06-29 05:07:58 +08:00
|
|
|
func (i *containerImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) {
|
2017-01-27 00:58:00 +08:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2018-04-12 22:20:36 +08:00
|
|
|
func (i *containerImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) {
|
2017-06-29 05:07:58 +08:00
|
|
|
return i.manifest, i.manifestType, nil
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
|
2019-08-30 23:47:45 +08:00
|
|
|
func (i *containerImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) {
|
2018-02-23 01:12:59 +08:00
|
|
|
return nil, nil
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
|
2018-12-19 18:20:31 +08:00
|
|
|
func (i *containerImageSource) HasThreadSafeGetBlob() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2018-11-23 18:40:49 +08:00
|
|
|
func (i *containerImageSource) GetBlob(ctx context.Context, blob types.BlobInfo, cache types.BlobInfoCache) (reader io.ReadCloser, size int64, err error) {
|
2017-01-27 00:58:00 +08:00
|
|
|
if blob.Digest == i.configDigest {
|
|
|
|
logrus.Debugf("start reading config")
|
|
|
|
reader := bytes.NewReader(i.config)
|
|
|
|
closer := func() error {
|
|
|
|
logrus.Debugf("finished reading config")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return ioutils.NewReadCloserWrapper(reader, closer), reader.Size(), nil
|
|
|
|
}
|
2021-05-26 04:34:36 +08:00
|
|
|
var layerReadCloser io.ReadCloser
|
|
|
|
size = -1
|
|
|
|
if blobLayerInfo, ok := i.blobLayers[blob.Digest]; ok {
|
|
|
|
noCompression := archive.Uncompressed
|
|
|
|
diffOptions := &storage.DiffOptions{
|
|
|
|
Compression: &noCompression,
|
2018-10-18 06:06:16 +08:00
|
|
|
}
|
2021-05-26 04:34:36 +08:00
|
|
|
layerReadCloser, err = i.store.Diff("", blobLayerInfo.ID, diffOptions)
|
|
|
|
size = blobLayerInfo.Size
|
|
|
|
} else {
|
|
|
|
for _, blobDir := range []string{i.blobDirectory, i.path} {
|
|
|
|
var layerFile *os.File
|
|
|
|
layerFile, err = os.OpenFile(filepath.Join(blobDir, blob.Digest.String()), os.O_RDONLY, 0600)
|
|
|
|
if err == nil {
|
|
|
|
st, err := layerFile.Stat()
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warnf("error reading size of layer file %q: %v", blob.Digest.String(), err)
|
|
|
|
} else {
|
|
|
|
size = st.Size()
|
|
|
|
layerReadCloser = layerFile
|
|
|
|
break
|
|
|
|
}
|
|
|
|
layerFile.Close()
|
|
|
|
}
|
2022-07-27 03:27:30 +08:00
|
|
|
if !errors.Is(err, os.ErrNotExist) {
|
2021-05-26 04:34:36 +08:00
|
|
|
logrus.Debugf("error checking for layer %q in %q: %v", blob.Digest.String(), blobDir, err)
|
|
|
|
}
|
2018-10-18 06:06:16 +08:00
|
|
|
}
|
|
|
|
}
|
2021-05-26 04:34:36 +08:00
|
|
|
if err != nil || layerReadCloser == nil || size == -1 {
|
2017-01-27 00:58:00 +08:00
|
|
|
logrus.Debugf("error reading layer %q: %v", blob.Digest.String(), err)
|
2022-09-18 18:36:08 +08:00
|
|
|
return nil, -1, fmt.Errorf("opening layer blob: %w", err)
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
logrus.Debugf("reading layer %q", blob.Digest.String())
|
2017-01-27 19:28:41 +08:00
|
|
|
closer := func() error {
|
|
|
|
logrus.Debugf("finished reading layer %q", blob.Digest.String())
|
2021-05-26 04:34:36 +08:00
|
|
|
if err := layerReadCloser.Close(); err != nil {
|
2022-09-18 18:36:08 +08:00
|
|
|
return fmt.Errorf("closing layer %q after reading: %w", blob.Digest.String(), err)
|
2018-10-03 22:05:46 +08:00
|
|
|
}
|
2017-01-27 19:28:41 +08:00
|
|
|
return nil
|
|
|
|
}
|
2021-05-26 04:34:36 +08:00
|
|
|
return ioutils.NewReadCloserWrapper(layerReadCloser, closer), size, nil
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
|
|
|
|
2023-12-13 03:54:42 +08:00
|
|
|
// makeExtraImageContentDiff creates an archive file containing the contents of
|
|
|
|
// files named in i.extraImageContent. The footer that marks the end of the
|
|
|
|
// archive may be omitted.
|
|
|
|
func (i *containerImageRef) makeExtraImageContentDiff(includeFooter bool) (string, digest.Digest, int64, error) {
|
|
|
|
cdir, err := i.store.ContainerDirectory(i.containerID)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", -1, err
|
|
|
|
}
|
|
|
|
diff, err := os.CreateTemp(cdir, "extradiff")
|
|
|
|
if err != nil {
|
|
|
|
return "", "", -1, err
|
|
|
|
}
|
|
|
|
defer diff.Close()
|
|
|
|
digester := digest.Canonical.Digester()
|
|
|
|
counter := ioutils.NewWriteCounter(digester.Hash())
|
|
|
|
tw := tar.NewWriter(io.MultiWriter(diff, counter))
|
|
|
|
created := time.Now()
|
|
|
|
if i.created != nil {
|
|
|
|
created = *i.created
|
|
|
|
}
|
|
|
|
for path, contents := range i.extraImageContent {
|
|
|
|
if err := func() error {
|
|
|
|
content, err := os.Open(contents)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer content.Close()
|
|
|
|
st, err := content.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := tw.WriteHeader(&tar.Header{
|
|
|
|
Name: path,
|
|
|
|
Typeflag: tar.TypeReg,
|
|
|
|
Mode: 0o644,
|
|
|
|
ModTime: created,
|
|
|
|
Size: st.Size(),
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if _, err := io.Copy(tw, content); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := tw.Flush(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}(); err != nil {
|
|
|
|
return "", "", -1, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !includeFooter {
|
|
|
|
return diff.Name(), "", -1, err
|
|
|
|
}
|
|
|
|
tw.Close()
|
|
|
|
return diff.Name(), digester.Digest(), counter.Count, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// makeContainerImageRef creates a containers/image/v5/types.ImageReference
|
|
|
|
// which is mainly used for representing the working container as a source
|
|
|
|
// image that can be copied, which is how we commit container to create the
|
|
|
|
// image.
|
2022-04-29 21:39:42 +08:00
|
|
|
func (b *Builder) makeContainerImageRef(options CommitOptions) (*containerImageRef, error) {
|
2017-01-27 00:58:00 +08:00
|
|
|
var name reference.Named
|
2017-06-29 05:07:58 +08:00
|
|
|
container, err := b.store.Container(b.ContainerID)
|
|
|
|
if err != nil {
|
2022-09-18 18:36:08 +08:00
|
|
|
return nil, fmt.Errorf("locating container %q: %w", b.ContainerID, err)
|
2017-06-29 05:07:58 +08:00
|
|
|
}
|
|
|
|
if len(container.Names) > 0 {
|
|
|
|
if parsed, err2 := reference.ParseNamed(container.Names[0]); err2 == nil {
|
2017-06-02 00:09:23 +08:00
|
|
|
name = parsed
|
|
|
|
}
|
|
|
|
}
|
2019-04-24 21:12:01 +08:00
|
|
|
manifestType := options.PreferredManifestType
|
2017-05-18 05:02:40 +08:00
|
|
|
if manifestType == "" {
|
2021-03-02 02:07:58 +08:00
|
|
|
manifestType = define.OCIv1ImageManifest
|
2017-05-18 05:02:40 +08:00
|
|
|
}
|
2021-11-19 05:26:32 +08:00
|
|
|
|
2021-11-02 04:52:48 +08:00
|
|
|
for _, u := range options.UnsetEnvs {
|
2021-11-19 05:26:32 +08:00
|
|
|
b.UnsetEnv(u)
|
2021-11-02 04:52:48 +08:00
|
|
|
}
|
2021-11-19 05:26:32 +08:00
|
|
|
oconfig, err := json.Marshal(&b.OCIv1)
|
2017-05-18 05:01:06 +08:00
|
|
|
if err != nil {
|
2022-09-18 18:36:08 +08:00
|
|
|
return nil, fmt.Errorf("encoding OCI-format image configuration %#v: %w", b.OCIv1, err)
|
2017-05-18 05:01:06 +08:00
|
|
|
}
|
2021-11-19 05:26:32 +08:00
|
|
|
dconfig, err := json.Marshal(&b.Docker)
|
Maintain multiple working container configs
Maintain the container configuration in multiple formats in the Buildah
object, initializing one based on the other, depending on which format
the source image used for its configuration.
Replace directly manipulated fields in the Buildah object (Annotations,
CreatedBy, OS, Architecture, Maintainer, User, Workdir, Env, Cmd,
Entrypoint, Expose, Labels, and Volumes) with accessor functions which
update both configurations and which read from whichever one we consider
to be authoritative. Drop Args because we weren't using them.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #102
Approved by: rhatdan
2017-05-16 23:08:52 +08:00
|
|
|
if err != nil {
|
2022-09-18 18:36:08 +08:00
|
|
|
return nil, fmt.Errorf("encoding docker-format image configuration %#v: %w", b.Docker, err)
|
Maintain multiple working container configs
Maintain the container configuration in multiple formats in the Buildah
object, initializing one based on the other, depending on which format
the source image used for its configuration.
Replace directly manipulated fields in the Buildah object (Annotations,
CreatedBy, OS, Architecture, Maintainer, User, Workdir, Env, Cmd,
Entrypoint, Expose, Labels, and Volumes) with accessor functions which
update both configurations and which read from whichever one we consider
to be authoritative. Drop Args because we weren't using them.
Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
Closes: #102
Approved by: rhatdan
2017-05-16 23:08:52 +08:00
|
|
|
}
|
2020-09-01 05:09:10 +08:00
|
|
|
var created *time.Time
|
2019-04-24 21:12:01 +08:00
|
|
|
if options.HistoryTimestamp != nil {
|
2020-09-01 05:09:10 +08:00
|
|
|
historyTimestampUTC := options.HistoryTimestamp.UTC()
|
|
|
|
created = &historyTimestampUTC
|
2017-06-07 02:11:46 +08:00
|
|
|
}
|
2019-03-23 05:06:56 +08:00
|
|
|
createdBy := b.CreatedBy()
|
|
|
|
if createdBy == "" {
|
|
|
|
createdBy = strings.Join(b.Shell(), " ")
|
|
|
|
if createdBy == "" {
|
|
|
|
createdBy = "/bin/sh"
|
|
|
|
}
|
|
|
|
}
|
2018-06-09 00:55:46 +08:00
|
|
|
|
2019-04-15 22:02:05 +08:00
|
|
|
parent := ""
|
|
|
|
if b.FromImageID != "" {
|
|
|
|
parentDigest := digest.NewDigestFromEncoded(digest.Canonical, b.FromImageID)
|
|
|
|
if parentDigest.Validate() == nil {
|
|
|
|
parent = parentDigest.String()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-11 00:48:15 +08:00
|
|
|
ref := &containerImageRef{
|
2020-08-08 01:11:31 +08:00
|
|
|
fromImageName: b.FromImage,
|
|
|
|
fromImageID: b.FromImageID,
|
2017-05-18 05:02:40 +08:00
|
|
|
store: b.store,
|
2019-04-24 21:12:01 +08:00
|
|
|
compression: options.Compression,
|
2017-05-18 05:02:40 +08:00
|
|
|
name: name,
|
2017-06-29 05:07:58 +08:00
|
|
|
names: container.Names,
|
2018-05-22 05:02:50 +08:00
|
|
|
containerID: container.ID,
|
|
|
|
mountLabel: b.MountLabel,
|
2017-06-29 05:07:58 +08:00
|
|
|
layerID: container.LayerID,
|
2017-05-18 05:02:40 +08:00
|
|
|
oconfig: oconfig,
|
|
|
|
dconfig: dconfig,
|
2020-09-01 05:09:10 +08:00
|
|
|
created: created,
|
2019-03-23 05:06:56 +08:00
|
|
|
createdBy: createdBy,
|
2018-04-27 22:59:03 +08:00
|
|
|
historyComment: b.HistoryComment(),
|
2017-05-18 05:02:40 +08:00
|
|
|
annotations: b.Annotations(),
|
|
|
|
preferredManifestType: manifestType,
|
2019-04-24 21:12:01 +08:00
|
|
|
squash: options.Squash,
|
2023-07-18 04:27:19 +08:00
|
|
|
confidentialWorkload: options.ConfidentialWorkloadOptions,
|
2022-06-01 15:59:52 +08:00
|
|
|
omitHistory: options.OmitHistory,
|
2023-11-14 06:10:05 +08:00
|
|
|
emptyLayer: options.EmptyLayer && !options.Squash && !options.ConfidentialWorkloadOptions.Convert,
|
2019-07-25 22:10:03 +08:00
|
|
|
idMappingOptions: &b.IDMappingOptions,
|
2018-06-09 00:55:46 +08:00
|
|
|
parent: parent,
|
2019-04-24 21:12:01 +08:00
|
|
|
blobDirectory: options.BlobDirectory,
|
2019-01-19 04:39:58 +08:00
|
|
|
preEmptyLayers: b.PrependedEmptyLayers,
|
|
|
|
postEmptyLayers: b.AppendedEmptyLayers,
|
2023-11-01 22:18:40 +08:00
|
|
|
overrideChanges: options.OverrideChanges,
|
|
|
|
overrideConfig: options.OverrideConfig,
|
2023-12-13 03:54:42 +08:00
|
|
|
extraImageContent: copyStringStringMap(options.ExtraImageContent),
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2017-02-11 00:48:15 +08:00
|
|
|
return ref, nil
|
2017-01-27 00:58:00 +08:00
|
|
|
}
|
2022-04-29 21:39:42 +08:00
|
|
|
|
|
|
|
// Extract the container's whole filesystem as if it were a single layer from current builder instance
|
|
|
|
func (b *Builder) ExtractRootfs(options CommitOptions, opts ExtractRootfsOptions) (io.ReadCloser, chan error, error) {
|
|
|
|
src, err := b.makeContainerImageRef(options)
|
|
|
|
if err != nil {
|
2022-09-18 18:36:08 +08:00
|
|
|
return nil, nil, fmt.Errorf("creating image reference for container %q to extract its contents: %w", b.ContainerID, err)
|
2022-04-29 21:39:42 +08:00
|
|
|
}
|
|
|
|
return src.extractRootfs(opts)
|
|
|
|
}
|