kaniko/vendor/github.com/google/go-containerregistry/v1/tarball/write.go

135 lines
3.5 KiB
Go

// Copyright 2018 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tarball
import (
"archive/tar"
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"github.com/google/go-containerregistry/name"
"github.com/google/go-containerregistry/v1"
)
// WriteOptions are used to expose optional information to guide or
// control the image write.
type WriteOptions struct {
// TODO(mattmoor): Whether to store things compressed?
}
// WriteToFile writes in the compressed format to a tarball, on disk.
// This is just syntactic sugar wrapping tarball.Write with a new file.
func WriteToFile(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
w, err := os.Create(p)
if err != nil {
return err
}
defer w.Close()
return Write(tag, img, wo, w)
}
// Write the contents of the image to the provided reader, in the compressed format.
// The contents are written in the following format:
// One manifest.json file at the top level containing information about several images.
// One file for each layer, named after the layer's SHA.
// One file for the config blob, named after its SHA.
func Write(tag name.Tag, img v1.Image, wo *WriteOptions, w io.Writer) error {
tf := tar.NewWriter(w)
defer tf.Close()
// Write the config.
cfgName, err := img.ConfigName()
if err != nil {
return err
}
cfgBlob, err := img.RawConfigFile()
if err != nil {
return err
}
if err := writeTarEntry(tf, cfgName.String(), bytes.NewReader(cfgBlob), int64(len(cfgBlob))); err != nil {
return err
}
// Write the layers.
layers, err := img.Layers()
if err != nil {
return err
}
layerFiles := make([]string, len(layers))
for i, l := range layers {
d, err := l.Digest()
if err != nil {
return err
}
// Munge the file name to appease ancient technology.
//
// tar assumes anything with a colon is a remote tape drive:
// https://www.gnu.org/software/tar/manual/html_section/tar_45.html
// Drop the algorithm prefix, e.g. "sha256:"
hex := d.Hex
// gunzip expects certain file extensions:
// https://www.gnu.org/software/gzip/manual/html_node/Overview.html
layerFiles[i] = fmt.Sprintf("%s.tar.gz", hex)
r, err := l.Compressed()
if err != nil {
return err
}
blobSize, err := l.Size()
if err != nil {
return err
}
if err := writeTarEntry(tf, layerFiles[i], r, blobSize); err != nil {
return err
}
}
// Generate the tar descriptor and write it.
td := tarDescriptor{
singleImageTarDescriptor{
Config: cfgName.String(),
RepoTags: []string{tag.String()},
Layers: layerFiles,
},
}
tdBytes, err := json.Marshal(td)
if err != nil {
return err
}
return writeTarEntry(tf, "manifest.json", bytes.NewReader(tdBytes), int64(len(tdBytes)))
}
// write a file to the provided writer with a corresponding tar header
func writeTarEntry(tf *tar.Writer, path string, r io.Reader, size int64) error {
hdr := &tar.Header{
Mode: 0644,
Typeflag: tar.TypeReg,
Size: size,
Name: path,
}
if err := tf.WriteHeader(hdr); err != nil {
return err
}
_, err := io.Copy(tf, r)
return err
}