mirror of
https://github.com/containers/skopeo.git
synced 2026-02-01 14:58:59 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de0fb93f3d | ||
|
|
4419612150 | ||
|
|
5ececfad2c | ||
|
|
4f376bbb5e | ||
|
|
d03123204d | ||
|
|
0df1c44b12 | ||
|
|
75fbb8483e | ||
|
|
52e2737460 | ||
|
|
c83cd3fba9 | ||
|
|
d41ac23a03 | ||
|
|
dbebeb7235 | ||
|
|
9e129fd653 | ||
|
|
0a44c7f162 | ||
|
|
b12735358a | ||
|
|
318beaa720 | ||
|
|
f7dc659e52 |
24
README.md
24
README.md
@@ -71,6 +71,30 @@ Then to install Buildah on Fedora follow the steps in this example:
|
||||
buildah --help
|
||||
```
|
||||
|
||||
In RHEL 7, ensure that you are subscribed to `rhel-7-server-rpms`,
|
||||
`rhel-7-server-extras-rpms`, and `rhel-7-server-optional-rpms`, then
|
||||
run this command:
|
||||
|
||||
```
|
||||
yum -y install \
|
||||
make \
|
||||
golang \
|
||||
bats \
|
||||
btrfs-progs-devel \
|
||||
device-mapper-devel \
|
||||
glib2-devel \
|
||||
gpgme-devel \
|
||||
libassuan-devel \
|
||||
ostree-devel \
|
||||
git \
|
||||
bzip2 \
|
||||
go-md2man \
|
||||
runc \
|
||||
skopeo-containers
|
||||
```
|
||||
|
||||
The build steps for Buildah on RHEL are the same as Fedora, above.
|
||||
|
||||
In Ubuntu zesty and xenial, you can use this command:
|
||||
|
||||
```
|
||||
|
||||
@@ -20,7 +20,7 @@ const (
|
||||
// identify working containers.
|
||||
Package = "buildah"
|
||||
// Version for the Package
|
||||
Version = "0.5"
|
||||
Version = "0.6"
|
||||
// The value we use to identify what type of information, currently a
|
||||
// serialized Builder structure, we are using as per-container state.
|
||||
// This should only be changed when we make incompatible changes to
|
||||
|
||||
@@ -5,9 +5,11 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/manifest"
|
||||
"github.com/containers/image/transports"
|
||||
"github.com/containers/image/transports/alltransports"
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/projectatomic/buildah"
|
||||
"github.com/urfave/cli"
|
||||
@@ -29,6 +31,10 @@ var (
|
||||
Name: "disable-compression, D",
|
||||
Usage: "don't compress layers",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "format, f",
|
||||
Usage: "manifest type (oci, v2s1, or v2s2) to use when saving image using the 'dir:' transport (default is manifest type of source)",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "quiet, q",
|
||||
Usage: "don't output progress information when pushing images",
|
||||
@@ -104,8 +110,23 @@ func pushCmd(c *cli.Context) error {
|
||||
return errors.Wrapf(err, "error building system context")
|
||||
}
|
||||
|
||||
var manifestType string
|
||||
if c.IsSet("format") {
|
||||
switch c.String("format") {
|
||||
case "oci":
|
||||
manifestType = imgspecv1.MediaTypeImageManifest
|
||||
case "v2s1":
|
||||
manifestType = manifest.DockerV2Schema1SignedMediaType
|
||||
case "v2s2", "docker":
|
||||
manifestType = manifest.DockerV2Schema2MediaType
|
||||
default:
|
||||
return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", c.String("format"))
|
||||
}
|
||||
}
|
||||
|
||||
options := buildah.PushOptions{
|
||||
Compression: compress,
|
||||
ManifestType: manifestType,
|
||||
SignaturePolicyPath: c.String("signature-policy"),
|
||||
Store: store,
|
||||
SystemContext: systemContext,
|
||||
|
||||
@@ -79,10 +79,12 @@ func runCmd(c *cli.Context) error {
|
||||
Args: c.StringSlice("runtime-flag"),
|
||||
}
|
||||
|
||||
if c.Bool("tty") {
|
||||
options.Terminal = buildah.WithTerminal
|
||||
} else {
|
||||
options.Terminal = buildah.WithoutTerminal
|
||||
if c.IsSet("tty") {
|
||||
if c.Bool("tty") {
|
||||
options.Terminal = buildah.WithTerminal
|
||||
} else {
|
||||
options.Terminal = buildah.WithoutTerminal
|
||||
}
|
||||
}
|
||||
|
||||
for _, volumeSpec := range c.StringSlice("volume") {
|
||||
|
||||
@@ -80,6 +80,9 @@ type PushOptions struct {
|
||||
// github.com/containers/image/types SystemContext to hold credentials
|
||||
// and other authentication/authorization information.
|
||||
SystemContext *types.SystemContext
|
||||
// ManifestType is the format to use when saving the imge using the 'dir' transport
|
||||
// possible options are oci, v2s1, and v2s2
|
||||
ManifestType string
|
||||
}
|
||||
|
||||
// shallowCopy copies the most recent layer, the configuration, and the manifest from one image to another.
|
||||
@@ -256,7 +259,7 @@ func (b *Builder) Commit(dest types.ImageReference, options CommitOptions) error
|
||||
}
|
||||
if exporting {
|
||||
// Copy everything.
|
||||
err = cp.Image(policyContext, dest, src, getCopyOptions(options.ReportWriter, nil, options.SystemContext))
|
||||
err = cp.Image(policyContext, dest, src, getCopyOptions(options.ReportWriter, nil, options.SystemContext, ""))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error copying layers and metadata")
|
||||
}
|
||||
@@ -328,7 +331,7 @@ func Push(image string, dest types.ImageReference, options PushOptions) error {
|
||||
return errors.Wrapf(err, "error recomputing layer digests and building metadata")
|
||||
}
|
||||
// Copy everything.
|
||||
err = cp.Image(policyContext, dest, src, getCopyOptions(options.ReportWriter, nil, options.SystemContext))
|
||||
err = cp.Image(policyContext, dest, src, getCopyOptions(options.ReportWriter, nil, options.SystemContext, options.ManifestType))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error copying layers and metadata")
|
||||
}
|
||||
|
||||
@@ -7,11 +7,12 @@ import (
|
||||
"github.com/containers/image/types"
|
||||
)
|
||||
|
||||
func getCopyOptions(reportWriter io.Writer, sourceSystemContext *types.SystemContext, destinationSystemContext *types.SystemContext) *cp.Options {
|
||||
func getCopyOptions(reportWriter io.Writer, sourceSystemContext *types.SystemContext, destinationSystemContext *types.SystemContext, manifestType string) *cp.Options {
|
||||
return &cp.Options{
|
||||
ReportWriter: reportWriter,
|
||||
SourceCtx: sourceSystemContext,
|
||||
DestinationCtx: destinationSystemContext,
|
||||
ReportWriter: reportWriter,
|
||||
SourceCtx: sourceSystemContext,
|
||||
DestinationCtx: destinationSystemContext,
|
||||
ForceManifestMIMEType: manifestType,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -483,6 +483,8 @@ return 1
|
||||
local options_with_args="
|
||||
--cert-dir
|
||||
--creds
|
||||
--format
|
||||
-f
|
||||
--signature-policy
|
||||
"
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
%global shortcommit %(c=%{commit}; echo ${c:0:7})
|
||||
|
||||
Name: buildah
|
||||
Version: 0.5
|
||||
Version: 0.6
|
||||
Release: 1.git%{shortcommit}%{?dist}
|
||||
Summary: A command line tool used to creating OCI Images
|
||||
License: ASL 2.0
|
||||
@@ -44,6 +44,7 @@ BuildRequires: libassuan-devel
|
||||
BuildRequires: glib2-devel
|
||||
BuildRequires: ostree-devel
|
||||
Requires: runc >= 1.0.0-6
|
||||
Requires: container-selinux
|
||||
Requires: skopeo-containers
|
||||
Provides: %{repo} = %{version}-%{release}
|
||||
|
||||
@@ -87,6 +88,12 @@ make DESTDIR=%{buildroot} PREFIX=%{_prefix} install install.completions
|
||||
%{_datadir}/bash-completion/completions/*
|
||||
|
||||
%changelog
|
||||
* Wed Nov 15 2017 Dan Walsh <dwalsh@redhat.com> 0.6-1
|
||||
- Adds support for converting manifest types when using the dir transport
|
||||
- Rework how we do UID resolution in images
|
||||
- Bump github.com/vbatts/tar-split
|
||||
- Set option.terminal appropriately in run
|
||||
|
||||
* Tue Nov 07 2017 Dan Walsh <dwalsh@redhat.com> 0.5-1
|
||||
- Add secrets patch to buildah
|
||||
- Add proper SELinux labeling to buildah run
|
||||
|
||||
@@ -52,6 +52,10 @@ The username[:password] to use to authenticate with the registry if required.
|
||||
|
||||
Don't compress copies of filesystem layers which will be pushed.
|
||||
|
||||
**--format, -f**
|
||||
|
||||
Manifest Type (oci, v2s1, or v2s2) to use when saving image to directory using the 'dir:' transport (default is manifest type of source)
|
||||
|
||||
**--quiet**
|
||||
|
||||
When writing the output image, suppress progress output.
|
||||
|
||||
2
pull.go
2
pull.go
@@ -109,6 +109,6 @@ func pullImage(store storage.Store, options BuilderOptions, sc *types.SystemCont
|
||||
|
||||
logrus.Debugf("copying %q to %q", spec, name)
|
||||
|
||||
err = cp.Image(policyContext, destRef, srcRef, getCopyOptions(options.ReportWriter, options.SystemContext, nil))
|
||||
err = cp.Image(policyContext, destRef, srcRef, getCopyOptions(options.ReportWriter, options.SystemContext, nil, ""))
|
||||
return destRef, err
|
||||
}
|
||||
|
||||
@@ -110,5 +110,6 @@ load helpers
|
||||
buildah rmi $id
|
||||
done
|
||||
run buildah --debug=false images -q
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" == "" ]
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ load helpers
|
||||
buildah rm ${cid}
|
||||
buildah rmi $(buildah --debug=false images -q)
|
||||
run buildah --debug=false images -q
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
}
|
||||
|
||||
@@ -24,6 +25,7 @@ load helpers
|
||||
buildah rm ${cid}
|
||||
buildah rmi $(buildah --debug=false images -q)
|
||||
run buildah --debug=false images -q
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
|
||||
target=alpine-image
|
||||
@@ -37,6 +39,7 @@ load helpers
|
||||
buildah rm ${cid}
|
||||
buildah rmi $(buildah --debug=false images -q)
|
||||
run buildah --debug=false images -q
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
}
|
||||
|
||||
@@ -52,6 +55,7 @@ load helpers
|
||||
buildah rm ${cid}
|
||||
buildah rmi $(buildah --debug=false images -q)
|
||||
run buildah --debug=false images -q
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
|
||||
target=alpine-image
|
||||
@@ -65,6 +69,7 @@ load helpers
|
||||
buildah rm ${cid}
|
||||
buildah rmi $(buildah --debug=false images -q)
|
||||
run buildah --debug=false images -q
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
}
|
||||
|
||||
@@ -88,6 +93,7 @@ load helpers
|
||||
buildah rm ${cid}
|
||||
buildah rmi $(buildah --debug=false images -q)
|
||||
run buildah --debug=false images -q
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
}
|
||||
|
||||
@@ -100,6 +106,7 @@ load helpers
|
||||
buildah rm ${cid}
|
||||
buildah rmi $(buildah --debug=false images -q)
|
||||
run buildah --debug=false images -q
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
}
|
||||
|
||||
@@ -112,6 +119,7 @@ load helpers
|
||||
buildah rm ${cid}
|
||||
buildah rmi $(buildah --debug=false images -q)
|
||||
run buildah --debug=false images -q
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
}
|
||||
|
||||
@@ -124,6 +132,7 @@ load helpers
|
||||
buildah rm ${cid}
|
||||
buildah rmi $(buildah --debug=false images -q)
|
||||
run buildah --debug=false images -q
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
}
|
||||
|
||||
@@ -136,6 +145,7 @@ load helpers
|
||||
buildah rm ${cid}
|
||||
buildah rmi $(buildah --debug=false images -q)
|
||||
run buildah --debug=false images -q
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
}
|
||||
|
||||
@@ -155,6 +165,7 @@ load helpers
|
||||
buildah rm ${cid}
|
||||
buildah rmi $(buildah --debug=false images -q)
|
||||
run buildah --debug=false images -q
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
}
|
||||
|
||||
@@ -168,6 +179,7 @@ load helpers
|
||||
buildah --debug=false images -q
|
||||
buildah rmi $(buildah --debug=false images -q)
|
||||
run buildah --debug=false images -q
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
}
|
||||
|
||||
@@ -177,6 +189,7 @@ load helpers
|
||||
target3=so-many-scratch-images
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json -t ${target} -t ${target2} -t ${target3} ${TESTSDIR}/bud/from-scratch
|
||||
run buildah --debug=false images
|
||||
[ "$status" -eq 0 ]
|
||||
cid=$(buildah from ${target})
|
||||
buildah rm ${cid}
|
||||
cid=$(buildah from library/${target2})
|
||||
@@ -185,6 +198,7 @@ load helpers
|
||||
buildah rm ${cid}
|
||||
buildah rmi -f $(buildah --debug=false images -q)
|
||||
run buildah --debug=false images -q
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
}
|
||||
|
||||
@@ -200,10 +214,12 @@ load helpers
|
||||
run test -s $root/vol/subvol/subvolfile
|
||||
[ "$status" -ne 0 ]
|
||||
run stat -c %f $root/vol/subvol
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = 41ed ]
|
||||
buildah rm ${cid}
|
||||
buildah rmi $(buildah --debug=false images -q)
|
||||
run buildah --debug=false images -q
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
}
|
||||
|
||||
@@ -217,5 +233,6 @@ load helpers
|
||||
buildah rm ${cid}
|
||||
buildah rmi $(buildah --debug=false images -q)
|
||||
run buildah --debug=false images -q
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
}
|
||||
|
||||
@@ -18,3 +18,23 @@ load helpers
|
||||
buildah rm "$cid"
|
||||
done
|
||||
}
|
||||
|
||||
@test "push with manifest type conversion" {
|
||||
cid=$(buildah from --pull --signature-policy ${TESTSDIR}/policy.json alpine)
|
||||
run buildah push --signature-policy ${TESTSDIR}/policy.json --format oci alpine dir:my-dir
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
manifest=$(cat my-dir/manifest.json)
|
||||
run grep "application/vnd.oci.image.config.v1+json" <<< "$manifest"
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run buildah push --signature-policy ${TESTSDIR}/policy.json --format v2s2 alpine dir:my-dir
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
run grep "application/vnd.docker.distribution.manifest.v2+json" my-dir/manifest.json
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
buildah rm "$cid"
|
||||
buildah rmi alpine
|
||||
rm -rf my-dir
|
||||
}
|
||||
|
||||
@@ -74,26 +74,32 @@ load helpers
|
||||
buildah config $cid --entrypoint ""
|
||||
buildah config $cid --cmd pwd
|
||||
run buildah --debug=false run $cid
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = /tmp ]
|
||||
|
||||
buildah config $cid --entrypoint echo
|
||||
run buildah --debug=false run $cid
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = pwd ]
|
||||
|
||||
buildah config $cid --cmd ""
|
||||
run buildah --debug=false run $cid
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
|
||||
buildah config $cid --entrypoint ""
|
||||
run buildah --debug=false run $cid echo that-other-thing
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = that-other-thing ]
|
||||
|
||||
buildah config $cid --cmd echo
|
||||
run buildah --debug=false run $cid echo that-other-thing
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = that-other-thing ]
|
||||
|
||||
buildah config $cid --entrypoint echo
|
||||
run buildah --debug=false run $cid echo that-other-thing
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = that-other-thing ]
|
||||
|
||||
buildah rm $cid
|
||||
@@ -112,8 +118,10 @@ load helpers
|
||||
root=$(buildah mount $cid)
|
||||
|
||||
testuser=jimbo
|
||||
testbogususer=nosuchuser
|
||||
testgroup=jimbogroup
|
||||
testuid=$RANDOM
|
||||
testotheruid=$RANDOM
|
||||
testgid=$RANDOM
|
||||
testgroupid=$RANDOM
|
||||
echo "$testuser:x:$testuid:$testgid:Jimbo Jenkins:/home/$testuser:/bin/sh" >> $root/etc/passwd
|
||||
@@ -122,52 +130,99 @@ load helpers
|
||||
buildah config $cid -u ""
|
||||
buildah run -- $cid id
|
||||
run buildah --debug=false run -- $cid id -u
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = 0 ]
|
||||
run buildah --debug=false run -- $cid id -g
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = 0 ]
|
||||
|
||||
buildah config $cid -u ${testuser}
|
||||
buildah run -- $cid id
|
||||
run buildah --debug=false run -- $cid id -u
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = $testuid ]
|
||||
run buildah --debug=false run -- $cid id -g
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = $testgid ]
|
||||
|
||||
buildah config $cid -u ${testuid}
|
||||
buildah run -- $cid id
|
||||
run buildah --debug=false run -- $cid id -u
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = $testuid ]
|
||||
run buildah --debug=false run -- $cid id -g
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = $testgid ]
|
||||
|
||||
buildah config $cid -u ${testuser}:${testgroup}
|
||||
buildah run -- $cid id
|
||||
run buildah --debug=false run -- $cid id -u
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = $testuid ]
|
||||
run buildah --debug=false run -- $cid id -g
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = $testgroupid ]
|
||||
|
||||
buildah config $cid -u ${testuid}:${testgroup}
|
||||
buildah run -- $cid id
|
||||
run buildah --debug=false run -- $cid id -u
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = $testuid ]
|
||||
run buildah --debug=false run -- $cid id -g
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = $testgroupid ]
|
||||
|
||||
buildah config $cid -u ${testotheruid}:${testgroup}
|
||||
buildah run -- $cid id
|
||||
run buildah --debug=false run -- $cid id -u
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = $testotheruid ]
|
||||
run buildah --debug=false run -- $cid id -g
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = $testgroupid ]
|
||||
|
||||
buildah config $cid -u ${testotheruid}
|
||||
buildah run -- $cid id
|
||||
run buildah --debug=false run -- $cid id -u
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = $testotheruid ]
|
||||
run buildah --debug=false run -- $cid id -g
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = 0 ]
|
||||
|
||||
buildah config $cid -u ${testuser}:${testgroupid}
|
||||
buildah run -- $cid id
|
||||
run buildah --debug=false run -- $cid id -u
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = $testuid ]
|
||||
run buildah --debug=false run -- $cid id -g
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = $testgroupid ]
|
||||
|
||||
buildah config $cid -u ${testuid}:${testgroupid}
|
||||
buildah run -- $cid id
|
||||
run buildah --debug=false run -- $cid id -u
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = $testuid ]
|
||||
run buildah --debug=false run -- $cid id -g
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = $testgroupid ]
|
||||
|
||||
buildah config $cid -u ${testbogususer}
|
||||
run buildah --debug=false run -- $cid id -u
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" =~ "unknown user" ]]
|
||||
run buildah --debug=false run -- $cid id -g
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" =~ "unknown user" ]]
|
||||
|
||||
ln -vsf /etc/passwd $root/etc/passwd
|
||||
buildah config $cid -u ${testuser}:${testgroup}
|
||||
run buildah --debug=false run -- $cid id -u
|
||||
echo "$output"
|
||||
[ "$status" -ne 0 ]
|
||||
[[ "$output" =~ "unknown user" ]]
|
||||
|
||||
buildah unmount $cid
|
||||
buildah rm $cid
|
||||
}
|
||||
@@ -177,17 +232,14 @@ load helpers
|
||||
skip
|
||||
fi
|
||||
runc --version
|
||||
createrandom ${TESTDIR}/randomfile
|
||||
cid=$(buildah from --pull --signature-policy ${TESTSDIR}/policy.json fedora)
|
||||
root=$(buildah mount $cid)
|
||||
run buildah --debug=false run -- $cid dnf -y install hostname
|
||||
[ "$status" -eq 0 ]
|
||||
cid=$(buildah from --pull --signature-policy ${TESTSDIR}/policy.json alpine)
|
||||
run buildah --debug=false run $cid hostname
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" != "foobar" ]
|
||||
run buildah --debug=false run --hostname foobar $cid hostname
|
||||
echo "$output"
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "foobar" ]
|
||||
buildah unmount $cid
|
||||
buildah rm $cid
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@ set -e
|
||||
|
||||
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
|
||||
|
||||
# Default to using /var/tmp for test space, since it's more likely to support
|
||||
# labels than /tmp, which is often on tmpfs.
|
||||
export TMPDIR=${TMPDIR:-/var/tmp}
|
||||
|
||||
# Load the helpers.
|
||||
. helpers.bash
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ load helpers
|
||||
|
||||
@test "buildah version up to date in .spec file" {
|
||||
run buildah version
|
||||
[ "$status" -eq 0 ]
|
||||
bversion=$(echo "$output" | awk '/^Version:/ { print $NF }')
|
||||
rversion=$(cat ${TESTSDIR}/../contrib/rpm/buildah.spec | awk '/^Version:/ { print $NF }')
|
||||
test "$bversion" = "$rversion"
|
||||
|
||||
27
user.go
27
user.go
@@ -26,39 +26,34 @@ func getUser(rootdir, userspec string) (specs.User, error) {
|
||||
uid64, uerr := strconv.ParseUint(userspec, 10, 32)
|
||||
if uerr == nil && groupspec == "" {
|
||||
// We parsed the user name as a number, and there's no group
|
||||
// component, so we need to look up the user's primary GID.
|
||||
// component, so try to look up the primary GID of the user who
|
||||
// has this UID.
|
||||
var name string
|
||||
name, gid64, gerr = lookupGroupForUIDInContainer(rootdir, uid64)
|
||||
if gerr == nil {
|
||||
userspec = name
|
||||
} else {
|
||||
if userrec, err := user.LookupId(userspec); err == nil {
|
||||
gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32)
|
||||
userspec = userrec.Name
|
||||
}
|
||||
// Leave userspec alone, but swallow the error and just
|
||||
// use GID 0.
|
||||
gid64 = 0
|
||||
gerr = nil
|
||||
}
|
||||
}
|
||||
if uerr != nil {
|
||||
// The user ID couldn't be parsed as a number, so try to look
|
||||
// up the user's UID and primary GID.
|
||||
uid64, gid64, uerr = lookupUserInContainer(rootdir, userspec)
|
||||
gerr = uerr
|
||||
}
|
||||
if uerr != nil {
|
||||
if userrec, err := user.Lookup(userspec); err == nil {
|
||||
uid64, uerr = strconv.ParseUint(userrec.Uid, 10, 32)
|
||||
gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32)
|
||||
}
|
||||
}
|
||||
|
||||
if groupspec != "" {
|
||||
// We have a group name or number, so parse it.
|
||||
gid64, gerr = strconv.ParseUint(groupspec, 10, 32)
|
||||
if gerr != nil {
|
||||
// The group couldn't be parsed as a number, so look up
|
||||
// the group's GID.
|
||||
gid64, gerr = lookupGroupInContainer(rootdir, groupspec)
|
||||
}
|
||||
if gerr != nil {
|
||||
if group, err := user.LookupGroup(groupspec); err == nil {
|
||||
gid64, gerr = strconv.ParseUint(group.Gid, 10, 32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if uerr == nil && gerr == nil {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +build !cgo !linux
|
||||
// +build !linux
|
||||
|
||||
package buildah
|
||||
|
||||
|
||||
235
user_linux.go
Normal file
235
user_linux.go
Normal file
@@ -0,0 +1,235 @@
|
||||
// +build linux
|
||||
|
||||
package buildah
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/containers/storage/pkg/reexec"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
openChrootedCommand = Package + "-open"
|
||||
)
|
||||
|
||||
func init() {
|
||||
reexec.Register(openChrootedCommand, openChrootedFileMain)
|
||||
}
|
||||
|
||||
func openChrootedFileMain() {
|
||||
status := 0
|
||||
flag.Parse()
|
||||
if len(flag.Args()) < 1 {
|
||||
os.Exit(1)
|
||||
}
|
||||
// Our first parameter is the directory to chroot into.
|
||||
if err := unix.Chdir(flag.Arg(0)); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "chdir(): %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := unix.Chroot(flag.Arg(0)); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "chroot(): %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// Anything else is a file we want to dump out.
|
||||
for _, filename := range flag.Args()[1:] {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "open(%q): %v", filename, err)
|
||||
status = 1
|
||||
continue
|
||||
}
|
||||
_, err = io.Copy(os.Stdout, f)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "read(%q): %v", filename, err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
os.Exit(status)
|
||||
}
|
||||
|
||||
func openChrootedFile(rootdir, filename string) (*exec.Cmd, io.ReadCloser, error) {
|
||||
// The child process expects a chroot and one or more filenames that
|
||||
// will be consulted relative to the chroot directory and concatenated
|
||||
// to its stdout. Start it up.
|
||||
cmd := reexec.Command(openChrootedCommand, rootdir, filename)
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Hand back the child's stdout for reading, and the child to reap.
|
||||
return cmd, stdout, nil
|
||||
}
|
||||
|
||||
var (
|
||||
lookupUser, lookupGroup sync.Mutex
|
||||
)
|
||||
|
||||
type lookupPasswdEntry struct {
|
||||
name string
|
||||
uid uint64
|
||||
gid uint64
|
||||
}
|
||||
type lookupGroupEntry struct {
|
||||
name string
|
||||
gid uint64
|
||||
}
|
||||
|
||||
func readWholeLine(rc *bufio.Reader) ([]byte, error) {
|
||||
line, isPrefix, err := rc.ReadLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for isPrefix {
|
||||
// We didn't get a whole line. Keep reading chunks until we find an end of line, and discard them.
|
||||
for isPrefix {
|
||||
logrus.Debugf("discarding partial line %q", string(line))
|
||||
_, isPrefix, err = rc.ReadLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// That last read was the end of a line, so now we try to read the (beginning of?) the next line.
|
||||
line, isPrefix, err = rc.ReadLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return line, nil
|
||||
}
|
||||
|
||||
func parseNextPasswd(rc *bufio.Reader) *lookupPasswdEntry {
|
||||
line, err := readWholeLine(rc)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields := strings.Split(string(line), ":")
|
||||
if len(fields) < 7 {
|
||||
return nil
|
||||
}
|
||||
uid, err := strconv.ParseUint(fields[2], 10, 32)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
gid, err := strconv.ParseUint(fields[3], 10, 32)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &lookupPasswdEntry{
|
||||
name: fields[0],
|
||||
uid: uid,
|
||||
gid: gid,
|
||||
}
|
||||
}
|
||||
|
||||
func parseNextGroup(rc *bufio.Reader) *lookupGroupEntry {
|
||||
line, err := readWholeLine(rc)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields := strings.Split(string(line), ":")
|
||||
if len(fields) < 4 {
|
||||
return nil
|
||||
}
|
||||
gid, err := strconv.ParseUint(fields[2], 10, 32)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &lookupGroupEntry{
|
||||
name: fields[0],
|
||||
gid: gid,
|
||||
}
|
||||
}
|
||||
|
||||
func lookupUserInContainer(rootdir, username string) (uid uint64, gid uint64, err error) {
|
||||
cmd, f, err := openChrootedFile(rootdir, "/etc/passwd")
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
defer func() {
|
||||
_ = cmd.Wait()
|
||||
}()
|
||||
rc := bufio.NewReader(f)
|
||||
defer f.Close()
|
||||
|
||||
lookupUser.Lock()
|
||||
defer lookupUser.Unlock()
|
||||
|
||||
pwd := parseNextPasswd(rc)
|
||||
for pwd != nil {
|
||||
if pwd.name != username {
|
||||
pwd = parseNextPasswd(rc)
|
||||
continue
|
||||
}
|
||||
return pwd.uid, pwd.gid, nil
|
||||
}
|
||||
|
||||
return 0, 0, user.UnknownUserError(fmt.Sprintf("error looking up user %q", username))
|
||||
}
|
||||
|
||||
func lookupGroupForUIDInContainer(rootdir string, userid uint64) (username string, gid uint64, err error) {
|
||||
cmd, f, err := openChrootedFile(rootdir, "/etc/passwd")
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
defer func() {
|
||||
_ = cmd.Wait()
|
||||
}()
|
||||
rc := bufio.NewReader(f)
|
||||
defer f.Close()
|
||||
|
||||
lookupUser.Lock()
|
||||
defer lookupUser.Unlock()
|
||||
|
||||
pwd := parseNextPasswd(rc)
|
||||
for pwd != nil {
|
||||
if pwd.uid != userid {
|
||||
pwd = parseNextPasswd(rc)
|
||||
continue
|
||||
}
|
||||
return pwd.name, pwd.gid, nil
|
||||
}
|
||||
|
||||
return "", 0, user.UnknownUserError(fmt.Sprintf("error looking up user with UID %d", userid))
|
||||
}
|
||||
|
||||
func lookupGroupInContainer(rootdir, groupname string) (gid uint64, err error) {
|
||||
cmd, f, err := openChrootedFile(rootdir, "/etc/group")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer func() {
|
||||
_ = cmd.Wait()
|
||||
}()
|
||||
rc := bufio.NewReader(f)
|
||||
defer f.Close()
|
||||
|
||||
lookupGroup.Lock()
|
||||
defer lookupGroup.Unlock()
|
||||
|
||||
grp := parseNextGroup(rc)
|
||||
for grp != nil {
|
||||
if grp.name != groupname {
|
||||
grp = parseNextGroup(rc)
|
||||
continue
|
||||
}
|
||||
return grp.gid, nil
|
||||
}
|
||||
|
||||
return 0, user.UnknownGroupError(fmt.Sprintf("error looking up group %q", groupname))
|
||||
}
|
||||
124
user_unix_cgo.go
124
user_unix_cgo.go
@@ -1,124 +0,0 @@
|
||||
// +build cgo
|
||||
// +build linux
|
||||
|
||||
package buildah
|
||||
|
||||
// #include <sys/types.h>
|
||||
// #include <grp.h>
|
||||
// #include <pwd.h>
|
||||
// #include <stdlib.h>
|
||||
// #include <stdio.h>
|
||||
// #include <string.h>
|
||||
// typedef FILE * pFILE;
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func fopenContainerFile(rootdir, filename string) (C.pFILE, error) {
|
||||
var st, lst syscall.Stat_t
|
||||
|
||||
ctrfile := filepath.Join(rootdir, filename)
|
||||
cctrfile := C.CString(ctrfile)
|
||||
defer C.free(unsafe.Pointer(cctrfile))
|
||||
mode := C.CString("r")
|
||||
defer C.free(unsafe.Pointer(mode))
|
||||
f, err := C.fopen(cctrfile, mode)
|
||||
if f == nil || err != nil {
|
||||
return nil, errors.Wrapf(err, "error opening %q", ctrfile)
|
||||
}
|
||||
if err = syscall.Fstat(int(C.fileno(f)), &st); err != nil {
|
||||
return nil, errors.Wrapf(err, "fstat(%q)", ctrfile)
|
||||
}
|
||||
if err = syscall.Lstat(ctrfile, &lst); err != nil {
|
||||
return nil, errors.Wrapf(err, "lstat(%q)", ctrfile)
|
||||
}
|
||||
if st.Dev != lst.Dev || st.Ino != lst.Ino {
|
||||
return nil, errors.Errorf("%q is not a regular file", ctrfile)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
var (
|
||||
lookupUser, lookupGroup sync.Mutex
|
||||
)
|
||||
|
||||
func lookupUserInContainer(rootdir, username string) (uint64, uint64, error) {
|
||||
name := C.CString(username)
|
||||
defer C.free(unsafe.Pointer(name))
|
||||
|
||||
f, err := fopenContainerFile(rootdir, "/etc/passwd")
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
defer C.fclose(f)
|
||||
|
||||
lookupUser.Lock()
|
||||
defer lookupUser.Unlock()
|
||||
|
||||
pwd := C.fgetpwent(f)
|
||||
for pwd != nil {
|
||||
if C.strcmp(pwd.pw_name, name) != 0 {
|
||||
pwd = C.fgetpwent(f)
|
||||
continue
|
||||
}
|
||||
return uint64(pwd.pw_uid), uint64(pwd.pw_gid), nil
|
||||
}
|
||||
|
||||
return 0, 0, user.UnknownUserError(fmt.Sprintf("error looking up user %q", username))
|
||||
}
|
||||
|
||||
func lookupGroupForUIDInContainer(rootdir string, userid uint64) (string, uint64, error) {
|
||||
f, err := fopenContainerFile(rootdir, "/etc/passwd")
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
defer C.fclose(f)
|
||||
|
||||
lookupUser.Lock()
|
||||
defer lookupUser.Unlock()
|
||||
|
||||
pwd := C.fgetpwent(f)
|
||||
for pwd != nil {
|
||||
if uint64(pwd.pw_uid) != userid {
|
||||
pwd = C.fgetpwent(f)
|
||||
continue
|
||||
}
|
||||
return C.GoString(pwd.pw_name), uint64(pwd.pw_gid), nil
|
||||
}
|
||||
|
||||
return "", 0, user.UnknownUserError(fmt.Sprintf("error looking up user with UID %d", userid))
|
||||
}
|
||||
|
||||
func lookupGroupInContainer(rootdir, groupname string) (uint64, error) {
|
||||
name := C.CString(groupname)
|
||||
defer C.free(unsafe.Pointer(name))
|
||||
|
||||
f, err := fopenContainerFile(rootdir, "/etc/group")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer C.fclose(f)
|
||||
|
||||
lookupGroup.Lock()
|
||||
defer lookupGroup.Unlock()
|
||||
|
||||
grp := C.fgetgrent(f)
|
||||
for grp != nil {
|
||||
if C.strcmp(grp.gr_name, name) != 0 {
|
||||
grp = C.fgetgrent(f)
|
||||
continue
|
||||
}
|
||||
return uint64(grp.gr_gid), nil
|
||||
}
|
||||
|
||||
return 0, user.UnknownGroupError(fmt.Sprintf("error looking up group %q", groupname))
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
github.com/BurntSushi/toml master
|
||||
github.com/Nvveen/Gotty master
|
||||
github.com/blang/semver master
|
||||
github.com/containers/image 063852766c3e82ec8359ce5f6612e056f3efaa76
|
||||
github.com/containers/image f950aa3529148eb0dea90888c24b6682da641b13
|
||||
github.com/containers/storage d7921c6facc516358070a1306689eda18adaa20a
|
||||
github.com/docker/distribution 5f6282db7d65e6d72ad7c2cc66310724a57be716
|
||||
github.com/docker/docker 30eb4d8cdc422b023d5f11f29a82ecb73554183b
|
||||
@@ -36,7 +36,7 @@ github.com/sirupsen/logrus master
|
||||
github.com/syndtr/gocapability master
|
||||
github.com/tchap/go-patricia master
|
||||
github.com/urfave/cli master
|
||||
github.com/vbatts/tar-split master
|
||||
github.com/vbatts/tar-split v0.10.2
|
||||
golang.org/x/crypto master
|
||||
golang.org/x/net master
|
||||
golang.org/x/sys master
|
||||
|
||||
7
vendor/github.com/containers/image/copy/copy.go
generated
vendored
7
vendor/github.com/containers/image/copy/copy.go
generated
vendored
@@ -12,8 +12,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
pb "gopkg.in/cheggaaa/pb.v1"
|
||||
|
||||
"github.com/containers/image/image"
|
||||
"github.com/containers/image/pkg/compression"
|
||||
"github.com/containers/image/signature"
|
||||
@@ -22,6 +20,7 @@ import (
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
pb "gopkg.in/cheggaaa/pb.v1"
|
||||
)
|
||||
|
||||
type digestingReader struct {
|
||||
@@ -95,6 +94,8 @@ type Options struct {
|
||||
DestinationCtx *types.SystemContext
|
||||
ProgressInterval time.Duration // time to wait between reports to signal the progress channel
|
||||
Progress chan types.ProgressProperties // Reported to when ProgressInterval has arrived for a single artifact+offset.
|
||||
// manifest MIME type of image set by user. "" is default and means use the autodetection to the the manifest MIME type
|
||||
ForceManifestMIMEType string
|
||||
}
|
||||
|
||||
// Image copies image from srcRef to destRef, using policyContext to validate
|
||||
@@ -193,7 +194,7 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe
|
||||
|
||||
// We compute preferredManifestMIMEType only to show it in error messages.
|
||||
// Without having to add this context in an error message, we would be happy enough to know only that no conversion is needed.
|
||||
preferredManifestMIMEType, otherManifestMIMETypeCandidates, err := determineManifestConversion(&manifestUpdates, src, dest.SupportedManifestMIMETypes(), canModifyManifest)
|
||||
preferredManifestMIMEType, otherManifestMIMETypeCandidates, err := determineManifestConversion(&manifestUpdates, src, dest.SupportedManifestMIMETypes(), canModifyManifest, options.ForceManifestMIMEType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
6
vendor/github.com/containers/image/copy/manifest.go
generated
vendored
6
vendor/github.com/containers/image/copy/manifest.go
generated
vendored
@@ -41,12 +41,16 @@ func (os *orderedSet) append(s string) {
|
||||
// Note that the conversion will only happen later, through src.UpdatedImage
|
||||
// Returns the preferred manifest MIME type (whether we are converting to it or using it unmodified),
|
||||
// and a list of other possible alternatives, in order.
|
||||
func determineManifestConversion(manifestUpdates *types.ManifestUpdateOptions, src types.Image, destSupportedManifestMIMETypes []string, canModifyManifest bool) (string, []string, error) {
|
||||
func determineManifestConversion(manifestUpdates *types.ManifestUpdateOptions, src types.Image, destSupportedManifestMIMETypes []string, canModifyManifest bool, forceManifestMIMEType string) (string, []string, error) {
|
||||
_, srcType, err := src.Manifest()
|
||||
if err != nil { // This should have been cached?!
|
||||
return "", nil, errors.Wrap(err, "Error reading manifest")
|
||||
}
|
||||
|
||||
if forceManifestMIMEType != "" {
|
||||
destSupportedManifestMIMETypes = []string{forceManifestMIMEType}
|
||||
}
|
||||
|
||||
if len(destSupportedManifestMIMETypes) == 0 {
|
||||
return srcType, []string{}, nil // Anything goes; just use the original as is, do not try any conversions.
|
||||
}
|
||||
|
||||
104
vendor/github.com/containers/image/directory/directory_dest.go
generated
vendored
104
vendor/github.com/containers/image/directory/directory_dest.go
generated
vendored
@@ -4,19 +4,77 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containers/image/types"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const version = "Directory Transport Version: 1.0\n"
|
||||
|
||||
// ErrNotContainerImageDir indicates that the directory doesn't match the expected contents of a directory created
|
||||
// using the 'dir' transport
|
||||
var ErrNotContainerImageDir = errors.New("not a containers image directory, don't want to overwrite important data")
|
||||
|
||||
type dirImageDestination struct {
|
||||
ref dirReference
|
||||
ref dirReference
|
||||
compress bool
|
||||
}
|
||||
|
||||
// newImageDestination returns an ImageDestination for writing to an existing directory.
|
||||
func newImageDestination(ref dirReference) types.ImageDestination {
|
||||
return &dirImageDestination{ref}
|
||||
// newImageDestination returns an ImageDestination for writing to a directory.
|
||||
func newImageDestination(ref dirReference, compress bool) (types.ImageDestination, error) {
|
||||
d := &dirImageDestination{ref: ref, compress: compress}
|
||||
|
||||
// If directory exists check if it is empty
|
||||
// if not empty, check whether the contents match that of a container image directory and overwrite the contents
|
||||
// if the contents don't match throw an error
|
||||
dirExists, err := pathExists(d.ref.resolvedPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error checking for path %q", d.ref.resolvedPath)
|
||||
}
|
||||
if dirExists {
|
||||
isEmpty, err := isDirEmpty(d.ref.resolvedPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !isEmpty {
|
||||
versionExists, err := pathExists(d.ref.versionPath())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error checking if path exists %q", d.ref.versionPath())
|
||||
}
|
||||
if versionExists {
|
||||
contents, err := ioutil.ReadFile(d.ref.versionPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// check if contents of version file is what we expect it to be
|
||||
if string(contents) != version {
|
||||
return nil, ErrNotContainerImageDir
|
||||
}
|
||||
} else {
|
||||
return nil, ErrNotContainerImageDir
|
||||
}
|
||||
// delete directory contents so that only one image is in the directory at a time
|
||||
if err = removeDirContents(d.ref.resolvedPath); err != nil {
|
||||
return nil, errors.Wrapf(err, "error erasing contents in %q", d.ref.resolvedPath)
|
||||
}
|
||||
logrus.Debugf("overwriting existing container image directory %q", d.ref.resolvedPath)
|
||||
}
|
||||
} else {
|
||||
// create directory if it doesn't exist
|
||||
if err := os.MkdirAll(d.ref.resolvedPath, 0755); err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to create directory %q", d.ref.resolvedPath)
|
||||
}
|
||||
}
|
||||
// create version file
|
||||
err = ioutil.WriteFile(d.ref.versionPath(), []byte(version), 0755)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating version file %q", d.ref.versionPath())
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
|
||||
@@ -42,7 +100,7 @@ func (d *dirImageDestination) SupportsSignatures() error {
|
||||
|
||||
// ShouldCompressLayers returns true iff it is desirable to compress layer blobs written to this destination.
|
||||
func (d *dirImageDestination) ShouldCompressLayers() bool {
|
||||
return false
|
||||
return d.compress
|
||||
}
|
||||
|
||||
// AcceptsForeignLayerURLs returns false iff foreign layers in manifest should be actually
|
||||
@@ -147,3 +205,39 @@ func (d *dirImageDestination) PutSignatures(signatures [][]byte) error {
|
||||
func (d *dirImageDestination) Commit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// returns true if path exists
|
||||
func pathExists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// returns true if directory is empty
|
||||
func isDirEmpty(path string) (bool, error) {
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return len(files) == 0, nil
|
||||
}
|
||||
|
||||
// deletes the contents of a directory
|
||||
func removeDirContents(path string) error {
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if err := os.RemoveAll(filepath.Join(path, file.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
11
vendor/github.com/containers/image/directory/directory_transport.go
generated
vendored
11
vendor/github.com/containers/image/directory/directory_transport.go
generated
vendored
@@ -152,7 +152,11 @@ func (ref dirReference) NewImageSource(ctx *types.SystemContext) (types.ImageSou
|
||||
// NewImageDestination returns a types.ImageDestination for this reference.
|
||||
// The caller must call .Close() on the returned ImageDestination.
|
||||
func (ref dirReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(ref), nil
|
||||
compress := false
|
||||
if ctx != nil {
|
||||
compress = ctx.DirForceCompress
|
||||
}
|
||||
return newImageDestination(ref, compress)
|
||||
}
|
||||
|
||||
// DeleteImage deletes the named image from the registry, if supported.
|
||||
@@ -175,3 +179,8 @@ func (ref dirReference) layerPath(digest digest.Digest) string {
|
||||
func (ref dirReference) signaturePath(index int) string {
|
||||
return filepath.Join(ref.path, fmt.Sprintf("signature-%d", index+1))
|
||||
}
|
||||
|
||||
// versionPath returns a path for the version file within a directory using our conventions.
|
||||
func (ref dirReference) versionPath() string {
|
||||
return filepath.Join(ref.path, "version")
|
||||
}
|
||||
|
||||
69
vendor/github.com/containers/image/docker/daemon/client.go
generated
vendored
Normal file
69
vendor/github.com/containers/image/docker/daemon/client.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containers/image/types"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
)
|
||||
|
||||
const (
|
||||
// The default API version to be used in case none is explicitly specified
|
||||
defaultAPIVersion = "1.22"
|
||||
)
|
||||
|
||||
// NewDockerClient initializes a new API client based on the passed SystemContext.
|
||||
func newDockerClient(ctx *types.SystemContext) (*dockerclient.Client, error) {
|
||||
host := dockerclient.DefaultDockerHost
|
||||
if ctx != nil && ctx.DockerDaemonHost != "" {
|
||||
host = ctx.DockerDaemonHost
|
||||
}
|
||||
|
||||
// Sadly, unix:// sockets don't work transparently with dockerclient.NewClient.
|
||||
// They work fine with a nil httpClient; with a non-nil httpClient, the transport’s
|
||||
// TLSClientConfig must be nil (or the client will try using HTTPS over the PF_UNIX socket
|
||||
// regardless of the values in the *tls.Config), and we would have to call sockets.ConfigureTransport.
|
||||
//
|
||||
// We don't really want to configure anything for unix:// sockets, so just pass a nil *http.Client.
|
||||
proto, _, _, err := dockerclient.ParseHost(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var httpClient *http.Client
|
||||
if proto != "unix" {
|
||||
hc, err := tlsConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
httpClient = hc
|
||||
}
|
||||
|
||||
return dockerclient.NewClient(host, defaultAPIVersion, httpClient, nil)
|
||||
}
|
||||
|
||||
func tlsConfig(ctx *types.SystemContext) (*http.Client, error) {
|
||||
options := tlsconfig.Options{}
|
||||
if ctx != nil && ctx.DockerDaemonInsecureSkipTLSVerify {
|
||||
options.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
if ctx != nil && ctx.DockerDaemonCertPath != "" {
|
||||
options.CAFile = filepath.Join(ctx.DockerDaemonCertPath, "ca.pem")
|
||||
options.CertFile = filepath.Join(ctx.DockerDaemonCertPath, "cert.pem")
|
||||
options.KeyFile = filepath.Join(ctx.DockerDaemonCertPath, "key.pem")
|
||||
}
|
||||
|
||||
tlsc, err := tlsconfig.Client(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsc,
|
||||
},
|
||||
CheckRedirect: dockerclient.CheckRedirect,
|
||||
}, nil
|
||||
}
|
||||
8
vendor/github.com/containers/image/docker/daemon/daemon_dest.go
generated
vendored
8
vendor/github.com/containers/image/docker/daemon/daemon_dest.go
generated
vendored
@@ -24,7 +24,7 @@ type daemonImageDestination struct {
|
||||
}
|
||||
|
||||
// newImageDestination returns a types.ImageDestination for the specified image reference.
|
||||
func newImageDestination(systemCtx *types.SystemContext, ref daemonReference) (types.ImageDestination, error) {
|
||||
func newImageDestination(ctx *types.SystemContext, ref daemonReference) (types.ImageDestination, error) {
|
||||
if ref.ref == nil {
|
||||
return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport())
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func newImageDestination(systemCtx *types.SystemContext, ref daemonReference) (t
|
||||
return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport())
|
||||
}
|
||||
|
||||
c, err := client.NewClient(client.DefaultDockerHost, "1.22", nil, nil) // FIXME: overridable host
|
||||
c, err := newDockerClient(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error initializing docker engine client")
|
||||
}
|
||||
@@ -42,8 +42,8 @@ func newImageDestination(systemCtx *types.SystemContext, ref daemonReference) (t
|
||||
// Commit() may never be called, so we may never read from this channel; so, make this buffered to allow imageLoadGoroutine to write status and terminate even if we never read it.
|
||||
statusChannel := make(chan error, 1)
|
||||
|
||||
ctx, goroutineCancel := context.WithCancel(context.Background())
|
||||
go imageLoadGoroutine(ctx, c, reader, statusChannel)
|
||||
goroutineContext, goroutineCancel := context.WithCancel(context.Background())
|
||||
go imageLoadGoroutine(goroutineContext, c, reader, statusChannel)
|
||||
|
||||
return &daemonImageDestination{
|
||||
ref: ref,
|
||||
|
||||
3
vendor/github.com/containers/image/docker/daemon/daemon_src.go
generated
vendored
3
vendor/github.com/containers/image/docker/daemon/daemon_src.go
generated
vendored
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/containers/image/docker/tarfile"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
@@ -35,7 +34,7 @@ type layerInfo struct {
|
||||
// is the config, and that the following len(RootFS) files are the layers, but that feels
|
||||
// way too brittle.)
|
||||
func newImageSource(ctx *types.SystemContext, ref daemonReference) (types.ImageSource, error) {
|
||||
c, err := client.NewClient(client.DefaultDockerHost, "1.22", nil, nil) // FIXME: overridable host
|
||||
c, err := newDockerClient(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error initializing docker engine client")
|
||||
}
|
||||
|
||||
214
vendor/github.com/containers/image/docker/docker_client.go
generated
vendored
214
vendor/github.com/containers/image/docker/docker_client.go
generated
vendored
@@ -3,23 +3,20 @@ package docker
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/pkg/docker/config"
|
||||
"github.com/containers/image/pkg/tlsclientconfig"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/containers/storage/pkg/homedir"
|
||||
"github.com/docker/distribution/registry/client"
|
||||
helperclient "github.com/docker/docker-credential-helpers/client"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
@@ -27,13 +24,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
dockerHostname = "docker.io"
|
||||
dockerRegistry = "registry-1.docker.io"
|
||||
dockerAuthRegistry = "https://index.docker.io/v1/"
|
||||
|
||||
dockerCfg = ".docker"
|
||||
dockerCfgFileName = "config.json"
|
||||
dockerCfgObsolete = ".dockercfg"
|
||||
dockerHostname = "docker.io"
|
||||
dockerRegistry = "registry-1.docker.io"
|
||||
|
||||
systemPerHostCertDirPath = "/etc/docker/certs.d"
|
||||
|
||||
@@ -51,9 +43,13 @@ const (
|
||||
extensionSignatureTypeAtomic = "atomic" // extensionSignature.Type
|
||||
)
|
||||
|
||||
// ErrV1NotSupported is returned when we're trying to talk to a
|
||||
// docker V1 registry.
|
||||
var ErrV1NotSupported = errors.New("can't talk to a V1 docker registry")
|
||||
var (
|
||||
// ErrV1NotSupported is returned when we're trying to talk to a
|
||||
// docker V1 registry.
|
||||
ErrV1NotSupported = errors.New("can't talk to a V1 docker registry")
|
||||
// ErrUnauthorizedForCredentials is returned when the status code returned is 401
|
||||
ErrUnauthorizedForCredentials = errors.New("unable to retrieve auth token: invalid username/password")
|
||||
)
|
||||
|
||||
// extensionSignature and extensionSignatureList come from github.com/openshift/origin/pkg/dockerregistry/server/signaturedispatcher.go:
|
||||
// signature represents a Docker image signature.
|
||||
@@ -128,52 +124,84 @@ func dockerCertDir(ctx *types.SystemContext, hostPort string) string {
|
||||
return filepath.Join(hostCertDir, hostPort)
|
||||
}
|
||||
|
||||
// newDockerClient returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry)
|
||||
// newDockerClientFromRef returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry)
|
||||
// “write” specifies whether the client will be used for "write" access (in particular passed to lookaside.go:toplevelFromSection)
|
||||
func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool, actions string) (*dockerClient, error) {
|
||||
func newDockerClientFromRef(ctx *types.SystemContext, ref dockerReference, write bool, actions string) (*dockerClient, error) {
|
||||
registry := reference.Domain(ref.ref)
|
||||
if registry == dockerHostname {
|
||||
registry = dockerRegistry
|
||||
username, password, err := config.GetAuthentication(ctx, reference.Domain(ref.ref))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error getting username and password")
|
||||
}
|
||||
username, password, err := getAuth(ctx, reference.Domain(ref.ref))
|
||||
sigBase, err := configuredSignatureStorageBase(ctx, ref, write)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
remoteName := reference.Path(ref.ref)
|
||||
|
||||
return newDockerClientWithDetails(ctx, registry, username, password, actions, sigBase, remoteName)
|
||||
}
|
||||
|
||||
// newDockerClientWithDetails returns a new dockerClient instance for the given parameters
|
||||
func newDockerClientWithDetails(ctx *types.SystemContext, registry, username, password, actions string, sigBase signatureStorageBase, remoteName string) (*dockerClient, error) {
|
||||
hostName := registry
|
||||
if registry == dockerHostname {
|
||||
registry = dockerRegistry
|
||||
}
|
||||
tr := tlsclientconfig.NewTransport()
|
||||
tr.TLSClientConfig = serverDefault()
|
||||
|
||||
// It is undefined whether the host[:port] string for dockerHostname should be dockerHostname or dockerRegistry,
|
||||
// because docker/docker does not read the certs.d subdirectory at all in that case. We use the user-visible
|
||||
// dockerHostname here, because it is more symmetrical to read the configuration in that case as well, and because
|
||||
// generally the UI hides the existence of the different dockerRegistry. But note that this behavior is
|
||||
// undocumented and may change if docker/docker changes.
|
||||
certDir := dockerCertDir(ctx, reference.Domain(ref.ref))
|
||||
certDir := dockerCertDir(ctx, hostName)
|
||||
if err := tlsclientconfig.SetupCertificates(certDir, tr.TLSClientConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ctx != nil && ctx.DockerInsecureSkipTLSVerify {
|
||||
tr.TLSClientConfig.InsecureSkipVerify = true
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
sigBase, err := configuredSignatureStorageBase(ctx, ref, write)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dockerClient{
|
||||
ctx: ctx,
|
||||
registry: registry,
|
||||
username: username,
|
||||
password: password,
|
||||
client: client,
|
||||
client: &http.Client{Transport: tr},
|
||||
signatureBase: sigBase,
|
||||
scope: authScope{
|
||||
actions: actions,
|
||||
remoteName: reference.Path(ref.ref),
|
||||
remoteName: remoteName,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CheckAuth validates the credentials by attempting to log into the registry
|
||||
// returns an error if an error occcured while making the http request or the status code received was 401
|
||||
func CheckAuth(ctx context.Context, sCtx *types.SystemContext, username, password, registry string) error {
|
||||
newLoginClient, err := newDockerClientWithDetails(sCtx, registry, username, password, "", nil, "")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error creating new docker client")
|
||||
}
|
||||
|
||||
resp, err := newLoginClient.makeRequest(ctx, "GET", "/v2/", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
return nil
|
||||
case http.StatusUnauthorized:
|
||||
return ErrUnauthorizedForCredentials
|
||||
default:
|
||||
return errors.Errorf("error occured with status code %q", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
|
||||
// The host name and schema is taken from the client or autodetected, and the path is relative to it, i.e. the path usually starts with /v2/.
|
||||
func (c *dockerClient) makeRequest(ctx context.Context, method, path string, headers map[string][]string, stream io.Reader) (*http.Response, error) {
|
||||
@@ -245,7 +273,10 @@ func (c *dockerClient) setupRequestAuth(req *http.Request) error {
|
||||
return errors.Errorf("missing realm in bearer auth challenge")
|
||||
}
|
||||
service, _ := challenge.Parameters["service"] // Will be "" if not present
|
||||
scope := fmt.Sprintf("repository:%s:%s", c.scope.remoteName, c.scope.actions)
|
||||
var scope string
|
||||
if c.scope.remoteName != "" && c.scope.actions != "" {
|
||||
scope = fmt.Sprintf("repository:%s:%s", c.scope.remoteName, c.scope.actions)
|
||||
}
|
||||
token, err := c.getBearerToken(req.Context(), realm, service, scope)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -291,7 +322,7 @@ func (c *dockerClient) getBearerToken(ctx context.Context, realm, service, scope
|
||||
defer res.Body.Close()
|
||||
switch res.StatusCode {
|
||||
case http.StatusUnauthorized:
|
||||
return nil, errors.Errorf("unable to retrieve auth token: 401 unauthorized")
|
||||
return nil, ErrUnauthorizedForCredentials
|
||||
case http.StatusOK:
|
||||
break
|
||||
default:
|
||||
@@ -315,65 +346,6 @@ func (c *dockerClient) getBearerToken(ctx context.Context, realm, service, scope
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
func getAuth(ctx *types.SystemContext, registry string) (string, string, error) {
|
||||
if ctx != nil && ctx.DockerAuthConfig != nil {
|
||||
return ctx.DockerAuthConfig.Username, ctx.DockerAuthConfig.Password, nil
|
||||
}
|
||||
var dockerAuth dockerConfigFile
|
||||
dockerCfgPath := filepath.Join(getDefaultConfigDir(".docker"), dockerCfgFileName)
|
||||
if _, err := os.Stat(dockerCfgPath); err == nil {
|
||||
j, err := ioutil.ReadFile(dockerCfgPath)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if err := json.Unmarshal(j, &dockerAuth); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
} else if os.IsNotExist(err) {
|
||||
// try old config path
|
||||
oldDockerCfgPath := filepath.Join(getDefaultConfigDir(dockerCfgObsolete))
|
||||
if _, err := os.Stat(oldDockerCfgPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", "", nil
|
||||
}
|
||||
return "", "", errors.Wrap(err, oldDockerCfgPath)
|
||||
}
|
||||
|
||||
j, err := ioutil.ReadFile(oldDockerCfgPath)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if err := json.Unmarshal(j, &dockerAuth.AuthConfigs); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
} else if err != nil {
|
||||
return "", "", errors.Wrap(err, dockerCfgPath)
|
||||
}
|
||||
|
||||
// First try cred helpers. They should always be normalized.
|
||||
if ch, exists := dockerAuth.CredHelpers[registry]; exists {
|
||||
return getAuthFromCredHelper(ch, registry)
|
||||
}
|
||||
|
||||
// I'm feeling lucky.
|
||||
if c, exists := dockerAuth.AuthConfigs[registry]; exists {
|
||||
return decodeDockerAuth(c.Auth)
|
||||
}
|
||||
|
||||
// bad luck; let's normalize the entries first
|
||||
registry = normalizeRegistry(registry)
|
||||
normalizedAuths := map[string]dockerAuthConfig{}
|
||||
for k, v := range dockerAuth.AuthConfigs {
|
||||
normalizedAuths[normalizeRegistry(k)] = v
|
||||
}
|
||||
if c, exists := normalizedAuths[registry]; exists {
|
||||
return decodeDockerAuth(c.Auth)
|
||||
}
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
// detectProperties detects various properties of the registry.
|
||||
// See the dockerClient documentation for members which are affected by this.
|
||||
func (c *dockerClient) detectProperties(ctx context.Context) error {
|
||||
@@ -456,67 +428,3 @@ func (c *dockerClient) getExtensionsSignatures(ctx context.Context, ref dockerRe
|
||||
}
|
||||
return &parsedBody, nil
|
||||
}
|
||||
|
||||
func getDefaultConfigDir(confPath string) string {
|
||||
return filepath.Join(homedir.Get(), confPath)
|
||||
}
|
||||
|
||||
type dockerAuthConfig struct {
|
||||
Auth string `json:"auth,omitempty"`
|
||||
}
|
||||
|
||||
type dockerConfigFile struct {
|
||||
AuthConfigs map[string]dockerAuthConfig `json:"auths"`
|
||||
CredHelpers map[string]string `json:"credHelpers,omitempty"`
|
||||
}
|
||||
|
||||
func getAuthFromCredHelper(credHelper, registry string) (string, string, error) {
|
||||
helperName := fmt.Sprintf("docker-credential-%s", credHelper)
|
||||
p := helperclient.NewShellProgramFunc(helperName)
|
||||
creds, err := helperclient.Get(p, registry)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return creds.Username, creds.Secret, nil
|
||||
}
|
||||
|
||||
func decodeDockerAuth(s string) (string, string, error) {
|
||||
decoded, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
parts := strings.SplitN(string(decoded), ":", 2)
|
||||
if len(parts) != 2 {
|
||||
// if it's invalid just skip, as docker does
|
||||
return "", "", nil
|
||||
}
|
||||
user := parts[0]
|
||||
password := strings.Trim(parts[1], "\x00")
|
||||
return user, password, nil
|
||||
}
|
||||
|
||||
// convertToHostname converts a registry url which has http|https prepended
|
||||
// to just an hostname.
|
||||
// Copied from github.com/docker/docker/registry/auth.go
|
||||
func convertToHostname(url string) string {
|
||||
stripped := url
|
||||
if strings.HasPrefix(url, "http://") {
|
||||
stripped = strings.TrimPrefix(url, "http://")
|
||||
} else if strings.HasPrefix(url, "https://") {
|
||||
stripped = strings.TrimPrefix(url, "https://")
|
||||
}
|
||||
|
||||
nameParts := strings.SplitN(stripped, "/", 2)
|
||||
|
||||
return nameParts[0]
|
||||
}
|
||||
|
||||
func normalizeRegistry(registry string) string {
|
||||
normalized := convertToHostname(registry)
|
||||
switch normalized {
|
||||
case "registry-1.docker.io", "docker.io":
|
||||
return "index.docker.io"
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
10
vendor/github.com/containers/image/docker/docker_image_dest.go
generated
vendored
10
vendor/github.com/containers/image/docker/docker_image_dest.go
generated
vendored
@@ -34,7 +34,7 @@ type dockerImageDestination struct {
|
||||
|
||||
// newImageDestination creates a new ImageDestination for the specified image reference.
|
||||
func newImageDestination(ctx *types.SystemContext, ref dockerReference) (types.ImageDestination, error) {
|
||||
c, err := newDockerClient(ctx, ref, true, "pull,push")
|
||||
c, err := newDockerClientFromRef(ctx, ref, true, "pull,push")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -236,7 +236,7 @@ func (d *dockerImageDestination) PutManifest(m []byte) error {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusCreated {
|
||||
if !successStatus(res.StatusCode) {
|
||||
err = errors.Wrapf(client.HandleErrorResponse(res), "Error uploading manifest to %s", path)
|
||||
if isManifestInvalidError(errors.Cause(err)) {
|
||||
err = types.ManifestTypeRejectedError{Err: err}
|
||||
@@ -246,6 +246,12 @@ func (d *dockerImageDestination) PutManifest(m []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// successStatus returns true if the argument is a successful HTTP response
|
||||
// code (in the range 200 - 399 inclusive).
|
||||
func successStatus(status int) bool {
|
||||
return status >= 200 && status <= 399
|
||||
}
|
||||
|
||||
// isManifestInvalidError returns true iff err from client.HandleErrorReponse is a “manifest invalid” error.
|
||||
func isManifestInvalidError(err error) bool {
|
||||
errors, ok := err.(errcode.Errors)
|
||||
|
||||
4
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
4
vendor/github.com/containers/image/docker/docker_image_src.go
generated
vendored
@@ -31,7 +31,7 @@ type dockerImageSource struct {
|
||||
// newImageSource creates a new ImageSource for the specified image reference.
|
||||
// The caller must call .Close() on the returned ImageSource.
|
||||
func newImageSource(ctx *types.SystemContext, ref dockerReference) (*dockerImageSource, error) {
|
||||
c, err := newDockerClient(ctx, ref, false, "pull")
|
||||
c, err := newDockerClientFromRef(ctx, ref, false, "pull")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -298,7 +298,7 @@ func (s *dockerImageSource) getSignaturesFromAPIExtension(ctx context.Context) (
|
||||
|
||||
// deleteImage deletes the named image from the registry, if supported.
|
||||
func deleteImage(ctx *types.SystemContext, ref dockerReference) error {
|
||||
c, err := newDockerClient(ctx, ref, true, "push")
|
||||
c, err := newDockerClientFromRef(ctx, ref, true, "push")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
9
vendor/github.com/containers/image/image/docker_schema1.go
generated
vendored
9
vendor/github.com/containers/image/image/docker_schema1.go
generated
vendored
@@ -161,14 +161,17 @@ func (m *manifestSchema1) imageInspectInfo() (*types.ImageInspectInfo, error) {
|
||||
if err := json.Unmarshal([]byte(m.History[0].V1Compatibility), v1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &types.ImageInspectInfo{
|
||||
i := &types.ImageInspectInfo{
|
||||
Tag: m.Tag,
|
||||
DockerVersion: v1.DockerVersion,
|
||||
Created: v1.Created,
|
||||
Labels: v1.Config.Labels,
|
||||
Architecture: v1.Architecture,
|
||||
Os: v1.OS,
|
||||
}, nil
|
||||
}
|
||||
if v1.Config != nil {
|
||||
i.Labels = v1.Config.Labels
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
|
||||
|
||||
9
vendor/github.com/containers/image/image/docker_schema2.go
generated
vendored
9
vendor/github.com/containers/image/image/docker_schema2.go
generated
vendored
@@ -157,13 +157,16 @@ func (m *manifestSchema2) imageInspectInfo() (*types.ImageInspectInfo, error) {
|
||||
if err := json.Unmarshal(config, v1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &types.ImageInspectInfo{
|
||||
i := &types.ImageInspectInfo{
|
||||
DockerVersion: v1.DockerVersion,
|
||||
Created: v1.Created,
|
||||
Labels: v1.Config.Labels,
|
||||
Architecture: v1.Architecture,
|
||||
Os: v1.OS,
|
||||
}, nil
|
||||
}
|
||||
if v1.Config != nil {
|
||||
i.Labels = v1.Config.Labels
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
|
||||
|
||||
11
vendor/github.com/containers/image/image/oci.go
generated
vendored
11
vendor/github.com/containers/image/image/oci.go
generated
vendored
@@ -109,7 +109,7 @@ func (m *manifestOCI1) OCIConfig() (*imgspecv1.Image, error) {
|
||||
func (m *manifestOCI1) LayerInfos() []types.BlobInfo {
|
||||
blobs := []types.BlobInfo{}
|
||||
for _, layer := range m.LayersDescriptors {
|
||||
blobs = append(blobs, types.BlobInfo{Digest: layer.Digest, Size: layer.Size, Annotations: layer.Annotations, URLs: layer.URLs})
|
||||
blobs = append(blobs, types.BlobInfo{Digest: layer.Digest, Size: layer.Size, Annotations: layer.Annotations, URLs: layer.URLs, MediaType: layer.MediaType})
|
||||
}
|
||||
return blobs
|
||||
}
|
||||
@@ -130,13 +130,16 @@ func (m *manifestOCI1) imageInspectInfo() (*types.ImageInspectInfo, error) {
|
||||
if err := json.Unmarshal(config, v1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &types.ImageInspectInfo{
|
||||
i := &types.ImageInspectInfo{
|
||||
DockerVersion: v1.DockerVersion,
|
||||
Created: v1.Created,
|
||||
Labels: v1.Config.Labels,
|
||||
Architecture: v1.Architecture,
|
||||
Os: v1.OS,
|
||||
}, nil
|
||||
}
|
||||
if v1.Config != nil {
|
||||
i.Labels = v1.Config.Labels
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// UpdatedImageNeedsLayerDiffIDs returns true iff UpdatedImage(options) needs InformationOnly.LayerDiffIDs.
|
||||
|
||||
1
vendor/github.com/containers/image/oci/archive/oci_dest.go
generated
vendored
1
vendor/github.com/containers/image/oci/archive/oci_dest.go
generated
vendored
@@ -106,7 +106,6 @@ func (d *ociArchiveImageDestination) Commit() error {
|
||||
src := d.tempDirRef.tempDirectory
|
||||
// path to save tarred up file
|
||||
dst := d.ref.resolvedFile
|
||||
|
||||
return tarDirectory(src, dst)
|
||||
}
|
||||
|
||||
|
||||
97
vendor/github.com/containers/image/oci/layout/oci_dest.go
generated
vendored
97
vendor/github.com/containers/image/oci/layout/oci_dest.go
generated
vendored
@@ -18,21 +18,47 @@ import (
|
||||
)
|
||||
|
||||
type ociImageDestination struct {
|
||||
ref ociReference
|
||||
index imgspecv1.Index
|
||||
ref ociReference
|
||||
index imgspecv1.Index
|
||||
sharedBlobDir string
|
||||
}
|
||||
|
||||
// newImageDestination returns an ImageDestination for writing to an existing directory.
|
||||
func newImageDestination(ref ociReference) (types.ImageDestination, error) {
|
||||
func newImageDestination(ctx *types.SystemContext, ref ociReference) (types.ImageDestination, error) {
|
||||
if ref.image == "" {
|
||||
return nil, errors.Errorf("cannot save image with empty image.ref.name")
|
||||
}
|
||||
index := imgspecv1.Index{
|
||||
Versioned: imgspec.Versioned{
|
||||
SchemaVersion: 2,
|
||||
},
|
||||
|
||||
var index *imgspecv1.Index
|
||||
if indexExists(ref) {
|
||||
var err error
|
||||
index, err = ref.getIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
index = &imgspecv1.Index{
|
||||
Versioned: imgspec.Versioned{
|
||||
SchemaVersion: 2,
|
||||
},
|
||||
}
|
||||
}
|
||||
return &ociImageDestination{ref: ref, index: index}, nil
|
||||
|
||||
d := &ociImageDestination{ref: ref, index: *index}
|
||||
if ctx != nil {
|
||||
d.sharedBlobDir = ctx.OCISharedBlobDirPath
|
||||
}
|
||||
|
||||
if err := ensureDirectoryExists(d.ref.dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Per the OCI image specification, layouts MUST have a "blobs" subdirectory,
|
||||
// but it MAY be empty (e.g. if we never end up calling PutBlob)
|
||||
// https://github.com/opencontainers/image-spec/blame/7c889fafd04a893f5c5f50b7ab9963d5d64e5242/image-layout.md#L19
|
||||
if err := ensureDirectoryExists(filepath.Join(d.ref.dir, "blobs")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent,
|
||||
@@ -81,9 +107,6 @@ func (d *ociImageDestination) MustMatchRuntimeOS() bool {
|
||||
// to any other readers for download using the supplied digest.
|
||||
// If stream.Read() at any time, ESPECIALLY at end of input, returns an error, PutBlob MUST 1) fail, and 2) delete any data stored so far.
|
||||
func (d *ociImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) {
|
||||
if err := ensureDirectoryExists(d.ref.dir); err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
blobFile, err := ioutil.TempFile(d.ref.dir, "oci-put-blob")
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
@@ -114,7 +137,7 @@ func (d *ociImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
|
||||
blobPath, err := d.ref.blobPath(computedDigest)
|
||||
blobPath, err := d.ref.blobPath(computedDigest, d.sharedBlobDir)
|
||||
if err != nil {
|
||||
return types.BlobInfo{}, err
|
||||
}
|
||||
@@ -136,7 +159,7 @@ func (d *ociImageDestination) HasBlob(info types.BlobInfo) (bool, int64, error)
|
||||
if info.Digest == "" {
|
||||
return false, -1, errors.Errorf(`"Can not check for a blob with unknown digest`)
|
||||
}
|
||||
blobPath, err := d.ref.blobPath(info.Digest)
|
||||
blobPath, err := d.ref.blobPath(info.Digest, d.sharedBlobDir)
|
||||
if err != nil {
|
||||
return false, -1, err
|
||||
}
|
||||
@@ -169,7 +192,7 @@ func (d *ociImageDestination) PutManifest(m []byte) error {
|
||||
desc.MediaType = imgspecv1.MediaTypeImageManifest
|
||||
desc.Size = int64(len(m))
|
||||
|
||||
blobPath, err := d.ref.blobPath(digest)
|
||||
blobPath, err := d.ref.blobPath(digest, d.sharedBlobDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -191,23 +214,20 @@ func (d *ociImageDestination) PutManifest(m []byte) error {
|
||||
Architecture: runtime.GOARCH,
|
||||
OS: runtime.GOOS,
|
||||
}
|
||||
d.index.Manifests = append(d.index.Manifests, desc)
|
||||
d.addManifest(&desc)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureDirectoryExists(path string) error {
|
||||
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return err
|
||||
func (d *ociImageDestination) addManifest(desc *imgspecv1.Descriptor) {
|
||||
for i, manifest := range d.index.Manifests {
|
||||
if manifest.Annotations["org.opencontainers.image.ref.name"] == desc.Annotations["org.opencontainers.image.ref.name"] {
|
||||
// TODO Should there first be a cleanup based on the descriptor we are going to replace?
|
||||
d.index.Manifests[i] = *desc
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureParentDirectoryExists ensures the parent of the supplied path exists.
|
||||
func ensureParentDirectoryExists(path string) error {
|
||||
return ensureDirectoryExists(filepath.Dir(path))
|
||||
d.index.Manifests = append(d.index.Manifests, *desc)
|
||||
}
|
||||
|
||||
func (d *ociImageDestination) PutSignatures(signatures [][]byte) error {
|
||||
@@ -231,3 +251,30 @@ func (d *ociImageDestination) Commit() error {
|
||||
}
|
||||
return ioutil.WriteFile(d.ref.indexPath(), indexJSON, 0644)
|
||||
}
|
||||
|
||||
func ensureDirectoryExists(path string) error {
|
||||
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureParentDirectoryExists ensures the parent of the supplied path exists.
|
||||
func ensureParentDirectoryExists(path string) error {
|
||||
return ensureDirectoryExists(filepath.Dir(path))
|
||||
}
|
||||
|
||||
// indexExists checks whether the index location specified in the OCI reference exists.
|
||||
// The implementation is opinionated, since in case of unexpected errors false is returned
|
||||
func indexExists(ref ociReference) bool {
|
||||
_, err := os.Stat(ref.indexPath())
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
20
vendor/github.com/containers/image/oci/layout/oci_src.go
generated
vendored
20
vendor/github.com/containers/image/oci/layout/oci_src.go
generated
vendored
@@ -17,9 +17,10 @@ import (
|
||||
)
|
||||
|
||||
type ociImageSource struct {
|
||||
ref ociReference
|
||||
descriptor imgspecv1.Descriptor
|
||||
client *http.Client
|
||||
ref ociReference
|
||||
descriptor imgspecv1.Descriptor
|
||||
client *http.Client
|
||||
sharedBlobDir string
|
||||
}
|
||||
|
||||
// newImageSource returns an ImageSource for reading from an existing directory.
|
||||
@@ -40,7 +41,12 @@ func newImageSource(ctx *types.SystemContext, ref ociReference) (types.ImageSour
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ociImageSource{ref: ref, descriptor: descriptor, client: client}, nil
|
||||
d := &ociImageSource{ref: ref, descriptor: descriptor, client: client}
|
||||
if ctx != nil {
|
||||
// TODO(jonboulle): check dir existence?
|
||||
d.sharedBlobDir = ctx.OCISharedBlobDirPath
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Reference returns the reference used to set up this source.
|
||||
@@ -56,7 +62,7 @@ func (s *ociImageSource) Close() error {
|
||||
// GetManifest returns the image's manifest along with its MIME type (which may be empty when it can't be determined but the manifest is available).
|
||||
// It may use a remote (= slow) service.
|
||||
func (s *ociImageSource) GetManifest() ([]byte, string, error) {
|
||||
manifestPath, err := s.ref.blobPath(digest.Digest(s.descriptor.Digest))
|
||||
manifestPath, err := s.ref.blobPath(digest.Digest(s.descriptor.Digest), s.sharedBlobDir)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -69,7 +75,7 @@ func (s *ociImageSource) GetManifest() ([]byte, string, error) {
|
||||
}
|
||||
|
||||
func (s *ociImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
|
||||
manifestPath, err := s.ref.blobPath(digest)
|
||||
manifestPath, err := s.ref.blobPath(digest, s.sharedBlobDir)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -92,7 +98,7 @@ func (s *ociImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, err
|
||||
return s.getExternalBlob(info.URLs)
|
||||
}
|
||||
|
||||
path, err := s.ref.blobPath(info.Digest)
|
||||
path, err := s.ref.blobPath(info.Digest, s.sharedBlobDir)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
29
vendor/github.com/containers/image/oci/layout/oci_transport.go
generated
vendored
29
vendor/github.com/containers/image/oci/layout/oci_transport.go
generated
vendored
@@ -189,14 +189,25 @@ func (ref ociReference) NewImage(ctx *types.SystemContext) (types.Image, error)
|
||||
return image.FromSource(src)
|
||||
}
|
||||
|
||||
func (ref ociReference) getManifestDescriptor() (imgspecv1.Descriptor, error) {
|
||||
// getIndex returns a pointer to the index references by this ociReference. If an error occurs opening an index nil is returned together
|
||||
// with an error.
|
||||
func (ref ociReference) getIndex() (*imgspecv1.Index, error) {
|
||||
indexJSON, err := os.Open(ref.indexPath())
|
||||
if err != nil {
|
||||
return imgspecv1.Descriptor{}, err
|
||||
return nil, err
|
||||
}
|
||||
defer indexJSON.Close()
|
||||
index := imgspecv1.Index{}
|
||||
if err := json.NewDecoder(indexJSON).Decode(&index); err != nil {
|
||||
|
||||
index := &imgspecv1.Index{}
|
||||
if err := json.NewDecoder(indexJSON).Decode(index); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return index, nil
|
||||
}
|
||||
|
||||
func (ref ociReference) getManifestDescriptor() (imgspecv1.Descriptor, error) {
|
||||
index, err := ref.getIndex()
|
||||
if err != nil {
|
||||
return imgspecv1.Descriptor{}, err
|
||||
}
|
||||
|
||||
@@ -250,7 +261,7 @@ func (ref ociReference) NewImageSource(ctx *types.SystemContext) (types.ImageSou
|
||||
// NewImageDestination returns a types.ImageDestination for this reference.
|
||||
// The caller must call .Close() on the returned ImageDestination.
|
||||
func (ref ociReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
return newImageDestination(ref)
|
||||
return newImageDestination(ctx, ref)
|
||||
}
|
||||
|
||||
// DeleteImage deletes the named image from the registry, if supported.
|
||||
@@ -269,9 +280,13 @@ func (ref ociReference) indexPath() string {
|
||||
}
|
||||
|
||||
// blobPath returns a path for a blob within a directory using OCI image-layout conventions.
|
||||
func (ref ociReference) blobPath(digest digest.Digest) (string, error) {
|
||||
func (ref ociReference) blobPath(digest digest.Digest, sharedBlobDir string) (string, error) {
|
||||
if err := digest.Validate(); err != nil {
|
||||
return "", errors.Wrapf(err, "unexpected digest reference %s", digest)
|
||||
}
|
||||
return filepath.Join(ref.dir, "blobs", digest.Algorithm().String(), digest.Hex()), nil
|
||||
blobDir := filepath.Join(ref.dir, "blobs")
|
||||
if sharedBlobDir != "" {
|
||||
blobDir = sharedBlobDir
|
||||
}
|
||||
return filepath.Join(blobDir, digest.Algorithm().String(), digest.Hex()), nil
|
||||
}
|
||||
|
||||
295
vendor/github.com/containers/image/pkg/docker/config/config.go
generated
vendored
Normal file
295
vendor/github.com/containers/image/pkg/docker/config/config.go
generated
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/types"
|
||||
helperclient "github.com/docker/docker-credential-helpers/client"
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
"github.com/docker/docker/pkg/homedir"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type dockerAuthConfig struct {
|
||||
Auth string `json:"auth,omitempty"`
|
||||
}
|
||||
|
||||
type dockerConfigFile struct {
|
||||
AuthConfigs map[string]dockerAuthConfig `json:"auths"`
|
||||
CredHelpers map[string]string `json:"credHelpers,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
defaultPath = "/run/user"
|
||||
authCfg = "containers"
|
||||
authCfgFileName = "auth.json"
|
||||
dockerCfg = ".docker"
|
||||
dockerCfgFileName = "config.json"
|
||||
dockerLegacyCfg = ".dockercfg"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotLoggedIn is returned for users not logged into a registry
|
||||
// that they are trying to logout of
|
||||
ErrNotLoggedIn = errors.New("not logged in")
|
||||
)
|
||||
|
||||
// SetAuthentication stores the username and password in the auth.json file
|
||||
func SetAuthentication(ctx *types.SystemContext, registry, username, password string) error {
|
||||
return modifyJSON(ctx, func(auths *dockerConfigFile) (bool, error) {
|
||||
if ch, exists := auths.CredHelpers[registry]; exists {
|
||||
return false, setAuthToCredHelper(ch, registry, username, password)
|
||||
}
|
||||
|
||||
creds := base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
|
||||
newCreds := dockerAuthConfig{Auth: creds}
|
||||
auths.AuthConfigs[registry] = newCreds
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetAuthentication returns the registry credentials stored in
|
||||
// either auth.json file or .docker/config.json
|
||||
// If an entry is not found empty strings are returned for the username and password
|
||||
func GetAuthentication(ctx *types.SystemContext, registry string) (string, string, error) {
|
||||
if ctx != nil && ctx.DockerAuthConfig != nil {
|
||||
return ctx.DockerAuthConfig.Username, ctx.DockerAuthConfig.Password, nil
|
||||
}
|
||||
|
||||
dockerLegacyPath := filepath.Join(homedir.Get(), dockerLegacyCfg)
|
||||
paths := [3]string{getPathToAuth(ctx), filepath.Join(homedir.Get(), dockerCfg, dockerCfgFileName), dockerLegacyPath}
|
||||
|
||||
for _, path := range paths {
|
||||
legacyFormat := path == dockerLegacyPath
|
||||
username, password, err := findAuthentication(registry, path, legacyFormat)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if username != "" && password != "" {
|
||||
return username, password, nil
|
||||
}
|
||||
}
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
// GetUserLoggedIn returns the username logged in to registry from either
|
||||
// auth.json or XDG_RUNTIME_DIR
|
||||
// Used to tell the user if someone is logged in to the registry when logging in
|
||||
func GetUserLoggedIn(ctx *types.SystemContext, registry string) string {
|
||||
path := getPathToAuth(ctx)
|
||||
username, _, _ := findAuthentication(registry, path, false)
|
||||
if username != "" {
|
||||
return username
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// RemoveAuthentication deletes the credentials stored in auth.json
|
||||
func RemoveAuthentication(ctx *types.SystemContext, registry string) error {
|
||||
return modifyJSON(ctx, func(auths *dockerConfigFile) (bool, error) {
|
||||
// First try cred helpers.
|
||||
if ch, exists := auths.CredHelpers[registry]; exists {
|
||||
return false, deleteAuthFromCredHelper(ch, registry)
|
||||
}
|
||||
|
||||
if _, ok := auths.AuthConfigs[registry]; ok {
|
||||
delete(auths.AuthConfigs, registry)
|
||||
} else if _, ok := auths.AuthConfigs[normalizeRegistry(registry)]; ok {
|
||||
delete(auths.AuthConfigs, normalizeRegistry(registry))
|
||||
} else {
|
||||
return false, ErrNotLoggedIn
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveAllAuthentication deletes all the credentials stored in auth.json
|
||||
func RemoveAllAuthentication(ctx *types.SystemContext) error {
|
||||
return modifyJSON(ctx, func(auths *dockerConfigFile) (bool, error) {
|
||||
auths.CredHelpers = make(map[string]string)
|
||||
auths.AuthConfigs = make(map[string]dockerAuthConfig)
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
// getPath gets the path of the auth.json file
|
||||
// The path can be overriden by the user if the overwrite-path flag is set
|
||||
// If the flag is not set and XDG_RUNTIME_DIR is ser, the auth.json file is saved in XDG_RUNTIME_DIR/containers
|
||||
// Otherwise, the auth.json file is stored in /run/user/UID/containers
|
||||
func getPathToAuth(ctx *types.SystemContext) string {
|
||||
if ctx != nil {
|
||||
if ctx.AuthFilePath != "" {
|
||||
return ctx.AuthFilePath
|
||||
}
|
||||
if ctx.RootForImplicitAbsolutePaths != "" {
|
||||
return filepath.Join(ctx.RootForImplicitAbsolutePaths, defaultPath, strconv.Itoa(os.Getuid()), authCfg, authCfgFileName)
|
||||
}
|
||||
}
|
||||
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
|
||||
if runtimeDir == "" {
|
||||
runtimeDir = filepath.Join(defaultPath, strconv.Itoa(os.Getuid()))
|
||||
}
|
||||
return filepath.Join(runtimeDir, authCfg, authCfgFileName)
|
||||
}
|
||||
|
||||
// readJSONFile unmarshals the authentications stored in the auth.json file and returns it
|
||||
// or returns an empty dockerConfigFile data structure if auth.json does not exist
|
||||
// if the file exists and is empty, readJSONFile returns an error
|
||||
func readJSONFile(path string, legacyFormat bool) (dockerConfigFile, error) {
|
||||
var auths dockerConfigFile
|
||||
|
||||
raw, err := ioutil.ReadFile(path)
|
||||
if os.IsNotExist(err) {
|
||||
auths.AuthConfigs = map[string]dockerAuthConfig{}
|
||||
return auths, nil
|
||||
}
|
||||
|
||||
if legacyFormat {
|
||||
if err = json.Unmarshal(raw, &auths.AuthConfigs); err != nil {
|
||||
return dockerConfigFile{}, errors.Wrapf(err, "error unmarshaling JSON at %q", path)
|
||||
}
|
||||
return auths, nil
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(raw, &auths); err != nil {
|
||||
return dockerConfigFile{}, errors.Wrapf(err, "error unmarshaling JSON at %q", path)
|
||||
}
|
||||
|
||||
return auths, nil
|
||||
}
|
||||
|
||||
// modifyJSON writes to auth.json if the dockerConfigFile has been updated
|
||||
func modifyJSON(ctx *types.SystemContext, editor func(auths *dockerConfigFile) (bool, error)) error {
|
||||
path := getPathToAuth(ctx)
|
||||
dir := filepath.Dir(path)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
if err = os.Mkdir(dir, 0700); err != nil {
|
||||
return errors.Wrapf(err, "error creating directory %q", dir)
|
||||
}
|
||||
}
|
||||
|
||||
auths, err := readJSONFile(path, false)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error reading JSON file %q", path)
|
||||
}
|
||||
|
||||
updated, err := editor(&auths)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error updating %q", path)
|
||||
}
|
||||
if updated {
|
||||
newData, err := json.MarshalIndent(auths, "", "\t")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error marshaling JSON %q", path)
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile(path, newData, 0755); err != nil {
|
||||
return errors.Wrapf(err, "error writing to file %q", path)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAuthFromCredHelper(credHelper, registry string) (string, string, error) {
|
||||
helperName := fmt.Sprintf("docker-credential-%s", credHelper)
|
||||
p := helperclient.NewShellProgramFunc(helperName)
|
||||
creds, err := helperclient.Get(p, registry)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return creds.Username, creds.Secret, nil
|
||||
}
|
||||
|
||||
func setAuthToCredHelper(credHelper, registry, username, password string) error {
|
||||
helperName := fmt.Sprintf("docker-credential-%s", credHelper)
|
||||
p := helperclient.NewShellProgramFunc(helperName)
|
||||
creds := &credentials.Credentials{
|
||||
ServerURL: registry,
|
||||
Username: username,
|
||||
Secret: password,
|
||||
}
|
||||
return helperclient.Store(p, creds)
|
||||
}
|
||||
|
||||
func deleteAuthFromCredHelper(credHelper, registry string) error {
|
||||
helperName := fmt.Sprintf("docker-credential-%s", credHelper)
|
||||
p := helperclient.NewShellProgramFunc(helperName)
|
||||
return helperclient.Erase(p, registry)
|
||||
}
|
||||
|
||||
// findAuthentication looks for auth of registry in path
|
||||
func findAuthentication(registry, path string, legacyFormat bool) (string, string, error) {
|
||||
auths, err := readJSONFile(path, legacyFormat)
|
||||
if err != nil {
|
||||
return "", "", errors.Wrapf(err, "error reading JSON file %q", path)
|
||||
}
|
||||
|
||||
// First try cred helpers. They should always be normalized.
|
||||
if ch, exists := auths.CredHelpers[registry]; exists {
|
||||
return getAuthFromCredHelper(ch, registry)
|
||||
}
|
||||
|
||||
// I'm feeling lucky
|
||||
if val, exists := auths.AuthConfigs[registry]; exists {
|
||||
return decodeDockerAuth(val.Auth)
|
||||
}
|
||||
|
||||
// bad luck; let's normalize the entries first
|
||||
registry = normalizeRegistry(registry)
|
||||
normalizedAuths := map[string]dockerAuthConfig{}
|
||||
for k, v := range auths.AuthConfigs {
|
||||
normalizedAuths[normalizeRegistry(k)] = v
|
||||
}
|
||||
if val, exists := normalizedAuths[registry]; exists {
|
||||
return decodeDockerAuth(val.Auth)
|
||||
}
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
func decodeDockerAuth(s string) (string, string, error) {
|
||||
decoded, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
parts := strings.SplitN(string(decoded), ":", 2)
|
||||
if len(parts) != 2 {
|
||||
// if it's invalid just skip, as docker does
|
||||
return "", "", nil
|
||||
}
|
||||
user := parts[0]
|
||||
password := strings.Trim(parts[1], "\x00")
|
||||
return user, password, nil
|
||||
}
|
||||
|
||||
// convertToHostname converts a registry url which has http|https prepended
|
||||
// to just an hostname.
|
||||
// Copied from github.com/docker/docker/registry/auth.go
|
||||
func convertToHostname(url string) string {
|
||||
stripped := url
|
||||
if strings.HasPrefix(url, "http://") {
|
||||
stripped = strings.TrimPrefix(url, "http://")
|
||||
} else if strings.HasPrefix(url, "https://") {
|
||||
stripped = strings.TrimPrefix(url, "https://")
|
||||
}
|
||||
|
||||
nameParts := strings.SplitN(stripped, "/", 2)
|
||||
|
||||
return nameParts[0]
|
||||
}
|
||||
|
||||
func normalizeRegistry(registry string) string {
|
||||
normalized := convertToHostname(registry)
|
||||
switch normalized {
|
||||
case "registry-1.docker.io", "docker.io":
|
||||
return "index.docker.io"
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
6
vendor/github.com/containers/image/signature/policy_config.go
generated
vendored
6
vendor/github.com/containers/image/signature/policy_config.go
generated
vendored
@@ -70,7 +70,11 @@ func NewPolicyFromFile(fileName string) (*Policy, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewPolicyFromBytes(contents)
|
||||
policy, err := NewPolicyFromBytes(contents)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid policy in %q", fileName)
|
||||
}
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
// NewPolicyFromBytes returns a policy parsed from the specified blob.
|
||||
|
||||
2
vendor/github.com/containers/image/storage/storage_image.go
generated
vendored
2
vendor/github.com/containers/image/storage/storage_image.go
generated
vendored
@@ -1,3 +1,5 @@
|
||||
// +build !containers_image_storage_stub
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
|
||||
2
vendor/github.com/containers/image/storage/storage_reference.go
generated
vendored
2
vendor/github.com/containers/image/storage/storage_reference.go
generated
vendored
@@ -1,3 +1,5 @@
|
||||
// +build !containers_image_storage_stub
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
|
||||
2
vendor/github.com/containers/image/storage/storage_transport.go
generated
vendored
2
vendor/github.com/containers/image/storage/storage_transport.go
generated
vendored
@@ -1,3 +1,5 @@
|
||||
// +build !containers_image_storage_stub
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
|
||||
48
vendor/github.com/containers/image/tarball/doc.go
generated
vendored
Normal file
48
vendor/github.com/containers/image/tarball/doc.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
// Package tarball provides a way to generate images using one or more layer
|
||||
// tarballs and an optional template configuration.
|
||||
//
|
||||
// An example:
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "fmt"
|
||||
//
|
||||
// cp "github.com/containers/image/copy"
|
||||
// "github.com/containers/image/tarball"
|
||||
// "github.com/containers/image/transports/alltransports"
|
||||
//
|
||||
// imgspecv1 "github.com/containers/image/transports/alltransports"
|
||||
// )
|
||||
//
|
||||
// func imageFromTarball() {
|
||||
// src, err := alltransports.ParseImageName("tarball:/var/cache/mock/fedora-26-x86_64/root_cache/cache.tar.gz")
|
||||
// // - or -
|
||||
// // src, err := tarball.Transport.ParseReference("/var/cache/mock/fedora-26-x86_64/root_cache/cache.tar.gz")
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// updater, ok := src.(tarball.ConfigUpdater)
|
||||
// if !ok {
|
||||
// panic("unexpected: a tarball reference should implement tarball.ConfigUpdater")
|
||||
// }
|
||||
// config := imgspecv1.Image{
|
||||
// Config: imgspecv1.ImageConfig{
|
||||
// Cmd: []string{"/bin/bash"},
|
||||
// },
|
||||
// }
|
||||
// annotations := make(map[string]string)
|
||||
// annotations[imgspecv1.AnnotationDescription] = "test image built from a mock root cache"
|
||||
// err = updater.ConfigUpdate(config, annotations)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// dest, err := alltransports.ParseImageName("docker-daemon:mock:latest")
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// err = cp.Image(nil, dest, src, nil)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// }
|
||||
package tarball
|
||||
88
vendor/github.com/containers/image/tarball/tarball_reference.go
generated
vendored
Normal file
88
vendor/github.com/containers/image/tarball/tarball_reference.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
package tarball
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
"github.com/containers/image/image"
|
||||
"github.com/containers/image/types"
|
||||
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// ConfigUpdater is an interface that ImageReferences for "tarball" images also
|
||||
// implement. It can be used to set values for a configuration, and to set
|
||||
// image annotations which will be present in the images returned by the
|
||||
// reference's NewImage() or NewImageSource() methods.
|
||||
type ConfigUpdater interface {
|
||||
ConfigUpdate(config imgspecv1.Image, annotations map[string]string) error
|
||||
}
|
||||
|
||||
type tarballReference struct {
|
||||
transport types.ImageTransport
|
||||
config imgspecv1.Image
|
||||
annotations map[string]string
|
||||
filenames []string
|
||||
stdin []byte
|
||||
}
|
||||
|
||||
// ConfigUpdate updates the image's default configuration and adds annotations
|
||||
// which will be visible in source images created using this reference.
|
||||
func (r *tarballReference) ConfigUpdate(config imgspecv1.Image, annotations map[string]string) error {
|
||||
r.config = config
|
||||
if r.annotations == nil {
|
||||
r.annotations = make(map[string]string)
|
||||
}
|
||||
for k, v := range annotations {
|
||||
r.annotations[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *tarballReference) Transport() types.ImageTransport {
|
||||
return r.transport
|
||||
}
|
||||
|
||||
func (r *tarballReference) StringWithinTransport() string {
|
||||
return strings.Join(r.filenames, ":")
|
||||
}
|
||||
|
||||
func (r *tarballReference) DockerReference() reference.Named {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *tarballReference) PolicyConfigurationIdentity() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r *tarballReference) PolicyConfigurationNamespaces() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *tarballReference) NewImage(ctx *types.SystemContext) (types.Image, error) {
|
||||
src, err := r.NewImageSource(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
img, err := image.FromSource(src)
|
||||
if err != nil {
|
||||
src.Close()
|
||||
return nil, err
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func (r *tarballReference) DeleteImage(ctx *types.SystemContext) error {
|
||||
for _, filename := range r.filenames {
|
||||
if err := os.Remove(filename); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("error removing %q: %v", filename, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *tarballReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
|
||||
return nil, fmt.Errorf("destination not implemented yet")
|
||||
}
|
||||
250
vendor/github.com/containers/image/tarball/tarball_src.go
generated
vendored
Normal file
250
vendor/github.com/containers/image/tarball/tarball_src.go
generated
vendored
Normal file
@@ -0,0 +1,250 @@
|
||||
package tarball
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/types"
|
||||
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
imgspecs "github.com/opencontainers/image-spec/specs-go"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
type tarballImageSource struct {
|
||||
reference tarballReference
|
||||
filenames []string
|
||||
diffIDs []digest.Digest
|
||||
diffSizes []int64
|
||||
blobIDs []digest.Digest
|
||||
blobSizes []int64
|
||||
blobTypes []string
|
||||
config []byte
|
||||
configID digest.Digest
|
||||
configSize int64
|
||||
manifest []byte
|
||||
}
|
||||
|
||||
func (r *tarballReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
|
||||
// Gather up the digests, sizes, and date information for all of the files.
|
||||
filenames := []string{}
|
||||
diffIDs := []digest.Digest{}
|
||||
diffSizes := []int64{}
|
||||
blobIDs := []digest.Digest{}
|
||||
blobSizes := []int64{}
|
||||
blobTimes := []time.Time{}
|
||||
blobTypes := []string{}
|
||||
for _, filename := range r.filenames {
|
||||
var file *os.File
|
||||
var err error
|
||||
var blobSize int64
|
||||
var blobTime time.Time
|
||||
var reader io.Reader
|
||||
if filename == "-" {
|
||||
blobSize = int64(len(r.stdin))
|
||||
blobTime = time.Now()
|
||||
reader = bytes.NewReader(r.stdin)
|
||||
} else {
|
||||
file, err = os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error opening %q for reading: %v", filename, err)
|
||||
}
|
||||
defer file.Close()
|
||||
reader = file
|
||||
fileinfo, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading size of %q: %v", filename, err)
|
||||
}
|
||||
blobSize = fileinfo.Size()
|
||||
blobTime = fileinfo.ModTime()
|
||||
}
|
||||
|
||||
// Default to assuming the layer is compressed.
|
||||
layerType := imgspecv1.MediaTypeImageLayerGzip
|
||||
|
||||
// Set up to digest the file as it is.
|
||||
blobIDdigester := digest.Canonical.Digester()
|
||||
reader = io.TeeReader(reader, blobIDdigester.Hash())
|
||||
|
||||
// Set up to digest the file after we maybe decompress it.
|
||||
diffIDdigester := digest.Canonical.Digester()
|
||||
uncompressed, err := gzip.NewReader(reader)
|
||||
if err == nil {
|
||||
// It is compressed, so the diffID is the digest of the uncompressed version
|
||||
reader = io.TeeReader(uncompressed, diffIDdigester.Hash())
|
||||
} else {
|
||||
// It is not compressed, so the diffID and the blobID are going to be the same
|
||||
diffIDdigester = blobIDdigester
|
||||
layerType = imgspecv1.MediaTypeImageLayer
|
||||
uncompressed = nil
|
||||
}
|
||||
n, err := io.Copy(ioutil.Discard, reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %q: %v", filename, err)
|
||||
}
|
||||
if uncompressed != nil {
|
||||
uncompressed.Close()
|
||||
}
|
||||
|
||||
// Grab our uncompressed and possibly-compressed digests and sizes.
|
||||
filenames = append(filenames, filename)
|
||||
diffIDs = append(diffIDs, diffIDdigester.Digest())
|
||||
diffSizes = append(diffSizes, n)
|
||||
blobIDs = append(blobIDs, blobIDdigester.Digest())
|
||||
blobSizes = append(blobSizes, blobSize)
|
||||
blobTimes = append(blobTimes, blobTime)
|
||||
blobTypes = append(blobTypes, layerType)
|
||||
}
|
||||
|
||||
// Build the rootfs and history for the configuration blob.
|
||||
rootfs := imgspecv1.RootFS{
|
||||
Type: "layers",
|
||||
DiffIDs: diffIDs,
|
||||
}
|
||||
created := time.Time{}
|
||||
history := []imgspecv1.History{}
|
||||
// Pick up the layer comment from the configuration's history list, if one is set.
|
||||
comment := "imported from tarball"
|
||||
if len(r.config.History) > 0 && r.config.History[0].Comment != "" {
|
||||
comment = r.config.History[0].Comment
|
||||
}
|
||||
for i := range diffIDs {
|
||||
createdBy := fmt.Sprintf("/bin/sh -c #(nop) ADD file:%s in %c", diffIDs[i].Hex(), os.PathSeparator)
|
||||
history = append(history, imgspecv1.History{
|
||||
Created: &blobTimes[i],
|
||||
CreatedBy: createdBy,
|
||||
Comment: comment,
|
||||
})
|
||||
// Use the mtime of the most recently modified file as the image's creation time.
|
||||
if created.Before(blobTimes[i]) {
|
||||
created = blobTimes[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Pick up other defaults from the config in the reference.
|
||||
config := r.config
|
||||
if config.Created == nil {
|
||||
config.Created = &created
|
||||
}
|
||||
if config.Architecture == "" {
|
||||
config.Architecture = runtime.GOARCH
|
||||
}
|
||||
if config.OS == "" {
|
||||
config.OS = runtime.GOOS
|
||||
}
|
||||
config.RootFS = rootfs
|
||||
config.History = history
|
||||
|
||||
// Encode and digest the image configuration blob.
|
||||
configBytes, err := json.Marshal(&config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error generating configuration blob for %q: %v", strings.Join(r.filenames, separator), err)
|
||||
}
|
||||
configID := digest.Canonical.FromBytes(configBytes)
|
||||
configSize := int64(len(configBytes))
|
||||
|
||||
// Populate a manifest with the configuration blob and the file as the single layer.
|
||||
layerDescriptors := []imgspecv1.Descriptor{}
|
||||
for i := range blobIDs {
|
||||
layerDescriptors = append(layerDescriptors, imgspecv1.Descriptor{
|
||||
Digest: blobIDs[i],
|
||||
Size: blobSizes[i],
|
||||
MediaType: blobTypes[i],
|
||||
})
|
||||
}
|
||||
annotations := make(map[string]string)
|
||||
for k, v := range r.annotations {
|
||||
annotations[k] = v
|
||||
}
|
||||
manifest := imgspecv1.Manifest{
|
||||
Versioned: imgspecs.Versioned{
|
||||
SchemaVersion: 2,
|
||||
},
|
||||
Config: imgspecv1.Descriptor{
|
||||
Digest: configID,
|
||||
Size: configSize,
|
||||
MediaType: imgspecv1.MediaTypeImageConfig,
|
||||
},
|
||||
Layers: layerDescriptors,
|
||||
Annotations: annotations,
|
||||
}
|
||||
|
||||
// Encode the manifest.
|
||||
manifestBytes, err := json.Marshal(&manifest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error generating manifest for %q: %v", strings.Join(r.filenames, separator), err)
|
||||
}
|
||||
|
||||
// Return the image.
|
||||
src := &tarballImageSource{
|
||||
reference: *r,
|
||||
filenames: filenames,
|
||||
diffIDs: diffIDs,
|
||||
diffSizes: diffSizes,
|
||||
blobIDs: blobIDs,
|
||||
blobSizes: blobSizes,
|
||||
blobTypes: blobTypes,
|
||||
config: configBytes,
|
||||
configID: configID,
|
||||
configSize: configSize,
|
||||
manifest: manifestBytes,
|
||||
}
|
||||
|
||||
return src, nil
|
||||
}
|
||||
|
||||
func (is *tarballImageSource) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (is *tarballImageSource) GetBlob(blobinfo types.BlobInfo) (io.ReadCloser, int64, error) {
|
||||
// We should only be asked about things in the manifest. Maybe the configuration blob.
|
||||
if blobinfo.Digest == is.configID {
|
||||
return ioutil.NopCloser(bytes.NewBuffer(is.config)), is.configSize, nil
|
||||
}
|
||||
// Maybe one of the layer blobs.
|
||||
for i := range is.blobIDs {
|
||||
if blobinfo.Digest == is.blobIDs[i] {
|
||||
// We want to read that layer: open the file or memory block and hand it back.
|
||||
if is.filenames[i] == "-" {
|
||||
return ioutil.NopCloser(bytes.NewBuffer(is.reference.stdin)), int64(len(is.reference.stdin)), nil
|
||||
}
|
||||
reader, err := os.Open(is.filenames[i])
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("error opening %q: %v", is.filenames[i], err)
|
||||
}
|
||||
return reader, is.blobSizes[i], nil
|
||||
}
|
||||
}
|
||||
return nil, -1, fmt.Errorf("no blob with digest %q found", blobinfo.Digest.String())
|
||||
}
|
||||
|
||||
func (is *tarballImageSource) GetManifest() ([]byte, string, error) {
|
||||
return is.manifest, imgspecv1.MediaTypeImageManifest, nil
|
||||
}
|
||||
|
||||
func (*tarballImageSource) GetSignatures(context.Context) ([][]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (*tarballImageSource) GetTargetManifest(digest.Digest) ([]byte, string, error) {
|
||||
return nil, "", fmt.Errorf("manifest lists are not supported by the %q transport", transportName)
|
||||
}
|
||||
|
||||
func (is *tarballImageSource) Reference() types.ImageReference {
|
||||
return &is.reference
|
||||
}
|
||||
|
||||
// UpdatedLayerInfos() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified.
|
||||
func (*tarballImageSource) UpdatedLayerInfos() []types.BlobInfo {
|
||||
return nil
|
||||
}
|
||||
66
vendor/github.com/containers/image/tarball/tarball_transport.go
generated
vendored
Normal file
66
vendor/github.com/containers/image/tarball/tarball_transport.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package tarball
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/transports"
|
||||
"github.com/containers/image/types"
|
||||
)
|
||||
|
||||
const (
|
||||
transportName = "tarball"
|
||||
separator = ":"
|
||||
)
|
||||
|
||||
var (
|
||||
// Transport implements the types.ImageTransport interface for "tarball:" images,
|
||||
// which are makeshift images constructed using one or more possibly-compressed tar
|
||||
// archives.
|
||||
Transport = &tarballTransport{}
|
||||
)
|
||||
|
||||
type tarballTransport struct {
|
||||
}
|
||||
|
||||
func (t *tarballTransport) Name() string {
|
||||
return transportName
|
||||
}
|
||||
|
||||
func (t *tarballTransport) ParseReference(reference string) (types.ImageReference, error) {
|
||||
var stdin []byte
|
||||
var err error
|
||||
filenames := strings.Split(reference, separator)
|
||||
for _, filename := range filenames {
|
||||
if filename == "-" {
|
||||
stdin, err = ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error buffering stdin: %v", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error opening %q: %v", filename, err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
ref := &tarballReference{
|
||||
transport: t,
|
||||
filenames: filenames,
|
||||
stdin: stdin,
|
||||
}
|
||||
return ref, nil
|
||||
}
|
||||
|
||||
func (t *tarballTransport) ValidatePolicyConfigurationScope(scope string) error {
|
||||
// See the explanation in daemonReference.PolicyConfigurationIdentity.
|
||||
return errors.New(`tarball: does not support any scopes except the default "" one`)
|
||||
}
|
||||
|
||||
func init() {
|
||||
transports.Register(Transport)
|
||||
}
|
||||
3
vendor/github.com/containers/image/transports/alltransports/alltransports.go
generated
vendored
3
vendor/github.com/containers/image/transports/alltransports/alltransports.go
generated
vendored
@@ -13,8 +13,9 @@ import (
|
||||
_ "github.com/containers/image/oci/archive"
|
||||
_ "github.com/containers/image/oci/layout"
|
||||
_ "github.com/containers/image/openshift"
|
||||
_ "github.com/containers/image/tarball"
|
||||
// The ostree transport is registered by ostree*.go
|
||||
_ "github.com/containers/image/storage"
|
||||
// The storage transport is registered by storage*.go
|
||||
"github.com/containers/image/transports"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
8
vendor/github.com/containers/image/transports/alltransports/storage.go
generated
vendored
Normal file
8
vendor/github.com/containers/image/transports/alltransports/storage.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
// +build !containers_image_storage_stub
|
||||
|
||||
package alltransports
|
||||
|
||||
import (
|
||||
// Register the storage transport
|
||||
_ "github.com/containers/image/storage"
|
||||
)
|
||||
9
vendor/github.com/containers/image/transports/alltransports/storage_stub.go
generated
vendored
Normal file
9
vendor/github.com/containers/image/transports/alltransports/storage_stub.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// +build containers_image_storage_stub
|
||||
|
||||
package alltransports
|
||||
|
||||
import "github.com/containers/image/transports"
|
||||
|
||||
func init() {
|
||||
transports.Register(transports.NewStubTransport("containers-storage"))
|
||||
}
|
||||
37
vendor/github.com/containers/image/types/types.go
generated
vendored
37
vendor/github.com/containers/image/types/types.go
generated
vendored
@@ -96,6 +96,7 @@ type BlobInfo struct {
|
||||
Size int64 // -1 if unknown
|
||||
URLs []string
|
||||
Annotations map[string]string
|
||||
MediaType string
|
||||
}
|
||||
|
||||
// ImageSource is a service, possibly remote (= slow), to download components of a single image.
|
||||
@@ -118,7 +119,7 @@ type ImageSource interface {
|
||||
// out of a manifest list.
|
||||
GetTargetManifest(digest digest.Digest) ([]byte, string, error)
|
||||
// GetBlob returns a stream for the specified blob, and the blob’s size (or -1 if unknown).
|
||||
// The Digest field in BlobInfo is guaranteed to be provided; Size may be -1.
|
||||
// The Digest field in BlobInfo is guaranteed to be provided, Size may be -1 and MediaType may be optionally provided.
|
||||
GetBlob(BlobInfo) (io.ReadCloser, int64, error)
|
||||
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
|
||||
GetSignatures(context.Context) ([][]byte, error)
|
||||
@@ -153,9 +154,10 @@ type ImageDestination interface {
|
||||
AcceptsForeignLayerURLs() bool
|
||||
// MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime OS. False otherwise.
|
||||
MustMatchRuntimeOS() bool
|
||||
// PutBlob writes contents of stream and returns data representing the result (with all data filled in).
|
||||
// PutBlob writes contents of stream and returns data representing the result.
|
||||
// inputInfo.Digest can be optionally provided if known; it is not mandatory for the implementation to verify it.
|
||||
// inputInfo.Size is the expected length of stream, if known.
|
||||
// inputInfo.MediaType describes the blob format, if known.
|
||||
// WARNING: The contents of stream are being verified on the fly. Until stream.Read() returns io.EOF, the contents of the data SHOULD NOT be available
|
||||
// to any other readers for download using the supplied digest.
|
||||
// If stream.Read() at any time, ESPECIALLY at end of input, returns an error, PutBlob MUST 1) fail, and 2) delete any data stored so far.
|
||||
@@ -215,7 +217,7 @@ type Image interface {
|
||||
// ConfigInfo returns a complete BlobInfo for the separate config object, or a BlobInfo{Digest:""} if there isn't a separate object.
|
||||
// Note that the config object may not exist in the underlying storage in the return value of UpdatedImage! Use ConfigBlob() below.
|
||||
ConfigInfo() BlobInfo
|
||||
// ConfigBlob returns the blob described by ConfigInfo, iff ConfigInfo().Digest != ""; nil otherwise.
|
||||
// ConfigBlob returns the blob described by ConfigInfo, if ConfigInfo().Digest != ""; nil otherwise.
|
||||
// The result is cached; it is OK to call this however often you need.
|
||||
ConfigBlob() ([]byte, error)
|
||||
// OCIConfig returns the image configuration as per OCI v1 image-spec. Information about
|
||||
@@ -223,7 +225,7 @@ type Image interface {
|
||||
// old image manifests work (docker v2s1 especially).
|
||||
OCIConfig() (*v1.Image, error)
|
||||
// LayerInfos returns a list of BlobInfos of layers referenced by this image, in order (the root layer first, and then successive layered layers).
|
||||
// The Digest field is guaranteed to be provided; Size may be -1.
|
||||
// The Digest field is guaranteed to be provided, Size may be -1 and MediaType may be optionally provided.
|
||||
// WARNING: The list may contain duplicates, and they are semantically relevant.
|
||||
LayerInfos() []BlobInfo
|
||||
// EmbeddedDockerReferenceConflicts whether a Docker reference embedded in the manifest, if any, conflicts with destination ref.
|
||||
@@ -249,7 +251,7 @@ type Image interface {
|
||||
|
||||
// ManifestUpdateOptions is a way to pass named optional arguments to Image.UpdatedManifest
|
||||
type ManifestUpdateOptions struct {
|
||||
LayerInfos []BlobInfo // Complete BlobInfos (size+digest+urls) which should replace the originals, in order (the root layer first, and then successive layered layers)
|
||||
LayerInfos []BlobInfo // Complete BlobInfos (size+digest+urls+annotations) which should replace the originals, in order (the root layer first, and then successive layered layers). BlobInfos' MediaType fields are ignored.
|
||||
EmbeddedDockerReference reference.Named
|
||||
ManifestMIMEType string
|
||||
// The values below are NOT requests to modify the image; they provide optional context which may or may not be used.
|
||||
@@ -283,7 +285,7 @@ type DockerAuthConfig struct {
|
||||
Password string
|
||||
}
|
||||
|
||||
// SystemContext allows parametrizing access to implicitly-accessed resources,
|
||||
// SystemContext allows parameterizing access to implicitly-accessed resources,
|
||||
// like configuration files in /etc and users' login state in their home directory.
|
||||
// Various components can share the same field only if their semantics is exactly
|
||||
// the same; if in doubt, add a new field.
|
||||
@@ -304,6 +306,8 @@ type SystemContext struct {
|
||||
RegistriesDirPath string
|
||||
// Path to the system-wide registries configuration file
|
||||
SystemRegistriesConfPath string
|
||||
// If not "", overrides the default path for the authentication file
|
||||
AuthFilePath string
|
||||
|
||||
// === OCI.Transport overrides ===
|
||||
// If not "", a directory containing a CA certificate (ending with ".crt"),
|
||||
@@ -312,6 +316,8 @@ type SystemContext struct {
|
||||
OCICertPath string
|
||||
// Allow downloading OCI image layers over HTTP, or HTTPS with failed TLS verification. Note that this does not affect other TLS connections.
|
||||
OCIInsecureSkipTLSVerify bool
|
||||
// If not "", use a shared directory for storing blobs rather than within OCI layouts
|
||||
OCISharedBlobDirPath string
|
||||
|
||||
// === docker.Transport overrides ===
|
||||
// If not "", a directory containing a CA certificate (ending with ".crt"),
|
||||
@@ -320,8 +326,9 @@ type SystemContext struct {
|
||||
DockerCertPath string
|
||||
// If not "", overrides the system’s default path for a directory containing host[:port] subdirectories with the same structure as DockerCertPath above.
|
||||
// Ignored if DockerCertPath is non-empty.
|
||||
DockerPerHostCertDirPath string
|
||||
DockerInsecureSkipTLSVerify bool // Allow contacting docker registries over HTTP, or HTTPS with failed TLS verification. Note that this does not affect other TLS connections.
|
||||
DockerPerHostCertDirPath string
|
||||
// Allow contacting docker registries over HTTP, or HTTPS with failed TLS verification. Note that this does not affect other TLS connections.
|
||||
DockerInsecureSkipTLSVerify bool
|
||||
// if nil, the library tries to parse ~/.docker/config.json to retrieve credentials
|
||||
DockerAuthConfig *DockerAuthConfig
|
||||
// if not "", an User-Agent header is added to each request when contacting a registry.
|
||||
@@ -332,6 +339,20 @@ type SystemContext struct {
|
||||
DockerDisableV1Ping bool
|
||||
// Directory to use for OSTree temporary files
|
||||
OSTreeTmpDirPath string
|
||||
|
||||
// === docker/daemon.Transport overrides ===
|
||||
// A directory containing a CA certificate (ending with ".crt"),
|
||||
// a client certificate (ending with ".cert") and a client certificate key
|
||||
// (ending with ".key") used when talking to a Docker daemon.
|
||||
DockerDaemonCertPath string
|
||||
// The hostname or IP to the Docker daemon. If not set (aka ""), client.DefaultDockerHost is assumed.
|
||||
DockerDaemonHost string
|
||||
// Used to skip TLS verification, off by default. To take effect DockerDaemonCertPath needs to be specified as well.
|
||||
DockerDaemonInsecureSkipTLSVerify bool
|
||||
|
||||
// === dir.Transport overrides ===
|
||||
// DirForceCompress compresses the image layers if set to true
|
||||
DirForceCompress bool
|
||||
}
|
||||
|
||||
// ProgressProperties is used to pass information from the copy code to a monitor which
|
||||
|
||||
2
vendor/github.com/containers/image/vendor.conf
generated
vendored
2
vendor/github.com/containers/image/vendor.conf
generated
vendored
@@ -22,7 +22,7 @@ github.com/pborman/uuid 1b00554d822231195d1babd97ff4a781231955c9
|
||||
github.com/pkg/errors 248dadf4e9068a0b3e79f02ed0a610d935de5302
|
||||
github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2
|
||||
github.com/stretchr/testify 4d4bfba8f1d1027c4fdbe371823030df51419987
|
||||
github.com/vbatts/tar-split bd4c5d64c3e9297f410025a3b1bd0c58f659e721
|
||||
github.com/vbatts/tar-split v0.10.2
|
||||
golang.org/x/crypto 453249f01cfeb54c3d549ddb75ff152ca243f9d8
|
||||
golang.org/x/net 6b27048ae5e6ad1ef927e72e437531493de612fe
|
||||
golang.org/x/sys 43e60d72a8e2bd92ee98319ba9a384a0e9837c08
|
||||
|
||||
23
vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go
generated
vendored
Normal file
23
vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
// +build linux
|
||||
|
||||
package homedir
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
)
|
||||
|
||||
// GetStatic returns the home directory for the current user without calling
|
||||
// os/user.Current(). This is useful for static-linked binary on glibc-based
|
||||
// system, because a call to os/user.Current() in a static binary leads to
|
||||
// segfault due to a glibc issue that won't be fixed in a short term.
|
||||
// (#29344, golang/go#13470, https://sourceware.org/bugzilla/show_bug.cgi?id=19341)
|
||||
func GetStatic() (string, error) {
|
||||
uid := os.Getuid()
|
||||
usr, err := idtools.LookupUID(uid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return usr.Home, nil
|
||||
}
|
||||
13
vendor/github.com/docker/docker/pkg/homedir/homedir_others.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/pkg/homedir/homedir_others.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// +build !linux
|
||||
|
||||
package homedir
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// GetStatic is not needed for non-linux systems.
|
||||
// (Precisely, it is needed only for glibc-based linux systems.)
|
||||
func GetStatic() (string, error) {
|
||||
return "", errors.New("homedir.GetStatic() is not supported on this system")
|
||||
}
|
||||
34
vendor/github.com/docker/docker/pkg/homedir/homedir_unix.go
generated
vendored
Normal file
34
vendor/github.com/docker/docker/pkg/homedir/homedir_unix.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// +build !windows
|
||||
|
||||
package homedir
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/user"
|
||||
)
|
||||
|
||||
// Key returns the env var name for the user's home dir based on
|
||||
// the platform being run on
|
||||
func Key() string {
|
||||
return "HOME"
|
||||
}
|
||||
|
||||
// Get returns the home directory of the current user with the help of
|
||||
// environment variables depending on the target operating system.
|
||||
// Returned path should be used with "path/filepath" to form new paths.
|
||||
func Get() string {
|
||||
home := os.Getenv(Key())
|
||||
if home == "" {
|
||||
if u, err := user.CurrentUser(); err == nil {
|
||||
return u.Home
|
||||
}
|
||||
}
|
||||
return home
|
||||
}
|
||||
|
||||
// GetShortcutString returns the string that is shortcut to user's home directory
|
||||
// in the native shell of the platform running on.
|
||||
func GetShortcutString() string {
|
||||
return "~"
|
||||
}
|
||||
24
vendor/github.com/docker/docker/pkg/homedir/homedir_windows.go
generated
vendored
Normal file
24
vendor/github.com/docker/docker/pkg/homedir/homedir_windows.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package homedir
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Key returns the env var name for the user's home dir based on
|
||||
// the platform being run on
|
||||
func Key() string {
|
||||
return "USERPROFILE"
|
||||
}
|
||||
|
||||
// Get returns the home directory of the current user with the help of
|
||||
// environment variables depending on the target operating system.
|
||||
// Returned path should be used with "path/filepath" to form new paths.
|
||||
func Get() string {
|
||||
return os.Getenv(Key())
|
||||
}
|
||||
|
||||
// GetShortcutString returns the string that is shortcut to user's home directory
|
||||
// in the native shell of the platform running on.
|
||||
func GetShortcutString() string {
|
||||
return "%USERPROFILE%" // be careful while using in format functions
|
||||
}
|
||||
138
vendor/github.com/vbatts/tar-split/README.md
generated
vendored
Normal file
138
vendor/github.com/vbatts/tar-split/README.md
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
# tar-split
|
||||
|
||||
[](https://travis-ci.org/vbatts/tar-split)
|
||||
[](https://goreportcard.com/report/github.com/vbatts/tar-split)
|
||||
|
||||
Pristinely disassembling a tar archive, and stashing needed raw bytes and offsets to reassemble a validating original archive.
|
||||
|
||||
## Docs
|
||||
|
||||
Code API for libraries provided by `tar-split`:
|
||||
|
||||
* https://godoc.org/github.com/vbatts/tar-split/tar/asm
|
||||
* https://godoc.org/github.com/vbatts/tar-split/tar/storage
|
||||
* https://godoc.org/github.com/vbatts/tar-split/archive/tar
|
||||
|
||||
## Install
|
||||
|
||||
The command line utilitiy is installable via:
|
||||
|
||||
```bash
|
||||
go get github.com/vbatts/tar-split/cmd/tar-split
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
For cli usage, see its [README.md](cmd/tar-split/README.md).
|
||||
For the library see the [docs](#docs)
|
||||
|
||||
## Demo
|
||||
|
||||
### Basic disassembly and assembly
|
||||
|
||||
This demonstrates the `tar-split` command and how to assemble a tar archive from the `tar-data.json.gz`
|
||||
|
||||
|
||||

|
||||
[youtube video of basic command demo](https://youtu.be/vh5wyjIOBtc)
|
||||
|
||||
### Docker layer preservation
|
||||
|
||||
This demonstrates the tar-split integration for docker-1.8. Providing consistent tar archives for the image layer content.
|
||||
|
||||

|
||||
[youtube vide of docker layer checksums](https://youtu.be/tV_Dia8E8xw)
|
||||
|
||||
## Caveat
|
||||
|
||||
Eventually this should detect TARs that this is not possible with.
|
||||
|
||||
For example stored sparse files that have "holes" in them, will be read as a
|
||||
contiguous file, though the archive contents may be recorded in sparse format.
|
||||
Therefore when adding the file payload to a reassembled tar, to achieve
|
||||
identical output, the file payload would need be precisely re-sparsified. This
|
||||
is not something I seek to fix immediately, but would rather have an alert that
|
||||
precise reassembly is not possible.
|
||||
(see more http://www.gnu.org/software/tar/manual/html_node/Sparse-Formats.html)
|
||||
|
||||
|
||||
Other caveat, while tar archives support having multiple file entries for the
|
||||
same path, we will not support this feature. If there are more than one entries
|
||||
with the same path, expect an err (like `ErrDuplicatePath`) or a resulting tar
|
||||
stream that does not validate your original checksum/signature.
|
||||
|
||||
## Contract
|
||||
|
||||
Do not break the API of stdlib `archive/tar` in our fork (ideally find an upstream mergeable solution).
|
||||
|
||||
## Std Version
|
||||
|
||||
The version of golang stdlib `archive/tar` is from go1.6
|
||||
It is minimally extended to expose the raw bytes of the TAR, rather than just the marshalled headers and file stream.
|
||||
|
||||
|
||||
## Design
|
||||
|
||||
See the [design](concept/DESIGN.md).
|
||||
|
||||
## Stored Metadata
|
||||
|
||||
Since the raw bytes of the headers and padding are stored, you may be wondering
|
||||
what the size implications are. The headers are at least 512 bytes per
|
||||
file (sometimes more), at least 1024 null bytes on the end, and then various
|
||||
padding. This makes for a constant linear growth in the stored metadata, with a
|
||||
naive storage implementation.
|
||||
|
||||
First we'll get an archive to work with. For repeatability, we'll make an
|
||||
archive from what you've just cloned:
|
||||
|
||||
```bash
|
||||
git archive --format=tar -o tar-split.tar HEAD .
|
||||
```
|
||||
|
||||
```bash
|
||||
$ go get github.com/vbatts/tar-split/cmd/tar-split
|
||||
$ tar-split checksize ./tar-split.tar
|
||||
inspecting "tar-split.tar" (size 210k)
|
||||
-- number of files: 50
|
||||
-- size of metadata uncompressed: 53k
|
||||
-- size of gzip compressed metadata: 3k
|
||||
```
|
||||
|
||||
So assuming you've managed the extraction of the archive yourself, for reuse of
|
||||
the file payloads from a relative path, then the only additional storage
|
||||
implications are as little as 3kb.
|
||||
|
||||
But let's look at a larger archive, with many files.
|
||||
|
||||
```bash
|
||||
$ ls -sh ./d.tar
|
||||
1.4G ./d.tar
|
||||
$ tar-split checksize ~/d.tar
|
||||
inspecting "/home/vbatts/d.tar" (size 1420749k)
|
||||
-- number of files: 38718
|
||||
-- size of metadata uncompressed: 43261k
|
||||
-- size of gzip compressed metadata: 2251k
|
||||
```
|
||||
|
||||
Here, an archive with 38,718 files has a compressed footprint of about 2mb.
|
||||
|
||||
Rolling the null bytes on the end of the archive, we will assume a
|
||||
bytes-per-file rate for the storage implications.
|
||||
|
||||
| uncompressed | compressed |
|
||||
| :----------: | :--------: |
|
||||
| ~ 1kb per/file | 0.06kb per/file |
|
||||
|
||||
|
||||
## What's Next?
|
||||
|
||||
* More implementations of storage Packer and Unpacker
|
||||
* More implementations of FileGetter and FilePutter
|
||||
* would be interesting to have an assembler stream that implements `io.Seeker`
|
||||
|
||||
|
||||
## License
|
||||
|
||||
See [LICENSE](LICENSE)
|
||||
|
||||
44
vendor/github.com/vbatts/tar-split/tar/asm/README.md
generated
vendored
Normal file
44
vendor/github.com/vbatts/tar-split/tar/asm/README.md
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
asm
|
||||
===
|
||||
|
||||
This library for assembly and disassembly of tar archives, facilitated by
|
||||
`github.com/vbatts/tar-split/tar/storage`.
|
||||
|
||||
|
||||
Concerns
|
||||
--------
|
||||
|
||||
For completely safe assembly/disassembly, there will need to be a Content
|
||||
Addressable Storage (CAS) directory, that maps to a checksum in the
|
||||
`storage.Entity` of `storage.FileType`.
|
||||
|
||||
This is due to the fact that tar archives _can_ allow multiple records for the
|
||||
same path, but the last one effectively wins. Even if the prior records had a
|
||||
different payload.
|
||||
|
||||
In this way, when assembling an archive from relative paths, if the archive has
|
||||
multiple entries for the same path, then all payloads read in from a relative
|
||||
path would be identical.
|
||||
|
||||
|
||||
Thoughts
|
||||
--------
|
||||
|
||||
Have a look-aside directory or storage. This way when a clobbering record is
|
||||
encountered from the tar stream, then the payload of the prior/existing file is
|
||||
stored to the CAS. This way the clobbering record's file payload can be
|
||||
extracted, but we'll have preserved the payload needed to reassemble a precise
|
||||
tar archive.
|
||||
|
||||
clobbered/path/to/file.[0-N]
|
||||
|
||||
*alternatively*
|
||||
|
||||
We could just _not_ support tar streams that have clobbering file paths.
|
||||
Appending records to the archive is not incredibly common, and doesn't happen
|
||||
by default for most implementations. Not supporting them wouldn't be a
|
||||
security concern either, as if it did occur, we would reassemble an archive
|
||||
that doesn't validate signature/checksum, so it shouldn't be trusted anyway.
|
||||
|
||||
Otherwise, this will allow us to defer support for appended files as a FUTURE FEATURE.
|
||||
|
||||
43
vendor/github.com/vbatts/tar-split/tar/asm/disassemble.go
generated
vendored
43
vendor/github.com/vbatts/tar-split/tar/asm/disassemble.go
generated
vendored
@@ -2,7 +2,6 @@ package asm
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/vbatts/tar-split/archive/tar"
|
||||
"github.com/vbatts/tar-split/tar/storage"
|
||||
@@ -119,20 +118,34 @@ func NewInputTarStream(r io.Reader, p storage.Packer, fp storage.FilePutter) (io
|
||||
}
|
||||
}
|
||||
|
||||
// it is allowable, and not uncommon that there is further padding on the
|
||||
// end of an archive, apart from the expected 1024 null bytes.
|
||||
remainder, err := ioutil.ReadAll(outputRdr)
|
||||
if err != nil && err != io.EOF {
|
||||
pW.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
_, err = p.AddEntry(storage.Entry{
|
||||
Type: storage.SegmentType,
|
||||
Payload: remainder,
|
||||
})
|
||||
if err != nil {
|
||||
pW.CloseWithError(err)
|
||||
return
|
||||
// It is allowable, and not uncommon that there is further padding on
|
||||
// the end of an archive, apart from the expected 1024 null bytes. We
|
||||
// do this in chunks rather than in one go to avoid cases where a
|
||||
// maliciously crafted tar file tries to trick us into reading many GBs
|
||||
// into memory.
|
||||
const paddingChunkSize = 1024 * 1024
|
||||
var paddingChunk [paddingChunkSize]byte
|
||||
for {
|
||||
var isEOF bool
|
||||
n, err := outputRdr.Read(paddingChunk[:])
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
pW.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
isEOF = true
|
||||
}
|
||||
_, err = p.AddEntry(storage.Entry{
|
||||
Type: storage.SegmentType,
|
||||
Payload: paddingChunk[:n],
|
||||
})
|
||||
if err != nil {
|
||||
pW.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
if isEOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
pW.Close()
|
||||
}()
|
||||
|
||||
Reference in New Issue
Block a user