Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ expect - see [Known Issues](#known-issues).
- [Flag `--cleanup`](#flag---cleanup)
- [Flag `--compression`](#flag---compression)
- [Flag `--compression-level`](#flag---compression-level)
- [Flag `--image-format`](#flag---image-format)
- [Flag `--compressed-caching`](#flag---compressed-caching)
- [Flag `--context-sub-path`](#flag---context-sub-path)
- [Flag `--credential-helpers`](#flag---credential-helpers)
Expand Down Expand Up @@ -742,6 +743,10 @@ Use this flag to select the compression algorithm `[gzip, zstd]`. Defaults to `g

Use this flag to select the compression level. Defaults to `-1` (no compression)

#### Flag `--image-format`

Use this flag to select the output image media type `[docker, oci]`. `docker` writes a Docker schema2 manifest, `oci` writes an OCI image manifest. When unset, kaniko inherits the format of the base image.

#### Flag `--compressed-caching`

Set this to false in order to prevent tar compression for cached layers. This
Expand Down
10 changes: 10 additions & 0 deletions cmd/executor/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,15 @@ var RootCmd = &cobra.Command{
if !opts.NoPush && len(opts.Destinations) == 0 {
return errors.New("you must provide --destination, or use --no-push")
}
if opts.ImageFormat == config.ImageFormatDocker && opts.Compression == config.ZStd {
return errors.New("--compression=zstd cannot be used with --image-format=docker, the Docker schema2 format has no zstd layer media type, use --image-format=oci")
}
if opts.TarPath != "" && opts.ImageFormat == config.ImageFormatOCI {
return errors.New("--tar-path writes a Docker schema2 tarball and cannot be used with --image-format=oci, use --oci-layout-path to write an OCI image")
}
if opts.TarPath != "" && opts.Compression == config.ZStd {
return errors.New("--compression=zstd cannot be used with --tar-path, the Docker schema2 tarball has no zstd layer media type, use --oci-layout-path for zstd layers")
}
if err := cacheFlagsValid(); err != nil {
return fmt.Errorf("cache flags invalid: %w", err)
}
Expand Down Expand Up @@ -284,6 +293,7 @@ func AddKanikoOptionsFlags(cmd *cobra.Command, opts *config.KanikoOptions) {
cmd.Flags().StringVarP(&opts.ImageNameTagDigestFile, "image-name-tag-with-digest-file", "", "", "Specify a file to save the image name w/ image tag w/ digest of the built image to.")
cmd.Flags().StringVarP(&opts.OCILayoutPath, "oci-layout-path", "", "", "Path to save the OCI image layout of the built image.")
cmd.Flags().VarP(&opts.Compression, "compression", "", "Compression algorithm (gzip, zstd)")
cmd.Flags().VarP(&opts.ImageFormat, "image-format", "", "Output image media type (docker, oci). Defaults to inheriting the format of the base image.")
cmd.Flags().IntVarP(&opts.CompressionLevel, "compression-level", "", -1, "Compression level")
cmd.Flags().BoolVarP(&opts.Cache, "cache", "", false, "Use cache when building image")
cmd.Flags().BoolVarP(&opts.CompressedCaching, "compressed-caching", "", true, "Compress the cached layers. Decreases build time, but increases memory usage.")
Expand Down
4 changes: 4 additions & 0 deletions integration/dockerfiles/Dockerfile_test_issue_mz849
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# mz849: --image-format=docker forces Docker schema2 output
# and overrides the OCI scratch base.
FROM scratch
COPY context/foo /foo
2 changes: 2 additions & 0 deletions integration/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ var additionalDockerFlagsMap = map[string][]string{
"Dockerfile_test_issue_mz661": {"--secret=id=kaniko,src=context/foo"},
// provenance forces ociv1 on buildkit but for these images we emit dockerv2 in kaniko
"Dockerfile_test_cross_compile": {"--platform=linux/" + crossCompileArch},
"Dockerfile_test_issue_mz849": {"--provenance=false"},
"Dockerfile_test_mv_add": {"--provenance=false"},
"Dockerfile_test_snapshotter_ignorelist": {"--provenance=false"},
"Dockerfile_test_whitelist": {"--provenance=false"},
Expand Down Expand Up @@ -170,6 +171,7 @@ var executorImages = map[string]string{
// Arguments to build Dockerfiles with when building with kaniko
var additionalKanikoFlagsMap = map[string][]string{
"Dockerfile_test_issue_519": {"--target=final_stage,nosquash1,nosquash2"},
"Dockerfile_test_issue_mz849": {"--image-format=docker"},
"Dockerfile_test_multistage_args_issue_1911": {"--target=base-custom2,nosquash1,nosquash2,nosquash3"},
"Dockerfile_test_cmd": {"--target=final,nosquash"},
"Dockerfile_test_issue_mz247": {"--target=final,nosquash"},
Expand Down
29 changes: 29 additions & 0 deletions pkg/config/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ type KanikoOptions struct {
ImageNameTagDigestFile string
OCILayoutPath string
Compression Compression
ImageFormat ImageFormat
CompressionLevel int
ImageFSExtractRetry int
SingleSnapshot bool
Expand Down Expand Up @@ -182,6 +183,34 @@ func (c *Compression) Type() string {
return "compression"
}

// ImageFormat is the output image media type
// unset means inherit from the base image (legacy behaviour)
type ImageFormat string

const (
ImageFormatInherit ImageFormat = ""
ImageFormatDocker ImageFormat = "docker"
ImageFormatOCI ImageFormat = "oci"
)

func (f *ImageFormat) String() string {
return string(*f)
}

func (f *ImageFormat) Set(v string) error {
switch v {
case "docker", "oci":
*f = ImageFormat(v)
return nil
default:
return errors.New(`must be either "docker" or "oci"`)
}
}

func (f *ImageFormat) Type() string {
return "imageformat"
}

// WarmerOptions are options that are set by command line arguments to the cache warmer.
type WarmerOptions struct {
CacheOptions
Expand Down
17 changes: 17 additions & 0 deletions pkg/executor/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ func newStageBuilder(sourceImage v1.Image, args *dockerfile.BuildArgs, opts *con
if !stage.Push {
_opts.Labels = []string{}
}
sourceImage, err := applyImageFormat(sourceImage, opts.ImageFormat)
if err != nil {
return nil, err
}
imageConfig, err := initializeConfig(sourceImage, &_opts)
if err != nil {
return nil, err
Expand Down Expand Up @@ -716,6 +720,8 @@ func saveSnapshotToLayer(tarPath string, imageMediaType types.MediaType, opts *c
} else {
layerOpts = append(layerOpts, tarball.WithMediaType(types.OCILayer))
}
} else if opts.Compression == config.ZStd {
logrus.Warn("ignoring --compression=zstd, the Docker schema2 output format has no zstd layer media type, use --image-format=oci for zstd layers")
}

layer, err := tarball.LayerFromFile(tarPath, layerOpts...)
Expand Down Expand Up @@ -746,6 +752,17 @@ func extractMediaTypeVendor(mt types.MediaType) string {
return types.DockerVendorPrefix
}

func applyImageFormat(image v1.Image, format config.ImageFormat) (v1.Image, error) {
switch format {
case config.ImageFormatOCI:
return image_util.WithMediaType(image, types.OCIManifestSchema1)
case config.ImageFormatDocker:
return image_util.WithMediaType(image, types.DockerManifestSchema2)
default:
return image, nil
}
}

// https://github.com/opencontainers/image-spec/blob/main/media-types.md#compatibility-matrix
func convertMediaType(mt types.MediaType) types.MediaType {
switch mt {
Expand Down
81 changes: 81 additions & 0 deletions pkg/image/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ package image

import (
"encoding/json"
"fmt"
"strings"

v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/partial"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/osscontainertools/kaniko/pkg/util"
)

Expand Down Expand Up @@ -132,3 +135,81 @@ func (n *noAnnotationsImage) RawManifest() ([]byte, error) {
func (n *noAnnotationsImage) Digest() (v1.Hash, error) {
return partial.Digest(n)
}

func WithMediaType(img v1.Image, manifestMT types.MediaType) (v1.Image, error) {
vendor := mediaTypeVendor(manifestMT)
configMT := types.DockerConfigJSON
if vendor == types.OCIVendorPrefix {
configMT = types.OCIConfigJSON
}
relabeled, err := relabelLayers(img, vendor)
if err != nil {
return nil, err
}
return mutate.ConfigMediaType(mutate.MediaType(relabeled, manifestMT), configMT), nil
}

func relabelLayers(img v1.Image, vendor string) (v1.Image, error) {
man, err := img.Manifest()
if err != nil {
return nil, err
}
relabeled := man.DeepCopy()
for i := range relabeled.Layers {
mt, err := relabelLayerMediaType(relabeled.Layers[i].MediaType, vendor)
if err != nil {
return nil, err
}
relabeled.Layers[i].MediaType = mt
}
return &layerRelabeledImage{Image: img, manifest: relabeled}, nil
}

type layerRelabeledImage struct {
v1.Image
manifest *v1.Manifest
}

func (i *layerRelabeledImage) Manifest() (*v1.Manifest, error) {
return i.manifest.DeepCopy(), nil
}

func (i *layerRelabeledImage) RawManifest() ([]byte, error) {
return json.Marshal(i.manifest)
}

func mediaTypeVendor(mt types.MediaType) string {
if strings.Contains(string(mt), types.OCIVendorPrefix) {
return types.OCIVendorPrefix
}
return types.DockerVendorPrefix
}

func relabelLayerMediaType(mt types.MediaType, vendor string) (types.MediaType, error) {
if mediaTypeVendor(mt) == vendor {
return mt, nil
}
switch vendor {
case types.OCIVendorPrefix:
switch mt {
case types.DockerLayer:
return types.OCILayer, nil
case types.DockerUncompressedLayer:
return types.OCIUncompressedLayer, nil
case types.DockerForeignLayer:
return types.OCIRestrictedLayer, nil
}
case types.DockerVendorPrefix:
switch mt {
case types.OCILayer:
return types.DockerLayer, nil
case types.OCIUncompressedLayer:
return types.DockerUncompressedLayer, nil
case types.OCIRestrictedLayer:
return types.DockerForeignLayer, nil
case types.OCILayerZStd:
return "", fmt.Errorf("cannot relabel zstd layer %q to docker schema2, which has no zstd media type, use --image-format=oci", mt)
}
}
return "", fmt.Errorf("cannot relabel layer media type %q to %s", mt, vendor)
}
Loading