mirror of https://github.com/docker/cli.git
vendor: github.com/docker/docker master (v24.0.0-dev)
- updates VolumeList() calls for docker/docker master - update fakeClient signature, and suppress err output in tests Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
parent
e86d2f4113
commit
bfa79fd75a
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/volume"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -66,13 +66,13 @@ func ContainerNames(dockerCli command.Cli, all bool, filters ...func(types.Conta
|
||||||
// VolumeNames offers completion for volumes
|
// VolumeNames offers completion for volumes
|
||||||
func VolumeNames(dockerCli command.Cli) ValidArgsFn {
|
func VolumeNames(dockerCli command.Cli) ValidArgsFn {
|
||||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
list, err := dockerCli.Client().VolumeList(cmd.Context(), filters.Args{})
|
list, err := dockerCli.Client().VolumeList(cmd.Context(), volume.ListOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, cobra.ShellCompDirectiveError
|
return nil, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
var names []string
|
var names []string
|
||||||
for _, volume := range list.Volumes {
|
for _, vol := range list.Volumes {
|
||||||
names = append(names, volume.Name)
|
names = append(names, vol.Name)
|
||||||
}
|
}
|
||||||
return names, cobra.ShellCompDirectiveNoFileComp
|
return names, cobra.ShellCompDirectiveNoFileComp
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,7 +153,7 @@ func resolveLocalPath(localPath string) (absPath string, err error) {
|
||||||
if absPath, err = filepath.Abs(localPath); err != nil {
|
if absPath, err = filepath.Abs(localPath); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return archive.PreserveTrailingDotOrSeparator(absPath, localPath, filepath.Separator), nil
|
return archive.PreserveTrailingDotOrSeparator(absPath, localPath), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpConfig) (err error) {
|
func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpConfig) (err error) {
|
||||||
|
|
|
@ -32,9 +32,9 @@ func (c *fakeClient) VolumeInspect(_ context.Context, volumeID string) (volume.V
|
||||||
return volume.Volume{}, nil
|
return volume.Volume{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *fakeClient) VolumeList(_ context.Context, filter filters.Args) (volume.ListResponse, error) {
|
func (c *fakeClient) VolumeList(_ context.Context, options volume.ListOptions) (volume.ListResponse, error) {
|
||||||
if c.volumeListFunc != nil {
|
if c.volumeListFunc != nil {
|
||||||
return c.volumeListFunc(filter)
|
return c.volumeListFunc(options.Filters)
|
||||||
}
|
}
|
||||||
return volume.ListResponse{}, nil
|
return volume.ListResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
flagsHelper "github.com/docker/cli/cli/flags"
|
flagsHelper "github.com/docker/cli/cli/flags"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
|
"github.com/docker/docker/api/types/volume"
|
||||||
"github.com/fvbommel/sortorder"
|
"github.com/fvbommel/sortorder"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -52,7 +53,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
|
||||||
func runList(dockerCli command.Cli, options listOptions) error {
|
func runList(dockerCli command.Cli, options listOptions) error {
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
volumes, err := client.VolumeList(context.Background(), options.filter.Value())
|
volumes, err := client.VolumeList(context.Background(), volume.ListOptions{Filters: options.filter.Value()})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -70,9 +71,9 @@ func runList(dockerCli command.Cli, options listOptions) error {
|
||||||
|
|
||||||
// trick for filtering in place
|
// trick for filtering in place
|
||||||
n := 0
|
n := 0
|
||||||
for _, volume := range volumes.Volumes {
|
for _, vol := range volumes.Volumes {
|
||||||
if volume.ClusterVolume != nil {
|
if vol.ClusterVolume != nil {
|
||||||
volumes.Volumes[n] = volume
|
volumes.Volumes[n] = vol
|
||||||
n++
|
n++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
14
vendor.mod
14
vendor.mod
|
@ -6,11 +6,23 @@ module github.com/docker/cli
|
||||||
|
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
|
// This is github.com/docker/docker "master". Unfortunately, go modules version
|
||||||
|
// resolution is very broken, and updating the dependency to "master" without
|
||||||
|
// a replace rule will roll it back to v23.0.0-rc.2+incompatible, likely because
|
||||||
|
// that version is used by some dependencies (BuildKit, SwarmKit).
|
||||||
|
//
|
||||||
|
// Why 20.10.3? Go modules generates pseudo versions based on the latest non-
|
||||||
|
// pre-release on the branch that "looks like SemVer" (v20.10.2 in this case).
|
||||||
|
// Pseudo versions are prefixed with "vMajor.Minor.Patch+1", so the version
|
||||||
|
// becomes "v20.10.3-0.20230207102624-b5568723cee5+incompatible" (latest stable
|
||||||
|
// (v20.10.2) "+1" (v20.10.3), followed by the commit-date, and sha.
|
||||||
|
replace github.com/docker/docker => github.com/docker/docker v20.10.3-0.20230327175735-54130b542db4+incompatible
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/containerd/containerd v1.6.19
|
github.com/containerd/containerd v1.6.19
|
||||||
github.com/creack/pty v1.1.11
|
github.com/creack/pty v1.1.11
|
||||||
github.com/docker/distribution v2.8.1+incompatible
|
github.com/docker/distribution v2.8.1+incompatible
|
||||||
github.com/docker/docker v23.0.2+incompatible
|
github.com/docker/docker v23.0.2+incompatible // replaced; currently using master branch (v24.0.0-dev)
|
||||||
github.com/docker/docker-credential-helpers v0.7.0
|
github.com/docker/docker-credential-helpers v0.7.0
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
|
|
|
@ -31,6 +31,7 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8 h1:V8krnnfGj4pV65YLUm3C0/8bl7V5Nry2Pwvy3ru/wLc=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
|
@ -86,7 +87,6 @@ github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u9
|
||||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||||
github.com/containerd/containerd v1.6.19 h1:F0qgQPrG0P2JPgwpxWxYavrVeXAG0ezUIB9Z/4FTUAU=
|
github.com/containerd/containerd v1.6.19 h1:F0qgQPrG0P2JPgwpxWxYavrVeXAG0ezUIB9Z/4FTUAU=
|
||||||
github.com/containerd/containerd v1.6.19/go.mod h1:HZCDMn4v/Xl2579/MvtOC2M206i+JJ6VxFWU/NetrGY=
|
github.com/containerd/containerd v1.6.19/go.mod h1:HZCDMn4v/Xl2579/MvtOC2M206i+JJ6VxFWU/NetrGY=
|
||||||
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
|
|
||||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
@ -101,8 +101,8 @@ github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xb
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
||||||
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker v23.0.2+incompatible h1:q81C2qQ/EhPm8COZMUGOQYh4qLv4Xu6CXELJ3WK/mlU=
|
github.com/docker/docker v20.10.3-0.20230327175735-54130b542db4+incompatible h1:o0dZx10GkjPNRtKU8c6tzPhpo1voSggUBYuuiJE7Y44=
|
||||||
github.com/docker/docker v23.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v20.10.3-0.20230327175735-54130b542db4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
||||||
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||||
|
|
|
@ -29,6 +29,7 @@ Adam Pointer <adam.pointer@skybettingandgaming.com>
|
||||||
Adam Singer <financeCoding@gmail.com>
|
Adam Singer <financeCoding@gmail.com>
|
||||||
Adam Walz <adam@adamwalz.net>
|
Adam Walz <adam@adamwalz.net>
|
||||||
Adam Williams <awilliams@mirantis.com>
|
Adam Williams <awilliams@mirantis.com>
|
||||||
|
AdamKorcz <adam@adalogics.com>
|
||||||
Addam Hardy <addam.hardy@gmail.com>
|
Addam Hardy <addam.hardy@gmail.com>
|
||||||
Aditi Rajagopal <arajagopal@us.ibm.com>
|
Aditi Rajagopal <arajagopal@us.ibm.com>
|
||||||
Aditya <aditya@netroy.in>
|
Aditya <aditya@netroy.in>
|
||||||
|
@ -81,6 +82,7 @@ Alex Goodman <wagoodman@gmail.com>
|
||||||
Alex Nordlund <alexander.nordlund@nasdaq.com>
|
Alex Nordlund <alexander.nordlund@nasdaq.com>
|
||||||
Alex Olshansky <i@creagenics.com>
|
Alex Olshansky <i@creagenics.com>
|
||||||
Alex Samorukov <samm@os2.kiev.ua>
|
Alex Samorukov <samm@os2.kiev.ua>
|
||||||
|
Alex Stockinger <alex@atomicjar.com>
|
||||||
Alex Warhawk <ax.warhawk@gmail.com>
|
Alex Warhawk <ax.warhawk@gmail.com>
|
||||||
Alexander Artemenko <svetlyak.40wt@gmail.com>
|
Alexander Artemenko <svetlyak.40wt@gmail.com>
|
||||||
Alexander Boyd <alex@opengroove.org>
|
Alexander Boyd <alex@opengroove.org>
|
||||||
|
@ -198,6 +200,7 @@ Anusha Ragunathan <anusha.ragunathan@docker.com>
|
||||||
Anyu Wang <wanganyu@outlook.com>
|
Anyu Wang <wanganyu@outlook.com>
|
||||||
apocas <petermdias@gmail.com>
|
apocas <petermdias@gmail.com>
|
||||||
Arash Deshmeh <adeshmeh@ca.ibm.com>
|
Arash Deshmeh <adeshmeh@ca.ibm.com>
|
||||||
|
arcosx <arcosx@outlook.com>
|
||||||
ArikaChen <eaglesora@gmail.com>
|
ArikaChen <eaglesora@gmail.com>
|
||||||
Arko Dasgupta <arko@tetrate.io>
|
Arko Dasgupta <arko@tetrate.io>
|
||||||
Arnaud Lefebvre <a.lefebvre@outlook.fr>
|
Arnaud Lefebvre <a.lefebvre@outlook.fr>
|
||||||
|
@ -241,6 +244,7 @@ Benjamin Atkin <ben@benatkin.com>
|
||||||
Benjamin Baker <Benjamin.baker@utexas.edu>
|
Benjamin Baker <Benjamin.baker@utexas.edu>
|
||||||
Benjamin Boudreau <boudreau.benjamin@gmail.com>
|
Benjamin Boudreau <boudreau.benjamin@gmail.com>
|
||||||
Benjamin Böhmke <benjamin@boehmke.net>
|
Benjamin Böhmke <benjamin@boehmke.net>
|
||||||
|
Benjamin Wang <wachao@vmware.com>
|
||||||
Benjamin Yolken <yolken@stripe.com>
|
Benjamin Yolken <yolken@stripe.com>
|
||||||
Benny Ng <benny.tpng@gmail.com>
|
Benny Ng <benny.tpng@gmail.com>
|
||||||
Benoit Chesneau <bchesneau@gmail.com>
|
Benoit Chesneau <bchesneau@gmail.com>
|
||||||
|
@ -634,6 +638,7 @@ Eng Zer Jun <engzerjun@gmail.com>
|
||||||
Enguerran <engcolson@gmail.com>
|
Enguerran <engcolson@gmail.com>
|
||||||
Eohyung Lee <liquidnuker@gmail.com>
|
Eohyung Lee <liquidnuker@gmail.com>
|
||||||
epeterso <epeterson@breakpoint-labs.com>
|
epeterso <epeterson@breakpoint-labs.com>
|
||||||
|
er0k <er0k@er0k.net>
|
||||||
Eric Barch <barch@tomesoftware.com>
|
Eric Barch <barch@tomesoftware.com>
|
||||||
Eric Curtin <ericcurtin17@gmail.com>
|
Eric Curtin <ericcurtin17@gmail.com>
|
||||||
Eric G. Noriega <enoriega@vizuri.com>
|
Eric G. Noriega <enoriega@vizuri.com>
|
||||||
|
@ -754,6 +759,7 @@ Félix Baylac-Jacqué <baylac.felix@gmail.com>
|
||||||
Félix Cantournet <felix.cantournet@cloudwatt.com>
|
Félix Cantournet <felix.cantournet@cloudwatt.com>
|
||||||
Gabe Rosenhouse <gabe@missionst.com>
|
Gabe Rosenhouse <gabe@missionst.com>
|
||||||
Gabor Nagy <mail@aigeruth.hu>
|
Gabor Nagy <mail@aigeruth.hu>
|
||||||
|
Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
|
||||||
Gabriel Goller <gabrielgoller123@gmail.com>
|
Gabriel Goller <gabrielgoller123@gmail.com>
|
||||||
Gabriel L. Somlo <gsomlo@gmail.com>
|
Gabriel L. Somlo <gsomlo@gmail.com>
|
||||||
Gabriel Linder <linder.gabriel@gmail.com>
|
Gabriel Linder <linder.gabriel@gmail.com>
|
||||||
|
@ -855,6 +861,7 @@ Hongbin Lu <hongbin034@gmail.com>
|
||||||
Hongxu Jia <hongxu.jia@windriver.com>
|
Hongxu Jia <hongxu.jia@windriver.com>
|
||||||
Honza Pokorny <me@honza.ca>
|
Honza Pokorny <me@honza.ca>
|
||||||
Hsing-Hui Hsu <hsinghui@amazon.com>
|
Hsing-Hui Hsu <hsinghui@amazon.com>
|
||||||
|
Hsing-Yu (David) Chen <davidhsingyuchen@gmail.com>
|
||||||
hsinko <21551195@zju.edu.cn>
|
hsinko <21551195@zju.edu.cn>
|
||||||
Hu Keping <hukeping@huawei.com>
|
Hu Keping <hukeping@huawei.com>
|
||||||
Hu Tao <hutao@cn.fujitsu.com>
|
Hu Tao <hutao@cn.fujitsu.com>
|
||||||
|
@ -887,6 +894,7 @@ Igor Dolzhikov <bluesriverz@gmail.com>
|
||||||
Igor Karpovich <i.karpovich@currencysolutions.com>
|
Igor Karpovich <i.karpovich@currencysolutions.com>
|
||||||
Iliana Weller <iweller@amazon.com>
|
Iliana Weller <iweller@amazon.com>
|
||||||
Ilkka Laukkanen <ilkka@ilkka.io>
|
Ilkka Laukkanen <ilkka@ilkka.io>
|
||||||
|
Illia Antypenko <ilya@antipenko.pp.ua>
|
||||||
Illo Abdulrahim <abdulrahim.illo@nokia.com>
|
Illo Abdulrahim <abdulrahim.illo@nokia.com>
|
||||||
Ilya Dmitrichenko <errordeveloper@gmail.com>
|
Ilya Dmitrichenko <errordeveloper@gmail.com>
|
||||||
Ilya Gusev <mail@igusev.ru>
|
Ilya Gusev <mail@igusev.ru>
|
||||||
|
@ -938,6 +946,7 @@ Jamie Hannaford <jamie@limetree.org>
|
||||||
Jamshid Afshar <jafshar@yahoo.com>
|
Jamshid Afshar <jafshar@yahoo.com>
|
||||||
Jan Breig <git@pygos.space>
|
Jan Breig <git@pygos.space>
|
||||||
Jan Chren <dev.rindeal@gmail.com>
|
Jan Chren <dev.rindeal@gmail.com>
|
||||||
|
Jan Garcia <github-public@n-garcia.com>
|
||||||
Jan Götte <jaseg@jaseg.net>
|
Jan Götte <jaseg@jaseg.net>
|
||||||
Jan Keromnes <janx@linux.com>
|
Jan Keromnes <janx@linux.com>
|
||||||
Jan Koprowski <jan.koprowski@gmail.com>
|
Jan Koprowski <jan.koprowski@gmail.com>
|
||||||
|
@ -1206,6 +1215,7 @@ Kimbro Staken <kstaken@kstaken.com>
|
||||||
Kir Kolyshkin <kolyshkin@gmail.com>
|
Kir Kolyshkin <kolyshkin@gmail.com>
|
||||||
Kiran Gangadharan <kiran.daredevil@gmail.com>
|
Kiran Gangadharan <kiran.daredevil@gmail.com>
|
||||||
Kirill SIbirev <l0kix2@gmail.com>
|
Kirill SIbirev <l0kix2@gmail.com>
|
||||||
|
Kirk Easterson <kirk.easterson@gmail.com>
|
||||||
knappe <tyler.knappe@gmail.com>
|
knappe <tyler.knappe@gmail.com>
|
||||||
Kohei Tsuruta <coheyxyz@gmail.com>
|
Kohei Tsuruta <coheyxyz@gmail.com>
|
||||||
Koichi Shiraishi <k@zchee.io>
|
Koichi Shiraishi <k@zchee.io>
|
||||||
|
@ -1240,10 +1250,12 @@ Lars Kellogg-Stedman <lars@redhat.com>
|
||||||
Lars R. Damerow <lars@pixar.com>
|
Lars R. Damerow <lars@pixar.com>
|
||||||
Lars-Magnus Skog <ralphtheninja@riseup.net>
|
Lars-Magnus Skog <ralphtheninja@riseup.net>
|
||||||
Laszlo Meszaros <lacienator@gmail.com>
|
Laszlo Meszaros <lacienator@gmail.com>
|
||||||
|
Laura Brehm <laurabrehm@hey.com>
|
||||||
Laura Frank <ljfrank@gmail.com>
|
Laura Frank <ljfrank@gmail.com>
|
||||||
Laurent Bernaille <laurent.bernaille@datadoghq.com>
|
Laurent Bernaille <laurent.bernaille@datadoghq.com>
|
||||||
Laurent Erignoux <lerignoux@gmail.com>
|
Laurent Erignoux <lerignoux@gmail.com>
|
||||||
Laurie Voss <github@seldo.com>
|
Laurie Voss <github@seldo.com>
|
||||||
|
Leandro Motta Barros <lmb@stackedboxes.org>
|
||||||
Leandro Siqueira <leandro.siqueira@gmail.com>
|
Leandro Siqueira <leandro.siqueira@gmail.com>
|
||||||
Lee Calcote <leecalcote@gmail.com>
|
Lee Calcote <leecalcote@gmail.com>
|
||||||
Lee Chao <932819864@qq.com>
|
Lee Chao <932819864@qq.com>
|
||||||
|
@ -1563,6 +1575,7 @@ Nick Neisen <nwneisen@gmail.com>
|
||||||
Nick Parker <nikaios@gmail.com>
|
Nick Parker <nikaios@gmail.com>
|
||||||
Nick Payne <nick@kurai.co.uk>
|
Nick Payne <nick@kurai.co.uk>
|
||||||
Nick Russo <nicholasjamesrusso@gmail.com>
|
Nick Russo <nicholasjamesrusso@gmail.com>
|
||||||
|
Nick Santos <nick.santos@docker.com>
|
||||||
Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
|
Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
|
||||||
Nick Stinemates <nick@stinemates.org>
|
Nick Stinemates <nick@stinemates.org>
|
||||||
Nick Wood <nwood@microsoft.com>
|
Nick Wood <nwood@microsoft.com>
|
||||||
|
@ -1584,6 +1597,7 @@ NikolaMandic <mn080202@gmail.com>
|
||||||
Nikolas Garofil <nikolas.garofil@uantwerpen.be>
|
Nikolas Garofil <nikolas.garofil@uantwerpen.be>
|
||||||
Nikolay Edigaryev <edigaryev@gmail.com>
|
Nikolay Edigaryev <edigaryev@gmail.com>
|
||||||
Nikolay Milovanov <nmil@itransformers.net>
|
Nikolay Milovanov <nmil@itransformers.net>
|
||||||
|
ningmingxiao <ning.mingxiao@zte.com.cn>
|
||||||
Nirmal Mehta <nirmalkmehta@gmail.com>
|
Nirmal Mehta <nirmalkmehta@gmail.com>
|
||||||
Nishant Totla <nishanttotla@gmail.com>
|
Nishant Totla <nishanttotla@gmail.com>
|
||||||
NIWA Hideyuki <niwa.niwa@nifty.ne.jp>
|
NIWA Hideyuki <niwa.niwa@nifty.ne.jp>
|
||||||
|
@ -1615,6 +1629,7 @@ Omri Shiv <Omri.Shiv@teradata.com>
|
||||||
Onur Filiz <onur.filiz@microsoft.com>
|
Onur Filiz <onur.filiz@microsoft.com>
|
||||||
Oriol Francès <oriolfa@gmail.com>
|
Oriol Francès <oriolfa@gmail.com>
|
||||||
Oscar Bonilla <6f6231@gmail.com>
|
Oscar Bonilla <6f6231@gmail.com>
|
||||||
|
oscar.chen <2972789494@qq.com>
|
||||||
Oskar Niburski <oskarniburski@gmail.com>
|
Oskar Niburski <oskarniburski@gmail.com>
|
||||||
Otto Kekäläinen <otto@seravo.fi>
|
Otto Kekäläinen <otto@seravo.fi>
|
||||||
Ouyang Liduo <oyld0210@163.com>
|
Ouyang Liduo <oyld0210@163.com>
|
||||||
|
@ -1822,6 +1837,7 @@ Rory Hunter <roryhunter2@gmail.com>
|
||||||
Rory McCune <raesene@gmail.com>
|
Rory McCune <raesene@gmail.com>
|
||||||
Ross Boucher <rboucher@gmail.com>
|
Ross Boucher <rboucher@gmail.com>
|
||||||
Rovanion Luckey <rovanion.luckey@gmail.com>
|
Rovanion Luckey <rovanion.luckey@gmail.com>
|
||||||
|
Roy Reznik <roy@wiz.io>
|
||||||
Royce Remer <royceremer@gmail.com>
|
Royce Remer <royceremer@gmail.com>
|
||||||
Rozhnov Alexandr <nox73@ya.ru>
|
Rozhnov Alexandr <nox73@ya.ru>
|
||||||
Rudolph Gottesheim <r.gottesheim@loot.at>
|
Rudolph Gottesheim <r.gottesheim@loot.at>
|
||||||
|
@ -2271,6 +2287,7 @@ Xiaoyu Zhang <zhang.xiaoyu33@zte.com.cn>
|
||||||
xichengliudui <1693291525@qq.com>
|
xichengliudui <1693291525@qq.com>
|
||||||
xiekeyang <xiekeyang@huawei.com>
|
xiekeyang <xiekeyang@huawei.com>
|
||||||
Ximo Guanter Gonzálbez <joaquin.guantergonzalbez@telefonica.com>
|
Ximo Guanter Gonzálbez <joaquin.guantergonzalbez@telefonica.com>
|
||||||
|
xin.li <xin.li@daocloud.io>
|
||||||
Xinbo Weng <xihuanbo_0521@zju.edu.cn>
|
Xinbo Weng <xihuanbo_0521@zju.edu.cn>
|
||||||
Xinfeng Liu <xinfeng.liu@gmail.com>
|
Xinfeng Liu <xinfeng.liu@gmail.com>
|
||||||
Xinzi Zhou <imdreamrunner@gmail.com>
|
Xinzi Zhou <imdreamrunner@gmail.com>
|
||||||
|
@ -2282,6 +2299,7 @@ Yahya <ya7yaz@gmail.com>
|
||||||
yalpul <yalpul@gmail.com>
|
yalpul <yalpul@gmail.com>
|
||||||
YAMADA Tsuyoshi <tyamada@minimum2scp.org>
|
YAMADA Tsuyoshi <tyamada@minimum2scp.org>
|
||||||
Yamasaki Masahide <masahide.y@gmail.com>
|
Yamasaki Masahide <masahide.y@gmail.com>
|
||||||
|
Yamazaki Masashi <masi19bw@gmail.com>
|
||||||
Yan Feng <yanfeng2@huawei.com>
|
Yan Feng <yanfeng2@huawei.com>
|
||||||
Yan Zhu <yanzhu@alauda.io>
|
Yan Zhu <yanzhu@alauda.io>
|
||||||
Yang Bai <hamo.by@gmail.com>
|
Yang Bai <hamo.by@gmail.com>
|
||||||
|
|
|
@ -3,7 +3,7 @@ package api // import "github.com/docker/docker/api"
|
||||||
// Common constants for daemon and client.
|
// Common constants for daemon and client.
|
||||||
const (
|
const (
|
||||||
// DefaultVersion of Current REST API
|
// DefaultVersion of Current REST API
|
||||||
DefaultVersion = "1.42"
|
DefaultVersion = "1.43"
|
||||||
|
|
||||||
// NoBaseImageSpecifier is the symbol used by the FROM
|
// NoBaseImageSpecifier is the symbol used by the FROM
|
||||||
// command to specify that no base image is to be used.
|
// command to specify that no base image is to be used.
|
||||||
|
|
|
@ -19,10 +19,10 @@ produces:
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
- "text/plain"
|
- "text/plain"
|
||||||
basePath: "/v1.42"
|
basePath: "/v1.43"
|
||||||
info:
|
info:
|
||||||
title: "Docker Engine API"
|
title: "Docker Engine API"
|
||||||
version: "1.42"
|
version: "1.43"
|
||||||
x-logo:
|
x-logo:
|
||||||
url: "https://docs.docker.com/assets/images/logo-docker-main.png"
|
url: "https://docs.docker.com/assets/images/logo-docker-main.png"
|
||||||
description: |
|
description: |
|
||||||
|
@ -55,8 +55,8 @@ info:
|
||||||
the URL is not supported by the daemon, a HTTP `400 Bad Request` error message
|
the URL is not supported by the daemon, a HTTP `400 Bad Request` error message
|
||||||
is returned.
|
is returned.
|
||||||
|
|
||||||
If you omit the version-prefix, the current version of the API (v1.42) is used.
|
If you omit the version-prefix, the current version of the API (v1.43) is used.
|
||||||
For example, calling `/info` is the same as calling `/v1.42/info`. Using the
|
For example, calling `/info` is the same as calling `/v1.43/info`. Using the
|
||||||
API without a version-prefix is deprecated and will be removed in a future release.
|
API without a version-prefix is deprecated and will be removed in a future release.
|
||||||
|
|
||||||
Engine releases in the near future should support this version of the API,
|
Engine releases in the near future should support this version of the API,
|
||||||
|
@ -4652,7 +4652,8 @@ definitions:
|
||||||
example: false
|
example: false
|
||||||
OOMKilled:
|
OOMKilled:
|
||||||
description: |
|
description: |
|
||||||
Whether this container has been killed because it ran out of memory.
|
Whether a process within this container has been killed because it ran
|
||||||
|
out of memory since the container was last started.
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
example: false
|
example: false
|
||||||
Dead:
|
Dead:
|
||||||
|
|
|
@ -1,22 +1,7 @@
|
||||||
package types // import "github.com/docker/docker/api/types"
|
package types // import "github.com/docker/docker/api/types"
|
||||||
|
import "github.com/docker/docker/api/types/registry"
|
||||||
|
|
||||||
// AuthConfig contains authorization information for connecting to a Registry
|
// AuthConfig contains authorization information for connecting to a Registry.
|
||||||
type AuthConfig struct {
|
//
|
||||||
Username string `json:"username,omitempty"`
|
// Deprecated: use github.com/docker/docker/api/types/registry.AuthConfig
|
||||||
Password string `json:"password,omitempty"`
|
type AuthConfig = registry.AuthConfig
|
||||||
Auth string `json:"auth,omitempty"`
|
|
||||||
|
|
||||||
// Email is an optional value associated with the username.
|
|
||||||
// This field is deprecated and will be removed in a later
|
|
||||||
// version of docker.
|
|
||||||
Email string `json:"email,omitempty"`
|
|
||||||
|
|
||||||
ServerAddress string `json:"serveraddress,omitempty"`
|
|
||||||
|
|
||||||
// IdentityToken is used to authenticate the user and get
|
|
||||||
// an access token for the registry.
|
|
||||||
IdentityToken string `json:"identitytoken,omitempty"`
|
|
||||||
|
|
||||||
// RegistryToken is a bearer token to be sent to a registry
|
|
||||||
RegistryToken string `json:"registrytoken,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/api/types/registry"
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -180,7 +181,7 @@ type ImageBuildOptions struct {
|
||||||
// at all (nil). See the parsing of buildArgs in
|
// at all (nil). See the parsing of buildArgs in
|
||||||
// api/server/router/build/build_routes.go for even more info.
|
// api/server/router/build/build_routes.go for even more info.
|
||||||
BuildArgs map[string]*string
|
BuildArgs map[string]*string
|
||||||
AuthConfigs map[string]AuthConfig
|
AuthConfigs map[string]registry.AuthConfig
|
||||||
Context io.Reader
|
Context io.Reader
|
||||||
Labels map[string]string
|
Labels map[string]string
|
||||||
// squash the resulting image's layers to the parent
|
// squash the resulting image's layers to the parent
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
package container // import "github.com/docker/docker/api/types/container"
|
|
||||||
|
|
||||||
// ContainerCreateCreatedBody OK response to ContainerCreate operation
|
|
||||||
//
|
|
||||||
// Deprecated: use CreateResponse
|
|
||||||
type ContainerCreateCreatedBody = CreateResponse
|
|
||||||
|
|
||||||
// ContainerWaitOKBody OK response to ContainerWait operation
|
|
||||||
//
|
|
||||||
// Deprecated: use WaitResponse
|
|
||||||
type ContainerWaitOKBody = WaitResponse
|
|
||||||
|
|
||||||
// ContainerWaitOKBodyError container waiting error, if any
|
|
||||||
//
|
|
||||||
// Deprecated: use WaitExitError
|
|
||||||
type ContainerWaitOKBodyError = WaitExitError
|
|
|
@ -101,7 +101,8 @@ func (n IpcMode) IsShareable() bool {
|
||||||
|
|
||||||
// IsContainer indicates whether the container uses another container's ipc namespace.
|
// IsContainer indicates whether the container uses another container's ipc namespace.
|
||||||
func (n IpcMode) IsContainer() bool {
|
func (n IpcMode) IsContainer() bool {
|
||||||
return strings.HasPrefix(string(n), string(IPCModeContainer)+":")
|
_, ok := containerID(string(n))
|
||||||
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNone indicates whether container IpcMode is set to "none".
|
// IsNone indicates whether container IpcMode is set to "none".
|
||||||
|
@ -116,15 +117,14 @@ func (n IpcMode) IsEmpty() bool {
|
||||||
|
|
||||||
// Valid indicates whether the ipc mode is valid.
|
// Valid indicates whether the ipc mode is valid.
|
||||||
func (n IpcMode) Valid() bool {
|
func (n IpcMode) Valid() bool {
|
||||||
|
// TODO(thaJeztah): align with PidMode, and consider container-mode without a container name/ID to be invalid.
|
||||||
return n.IsEmpty() || n.IsNone() || n.IsPrivate() || n.IsHost() || n.IsShareable() || n.IsContainer()
|
return n.IsEmpty() || n.IsNone() || n.IsPrivate() || n.IsHost() || n.IsShareable() || n.IsContainer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Container returns the name of the container ipc stack is going to be used.
|
// Container returns the name of the container ipc stack is going to be used.
|
||||||
func (n IpcMode) Container() string {
|
func (n IpcMode) Container() (idOrName string) {
|
||||||
if n.IsContainer() {
|
idOrName, _ = containerID(string(n))
|
||||||
return strings.TrimPrefix(string(n), string(IPCModeContainer)+":")
|
return idOrName
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkMode represents the container network stack.
|
// NetworkMode represents the container network stack.
|
||||||
|
@ -147,17 +147,14 @@ func (n NetworkMode) IsPrivate() bool {
|
||||||
|
|
||||||
// IsContainer indicates whether container uses a container network stack.
|
// IsContainer indicates whether container uses a container network stack.
|
||||||
func (n NetworkMode) IsContainer() bool {
|
func (n NetworkMode) IsContainer() bool {
|
||||||
parts := strings.SplitN(string(n), ":", 2)
|
_, ok := containerID(string(n))
|
||||||
return len(parts) > 1 && parts[0] == "container"
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConnectedContainer is the id of the container which network this container is connected to.
|
// ConnectedContainer is the id of the container which network this container is connected to.
|
||||||
func (n NetworkMode) ConnectedContainer() string {
|
func (n NetworkMode) ConnectedContainer() (idOrName string) {
|
||||||
parts := strings.SplitN(string(n), ":", 2)
|
idOrName, _ = containerID(string(n))
|
||||||
if len(parts) > 1 {
|
return idOrName
|
||||||
return parts[1]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserDefined indicates user-created network
|
// UserDefined indicates user-created network
|
||||||
|
@ -178,18 +175,12 @@ func (n UsernsMode) IsHost() bool {
|
||||||
|
|
||||||
// IsPrivate indicates whether the container uses the a private userns.
|
// IsPrivate indicates whether the container uses the a private userns.
|
||||||
func (n UsernsMode) IsPrivate() bool {
|
func (n UsernsMode) IsPrivate() bool {
|
||||||
return !(n.IsHost())
|
return !n.IsHost()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valid indicates whether the userns is valid.
|
// Valid indicates whether the userns is valid.
|
||||||
func (n UsernsMode) Valid() bool {
|
func (n UsernsMode) Valid() bool {
|
||||||
parts := strings.Split(string(n), ":")
|
return n == "" || n.IsHost()
|
||||||
switch mode := parts[0]; mode {
|
|
||||||
case "", "host":
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CgroupSpec represents the cgroup to use for the container.
|
// CgroupSpec represents the cgroup to use for the container.
|
||||||
|
@ -197,22 +188,20 @@ type CgroupSpec string
|
||||||
|
|
||||||
// IsContainer indicates whether the container is using another container cgroup
|
// IsContainer indicates whether the container is using another container cgroup
|
||||||
func (c CgroupSpec) IsContainer() bool {
|
func (c CgroupSpec) IsContainer() bool {
|
||||||
parts := strings.SplitN(string(c), ":", 2)
|
_, ok := containerID(string(c))
|
||||||
return len(parts) > 1 && parts[0] == "container"
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valid indicates whether the cgroup spec is valid.
|
// Valid indicates whether the cgroup spec is valid.
|
||||||
func (c CgroupSpec) Valid() bool {
|
func (c CgroupSpec) Valid() bool {
|
||||||
return c.IsContainer() || c == ""
|
// TODO(thaJeztah): align with PidMode, and consider container-mode without a container name/ID to be invalid.
|
||||||
|
return c == "" || c.IsContainer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Container returns the name of the container whose cgroup will be used.
|
// Container returns the ID or name of the container whose cgroup will be used.
|
||||||
func (c CgroupSpec) Container() string {
|
func (c CgroupSpec) Container() (idOrName string) {
|
||||||
parts := strings.SplitN(string(c), ":", 2)
|
idOrName, _ = containerID(string(c))
|
||||||
if len(parts) > 1 {
|
return idOrName
|
||||||
return parts[1]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UTSMode represents the UTS namespace of the container.
|
// UTSMode represents the UTS namespace of the container.
|
||||||
|
@ -220,7 +209,7 @@ type UTSMode string
|
||||||
|
|
||||||
// IsPrivate indicates whether the container uses its private UTS namespace.
|
// IsPrivate indicates whether the container uses its private UTS namespace.
|
||||||
func (n UTSMode) IsPrivate() bool {
|
func (n UTSMode) IsPrivate() bool {
|
||||||
return !(n.IsHost())
|
return !n.IsHost()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsHost indicates whether the container uses the host's UTS namespace.
|
// IsHost indicates whether the container uses the host's UTS namespace.
|
||||||
|
@ -230,13 +219,7 @@ func (n UTSMode) IsHost() bool {
|
||||||
|
|
||||||
// Valid indicates whether the UTS namespace is valid.
|
// Valid indicates whether the UTS namespace is valid.
|
||||||
func (n UTSMode) Valid() bool {
|
func (n UTSMode) Valid() bool {
|
||||||
parts := strings.Split(string(n), ":")
|
return n == "" || n.IsHost()
|
||||||
switch mode := parts[0]; mode {
|
|
||||||
case "", "host":
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PidMode represents the pid namespace of the container.
|
// PidMode represents the pid namespace of the container.
|
||||||
|
@ -254,32 +237,19 @@ func (n PidMode) IsHost() bool {
|
||||||
|
|
||||||
// IsContainer indicates whether the container uses a container's pid namespace.
|
// IsContainer indicates whether the container uses a container's pid namespace.
|
||||||
func (n PidMode) IsContainer() bool {
|
func (n PidMode) IsContainer() bool {
|
||||||
parts := strings.SplitN(string(n), ":", 2)
|
_, ok := containerID(string(n))
|
||||||
return len(parts) > 1 && parts[0] == "container"
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valid indicates whether the pid namespace is valid.
|
// Valid indicates whether the pid namespace is valid.
|
||||||
func (n PidMode) Valid() bool {
|
func (n PidMode) Valid() bool {
|
||||||
parts := strings.Split(string(n), ":")
|
return n == "" || n.IsHost() || validContainer(string(n))
|
||||||
switch mode := parts[0]; mode {
|
|
||||||
case "", "host":
|
|
||||||
case "container":
|
|
||||||
if len(parts) != 2 || parts[1] == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Container returns the name of the container whose pid namespace is going to be used.
|
// Container returns the name of the container whose pid namespace is going to be used.
|
||||||
func (n PidMode) Container() string {
|
func (n PidMode) Container() (idOrName string) {
|
||||||
parts := strings.SplitN(string(n), ":", 2)
|
idOrName, _ = containerID(string(n))
|
||||||
if len(parts) > 1 {
|
return idOrName
|
||||||
return parts[1]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeviceRequest represents a request for devices from a device driver.
|
// DeviceRequest represents a request for devices from a device driver.
|
||||||
|
@ -408,16 +378,17 @@ type UpdateConfig struct {
|
||||||
// Portable information *should* appear in Config.
|
// Portable information *should* appear in Config.
|
||||||
type HostConfig struct {
|
type HostConfig struct {
|
||||||
// Applicable to all platforms
|
// Applicable to all platforms
|
||||||
Binds []string // List of volume bindings for this container
|
Binds []string // List of volume bindings for this container
|
||||||
ContainerIDFile string // File (path) where the containerId is written
|
ContainerIDFile string // File (path) where the containerId is written
|
||||||
LogConfig LogConfig // Configuration of the logs for this container
|
LogConfig LogConfig // Configuration of the logs for this container
|
||||||
NetworkMode NetworkMode // Network mode to use for the container
|
NetworkMode NetworkMode // Network mode to use for the container
|
||||||
PortBindings nat.PortMap // Port mapping between the exposed port (container) and the host
|
PortBindings nat.PortMap // Port mapping between the exposed port (container) and the host
|
||||||
RestartPolicy RestartPolicy // Restart policy to be used for the container
|
RestartPolicy RestartPolicy // Restart policy to be used for the container
|
||||||
AutoRemove bool // Automatically remove container when it exits
|
AutoRemove bool // Automatically remove container when it exits
|
||||||
VolumeDriver string // Name of the volume driver used to mount volumes
|
VolumeDriver string // Name of the volume driver used to mount volumes
|
||||||
VolumesFrom []string // List of volumes to take from other container
|
VolumesFrom []string // List of volumes to take from other container
|
||||||
ConsoleSize [2]uint // Initial console size (height,width)
|
ConsoleSize [2]uint // Initial console size (height,width)
|
||||||
|
Annotations map[string]string `json:",omitempty"` // Arbitrary non-identifying metadata attached to container and provided to the runtime
|
||||||
|
|
||||||
// Applicable to UNIX platforms
|
// Applicable to UNIX platforms
|
||||||
CapAdd strslice.StrSlice // List of kernel capabilities to add to the container
|
CapAdd strslice.StrSlice // List of kernel capabilities to add to the container
|
||||||
|
@ -463,3 +434,23 @@ type HostConfig struct {
|
||||||
// Run a custom init inside the container, if null, use the daemon's configured settings
|
// Run a custom init inside the container, if null, use the daemon's configured settings
|
||||||
Init *bool `json:",omitempty"`
|
Init *bool `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// containerID splits "container:<ID|name>" values. It returns the container
|
||||||
|
// ID or name, and whether an ID/name was found. It returns an empty string and
|
||||||
|
// a "false" if the value does not have a "container:" prefix. Further validation
|
||||||
|
// of the returned, including checking if the value is empty, should be handled
|
||||||
|
// by the caller.
|
||||||
|
func containerID(val string) (idOrName string, ok bool) {
|
||||||
|
k, v, hasSep := strings.Cut(val, ":")
|
||||||
|
if !hasSep || k != "container" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return v, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// validContainer checks if the given value is a "container:" mode with
|
||||||
|
// a non-empty name/ID.
|
||||||
|
func validContainer(val string) bool {
|
||||||
|
id, ok := containerID(val)
|
||||||
|
return ok && id != ""
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
package types // import "github.com/docker/docker/api/types"
|
|
||||||
|
|
||||||
import "github.com/docker/docker/api/types/volume"
|
|
||||||
|
|
||||||
// Volume volume
|
|
||||||
//
|
|
||||||
// Deprecated: use github.com/docker/docker/api/types/volume.Volume
|
|
||||||
type Volume = volume.Volume
|
|
||||||
|
|
||||||
// VolumeUsageData Usage details about the volume. This information is used by the
|
|
||||||
// `GET /system/df` endpoint, and omitted in other endpoints.
|
|
||||||
//
|
|
||||||
// Deprecated: use github.com/docker/docker/api/types/volume.UsageData
|
|
||||||
type VolumeUsageData = volume.UsageData
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package filters
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// invalidFilter indicates that the provided filter or its value is invalid
|
||||||
|
type invalidFilter struct {
|
||||||
|
Filter string
|
||||||
|
Value []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e invalidFilter) Error() string {
|
||||||
|
msg := "invalid filter"
|
||||||
|
if e.Filter != "" {
|
||||||
|
msg += " '" + e.Filter
|
||||||
|
if e.Value != nil {
|
||||||
|
msg = fmt.Sprintf("%s=%s", msg, e.Value)
|
||||||
|
}
|
||||||
|
msg += "'"
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidParameter marks this error as ErrInvalidParameter
|
||||||
|
func (e invalidFilter) InvalidParameter() {}
|
||||||
|
|
||||||
|
// unreachableCode is an error indicating that the code path was not expected to be reached.
|
||||||
|
type unreachableCode struct {
|
||||||
|
Filter string
|
||||||
|
Value []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// System marks this error as ErrSystem
|
||||||
|
func (e unreachableCode) System() {}
|
||||||
|
|
||||||
|
func (e unreachableCode) Error() string {
|
||||||
|
return fmt.Sprintf("unreachable code reached for filter: %q with values: %s", e.Filter, e.Value)
|
||||||
|
}
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Args stores a mapping of keys to a set of multiple values.
|
// Args stores a mapping of keys to a set of multiple values.
|
||||||
|
@ -99,7 +98,7 @@ func FromJSON(p string) (Args, error) {
|
||||||
// Fallback to parsing arguments in the legacy slice format
|
// Fallback to parsing arguments in the legacy slice format
|
||||||
deprecated := map[string][]string{}
|
deprecated := map[string][]string{}
|
||||||
if legacyErr := json.Unmarshal(raw, &deprecated); legacyErr != nil {
|
if legacyErr := json.Unmarshal(raw, &deprecated); legacyErr != nil {
|
||||||
return args, invalidFilter{errors.Wrap(err, "invalid filter")}
|
return args, invalidFilter{}
|
||||||
}
|
}
|
||||||
|
|
||||||
args.fields = deprecatedArgs(deprecated)
|
args.fields = deprecatedArgs(deprecated)
|
||||||
|
@ -163,13 +162,13 @@ func (args Args) MatchKVList(key string, sources map[string]string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
for value := range fieldValues {
|
for value := range fieldValues {
|
||||||
testKV := strings.SplitN(value, "=", 2)
|
testK, testV, hasValue := strings.Cut(value, "=")
|
||||||
|
|
||||||
v, ok := sources[testKV[0]]
|
v, ok := sources[testK]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(testKV) == 2 && testKV[1] != v {
|
if hasValue && testV != v {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,6 +195,38 @@ func (args Args) Match(field, source string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBoolOrDefault returns a boolean value of the key if the key is present
|
||||||
|
// and is intepretable as a boolean value. Otherwise the default value is returned.
|
||||||
|
// Error is not nil only if the filter values are not valid boolean or are conflicting.
|
||||||
|
func (args Args) GetBoolOrDefault(key string, defaultValue bool) (bool, error) {
|
||||||
|
fieldValues, ok := args.fields[key]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return defaultValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fieldValues) == 0 {
|
||||||
|
return defaultValue, invalidFilter{key, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
isFalse := fieldValues["0"] || fieldValues["false"]
|
||||||
|
isTrue := fieldValues["1"] || fieldValues["true"]
|
||||||
|
|
||||||
|
conflicting := isFalse && isTrue
|
||||||
|
invalid := !isFalse && !isTrue
|
||||||
|
|
||||||
|
if conflicting || invalid {
|
||||||
|
return defaultValue, invalidFilter{key, args.Get(key)}
|
||||||
|
} else if isFalse {
|
||||||
|
return false, nil
|
||||||
|
} else if isTrue {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This code shouldn't be reached.
|
||||||
|
return defaultValue, unreachableCode{Filter: key, Value: args.Get(key)}
|
||||||
|
}
|
||||||
|
|
||||||
// ExactMatch returns true if the source matches exactly one of the values.
|
// ExactMatch returns true if the source matches exactly one of the values.
|
||||||
func (args Args) ExactMatch(key, source string) bool {
|
func (args Args) ExactMatch(key, source string) bool {
|
||||||
fieldValues, ok := args.fields[key]
|
fieldValues, ok := args.fields[key]
|
||||||
|
@ -246,20 +277,12 @@ func (args Args) Contains(field string) bool {
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
type invalidFilter struct{ error }
|
|
||||||
|
|
||||||
func (e invalidFilter) Error() string {
|
|
||||||
return e.error.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (invalidFilter) InvalidParameter() {}
|
|
||||||
|
|
||||||
// Validate compared the set of accepted keys against the keys in the mapping.
|
// Validate compared the set of accepted keys against the keys in the mapping.
|
||||||
// An error is returned if any mapping keys are not in the accepted set.
|
// An error is returned if any mapping keys are not in the accepted set.
|
||||||
func (args Args) Validate(accepted map[string]bool) error {
|
func (args Args) Validate(accepted map[string]bool) error {
|
||||||
for name := range args.fields {
|
for name := range args.fields {
|
||||||
if !accepted[name] {
|
if !accepted[name] {
|
||||||
return invalidFilter{errors.New("invalid filter '" + name + "'")}
|
return invalidFilter{name, nil}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
|
||||||
|
// GetImageOpts holds parameters to inspect an image.
|
||||||
|
type GetImageOpts struct {
|
||||||
|
Platform *specs.Platform
|
||||||
|
Details bool
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package registry // import "github.com/docker/docker/api/types/registry"
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthHeader is the name of the header used to send encoded registry
|
||||||
|
// authorization credentials for registry operations (push/pull).
|
||||||
|
const AuthHeader = "X-Registry-Auth"
|
||||||
|
|
||||||
|
// AuthConfig contains authorization information for connecting to a Registry.
|
||||||
|
type AuthConfig struct {
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
Auth string `json:"auth,omitempty"`
|
||||||
|
|
||||||
|
// Email is an optional value associated with the username.
|
||||||
|
// This field is deprecated and will be removed in a later
|
||||||
|
// version of docker.
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
|
||||||
|
ServerAddress string `json:"serveraddress,omitempty"`
|
||||||
|
|
||||||
|
// IdentityToken is used to authenticate the user and get
|
||||||
|
// an access token for the registry.
|
||||||
|
IdentityToken string `json:"identitytoken,omitempty"`
|
||||||
|
|
||||||
|
// RegistryToken is a bearer token to be sent to a registry
|
||||||
|
RegistryToken string `json:"registrytoken,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeAuthConfig serializes the auth configuration as a base64url encoded
|
||||||
|
// RFC4648, section 5) JSON string for sending through the X-Registry-Auth header.
|
||||||
|
//
|
||||||
|
// For details on base64url encoding, see:
|
||||||
|
// - RFC4648, section 5: https://tools.ietf.org/html/rfc4648#section-5
|
||||||
|
func EncodeAuthConfig(authConfig AuthConfig) (string, error) {
|
||||||
|
buf, err := json.Marshal(authConfig)
|
||||||
|
if err != nil {
|
||||||
|
return "", errInvalidParameter{err}
|
||||||
|
}
|
||||||
|
return base64.URLEncoding.EncodeToString(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeAuthConfig decodes base64url encoded (RFC4648, section 5) JSON
|
||||||
|
// authentication information as sent through the X-Registry-Auth header.
|
||||||
|
//
|
||||||
|
// This function always returns an AuthConfig, even if an error occurs. It is up
|
||||||
|
// to the caller to decide if authentication is required, and if the error can
|
||||||
|
// be ignored.
|
||||||
|
//
|
||||||
|
// For details on base64url encoding, see:
|
||||||
|
// - RFC4648, section 5: https://tools.ietf.org/html/rfc4648#section-5
|
||||||
|
func DecodeAuthConfig(authEncoded string) (*AuthConfig, error) {
|
||||||
|
if authEncoded == "" {
|
||||||
|
return &AuthConfig{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
||||||
|
return decodeAuthConfigFromReader(authJSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeAuthConfigBody decodes authentication information as sent as JSON in the
|
||||||
|
// body of a request. This function is to provide backward compatibility with old
|
||||||
|
// clients and API versions. Current clients and API versions expect authentication
|
||||||
|
// to be provided through the X-Registry-Auth header.
|
||||||
|
//
|
||||||
|
// Like DecodeAuthConfig, this function always returns an AuthConfig, even if an
|
||||||
|
// error occurs. It is up to the caller to decide if authentication is required,
|
||||||
|
// and if the error can be ignored.
|
||||||
|
func DecodeAuthConfigBody(rdr io.ReadCloser) (*AuthConfig, error) {
|
||||||
|
return decodeAuthConfigFromReader(rdr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeAuthConfigFromReader(rdr io.Reader) (*AuthConfig, error) {
|
||||||
|
authConfig := &AuthConfig{}
|
||||||
|
if err := json.NewDecoder(rdr).Decode(authConfig); err != nil {
|
||||||
|
// always return an (empty) AuthConfig to increase compatibility with
|
||||||
|
// the existing API.
|
||||||
|
return &AuthConfig{}, invalid(err)
|
||||||
|
}
|
||||||
|
return authConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func invalid(err error) error {
|
||||||
|
return errInvalidParameter{errors.Wrap(err, "invalid X-Registry-Auth header")}
|
||||||
|
}
|
||||||
|
|
||||||
|
type errInvalidParameter struct{ error }
|
||||||
|
|
||||||
|
func (errInvalidParameter) InvalidParameter() {}
|
||||||
|
|
||||||
|
func (e errInvalidParameter) Cause() error { return e.error }
|
||||||
|
|
||||||
|
func (e errInvalidParameter) Unwrap() error { return e.error }
|
|
@ -95,37 +95,37 @@ func GetTimestamp(value string, reference time.Time) (string, error) {
|
||||||
return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond())), nil
|
return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond())), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseTimestamps returns seconds and nanoseconds from a timestamp that has the
|
// ParseTimestamps returns seconds and nanoseconds from a timestamp that has
|
||||||
// format "%d.%09d", time.Unix(), int64(time.Nanosecond()))
|
// the format ("%d.%09d", time.Unix(), int64(time.Nanosecond())).
|
||||||
// if the incoming nanosecond portion is longer or shorter than 9 digits it is
|
// If the incoming nanosecond portion is longer than 9 digits it is truncated.
|
||||||
// converted to nanoseconds. The expectation is that the seconds and
|
// The expectation is that the seconds and nanoseconds will be used to create a
|
||||||
// seconds will be used to create a time variable. For example:
|
// time variable. For example:
|
||||||
//
|
//
|
||||||
// seconds, nanoseconds, err := ParseTimestamp("1136073600.000000001",0)
|
// seconds, nanoseconds, _ := ParseTimestamp("1136073600.000000001",0)
|
||||||
// if err == nil since := time.Unix(seconds, nanoseconds)
|
// since := time.Unix(seconds, nanoseconds)
|
||||||
//
|
//
|
||||||
// returns seconds as def(aultSeconds) if value == ""
|
// returns seconds as defaultSeconds if value == ""
|
||||||
func ParseTimestamps(value string, def int64) (int64, int64, error) {
|
func ParseTimestamps(value string, defaultSeconds int64) (seconds int64, nanoseconds int64, err error) {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return def, 0, nil
|
return defaultSeconds, 0, nil
|
||||||
}
|
}
|
||||||
return parseTimestamp(value)
|
return parseTimestamp(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTimestamp(value string) (int64, int64, error) {
|
func parseTimestamp(value string) (sec int64, nsec int64, err error) {
|
||||||
sa := strings.SplitN(value, ".", 2)
|
s, n, ok := strings.Cut(value, ".")
|
||||||
s, err := strconv.ParseInt(sa[0], 10, 64)
|
sec, err = strconv.ParseInt(s, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, 0, err
|
return sec, 0, err
|
||||||
}
|
}
|
||||||
if len(sa) != 2 {
|
if !ok {
|
||||||
return s, 0, nil
|
return sec, 0, nil
|
||||||
}
|
}
|
||||||
n, err := strconv.ParseInt(sa[1], 10, 64)
|
nsec, err = strconv.ParseInt(n, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return s, n, err
|
return sec, nsec, err
|
||||||
}
|
}
|
||||||
// should already be in nanoseconds but just in case convert n to nanoseconds
|
// should already be in nanoseconds but just in case convert n to nanoseconds
|
||||||
n = int64(float64(n) * math.Pow(float64(10), float64(9-len(sa[1]))))
|
nsec = int64(float64(nsec) * math.Pow(float64(10), float64(9-len(n))))
|
||||||
return s, n, nil
|
return sec, nsec, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -297,8 +297,6 @@ type Info struct {
|
||||||
Labels []string
|
Labels []string
|
||||||
ExperimentalBuild bool
|
ExperimentalBuild bool
|
||||||
ServerVersion string
|
ServerVersion string
|
||||||
ClusterStore string `json:",omitempty"` // Deprecated: host-discovery and overlay networks with external k/v stores are deprecated
|
|
||||||
ClusterAdvertise string `json:",omitempty"` // Deprecated: host-discovery and overlay networks with external k/v stores are deprecated
|
|
||||||
Runtimes map[string]Runtime
|
Runtimes map[string]Runtime
|
||||||
DefaultRuntime string
|
DefaultRuntime string
|
||||||
Swarm swarm.Info
|
Swarm swarm.Info
|
||||||
|
@ -350,20 +348,19 @@ func DecodeSecurityOptions(opts []string) ([]SecurityOpt, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
secopt := SecurityOpt{}
|
secopt := SecurityOpt{}
|
||||||
split := strings.Split(opt, ",")
|
for _, s := range strings.Split(opt, ",") {
|
||||||
for _, s := range split {
|
k, v, ok := strings.Cut(s, "=")
|
||||||
kv := strings.SplitN(s, "=", 2)
|
if !ok {
|
||||||
if len(kv) != 2 {
|
|
||||||
return nil, fmt.Errorf("invalid security option %q", s)
|
return nil, fmt.Errorf("invalid security option %q", s)
|
||||||
}
|
}
|
||||||
if kv[0] == "" || kv[1] == "" {
|
if k == "" || v == "" {
|
||||||
return nil, errors.New("invalid empty security option")
|
return nil, errors.New("invalid empty security option")
|
||||||
}
|
}
|
||||||
if kv[0] == "name" {
|
if k == "name" {
|
||||||
secopt.Name = kv[1]
|
secopt.Name = v
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
secopt.Options = append(secopt.Options, KeyValue{Key: kv[0], Value: kv[1]})
|
secopt.Options = append(secopt.Options, KeyValue{Key: k, Value: v})
|
||||||
}
|
}
|
||||||
so = append(so, secopt)
|
so = append(so, secopt)
|
||||||
}
|
}
|
||||||
|
@ -656,12 +653,18 @@ type Checkpoint struct {
|
||||||
|
|
||||||
// Runtime describes an OCI runtime
|
// Runtime describes an OCI runtime
|
||||||
type Runtime struct {
|
type Runtime struct {
|
||||||
Path string `json:"path"`
|
// "Legacy" runtime configuration for runc-compatible runtimes.
|
||||||
|
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
Args []string `json:"runtimeArgs,omitempty"`
|
Args []string `json:"runtimeArgs,omitempty"`
|
||||||
|
|
||||||
|
// Shimv2 runtime configuration. Mutually exclusive with the legacy config above.
|
||||||
|
|
||||||
|
Type string `json:"runtimeType,omitempty"`
|
||||||
|
Options map[string]interface{} `json:"options,omitempty"`
|
||||||
|
|
||||||
// This is exposed here only for internal use
|
// This is exposed here only for internal use
|
||||||
// It is not currently supported to specify custom shim configs
|
ShimConfig *ShimConfig `json:"-"`
|
||||||
Shim *ShimConfig `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShimConfig is used by runtime to configure containerd shims
|
// ShimConfig is used by runtime to configure containerd shims
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
package volume // import "github.com/docker/docker/api/types/volume"
|
|
||||||
|
|
||||||
// VolumeCreateBody Volume configuration
|
|
||||||
//
|
|
||||||
// Deprecated: use CreateOptions
|
|
||||||
type VolumeCreateBody = CreateOptions
|
|
||||||
|
|
||||||
// VolumeListOKBody Volume list response
|
|
||||||
//
|
|
||||||
// Deprecated: use ListResponse
|
|
||||||
type VolumeListOKBody = ListResponse
|
|
|
@ -4,12 +4,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/moby/sys/symlink"
|
"github.com/moby/sys/symlink"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
exec "golang.org/x/sys/execabs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type gitRepo struct {
|
type gitRepo struct {
|
||||||
|
@ -97,15 +97,10 @@ func parseRemoteURL(remoteURL string) (gitRepo, error) {
|
||||||
remoteURL = "https://" + remoteURL
|
remoteURL = "https://" + remoteURL
|
||||||
}
|
}
|
||||||
|
|
||||||
var fragment string
|
|
||||||
if strings.HasPrefix(remoteURL, "git@") {
|
if strings.HasPrefix(remoteURL, "git@") {
|
||||||
// git@.. is not an URL, so cannot be parsed as URL
|
// git@.. is not an URL, so cannot be parsed as URL
|
||||||
parts := strings.SplitN(remoteURL, "#", 2)
|
var fragment string
|
||||||
|
repo.remote, fragment, _ = strings.Cut(remoteURL, "#")
|
||||||
repo.remote = parts[0]
|
|
||||||
if len(parts) == 2 {
|
|
||||||
fragment = parts[1]
|
|
||||||
}
|
|
||||||
repo.ref, repo.subdir = getRefAndSubdir(fragment)
|
repo.ref, repo.subdir = getRefAndSubdir(fragment)
|
||||||
} else {
|
} else {
|
||||||
u, err := url.Parse(remoteURL)
|
u, err := url.Parse(remoteURL)
|
||||||
|
@ -126,15 +121,11 @@ func parseRemoteURL(remoteURL string) (gitRepo, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRefAndSubdir(fragment string) (ref string, subdir string) {
|
func getRefAndSubdir(fragment string) (ref string, subdir string) {
|
||||||
refAndDir := strings.SplitN(fragment, ":", 2)
|
ref, subdir, _ = strings.Cut(fragment, ":")
|
||||||
ref = "master"
|
if ref == "" {
|
||||||
if len(refAndDir[0]) != 0 {
|
ref = "master"
|
||||||
ref = refAndDir[0]
|
|
||||||
}
|
}
|
||||||
if len(refAndDir) > 1 && len(refAndDir[1]) != 0 {
|
return ref, subdir
|
||||||
subdir = refAndDir[1]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchArgs(remoteURL string, ref string) []string {
|
func fetchArgs(remoteURL string, ref string) []string {
|
||||||
|
|
|
@ -3,8 +3,8 @@ package client // import "github.com/docker/docker/client"
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
@ -23,12 +23,12 @@ func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePru
|
||||||
if opts.All {
|
if opts.All {
|
||||||
query.Set("all", "1")
|
query.Set("all", "1")
|
||||||
}
|
}
|
||||||
query.Set("keep-storage", fmt.Sprintf("%d", opts.KeepStorage))
|
query.Set("keep-storage", strconv.Itoa(int(opts.KeepStorage)))
|
||||||
filters, err := filters.ToJSON(opts.Filters)
|
f, err := filters.ToJSON(opts.Filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "prune could not marshal filters option")
|
return nil, errors.Wrap(err, "prune could not marshal filters option")
|
||||||
}
|
}
|
||||||
query.Set("filters", filters)
|
query.Set("filters", f)
|
||||||
|
|
||||||
serverResp, err := cli.post(ctx, "/build/prune", query, nil, nil)
|
serverResp, err := cli.post(ctx, "/build/prune", query, nil, nil)
|
||||||
defer ensureReaderClosed(serverResp)
|
defer ensureReaderClosed(serverResp)
|
||||||
|
@ -38,7 +38,7 @@ func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePru
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
|
if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
|
||||||
return nil, fmt.Errorf("Error retrieving disk usage: %v", err)
|
return nil, errors.Wrap(err, "error retrieving disk usage")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &report, nil
|
return &report, nil
|
||||||
|
|
|
@ -282,13 +282,12 @@ func (cli *Client) HTTPClient() *http.Client {
|
||||||
// ParseHostURL parses a url string, validates the string is a host url, and
|
// ParseHostURL parses a url string, validates the string is a host url, and
|
||||||
// returns the parsed URL
|
// returns the parsed URL
|
||||||
func ParseHostURL(host string) (*url.URL, error) {
|
func ParseHostURL(host string) (*url.URL, error) {
|
||||||
protoAddrParts := strings.SplitN(host, "://", 2)
|
proto, addr, ok := strings.Cut(host, "://")
|
||||||
if len(protoAddrParts) == 1 {
|
if !ok || addr == "" {
|
||||||
return nil, errors.Errorf("unable to parse docker host `%s`", host)
|
return nil, errors.Errorf("unable to parse docker host `%s`", host)
|
||||||
}
|
}
|
||||||
|
|
||||||
var basePath string
|
var basePath string
|
||||||
proto, addr := protoAddrParts[0], protoAddrParts[1]
|
|
||||||
if proto == "tcp" {
|
if proto == "tcp" {
|
||||||
parsed, err := url.Parse("tcp://" + addr)
|
parsed, err := url.Parse("tcp://" + addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//go:build linux || freebsd || openbsd || netbsd || darwin || solaris || illumos || dragonfly
|
//go:build !windows
|
||||||
// +build linux freebsd openbsd netbsd darwin solaris illumos dragonfly
|
// +build !windows
|
||||||
|
|
||||||
package client // import "github.com/docker/docker/client"
|
package client // import "github.com/docker/docker/client"
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,13 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
"github.com/docker/docker/api/types/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DistributionInspect returns the image digest with the full manifest.
|
// DistributionInspect returns the image digest with the full manifest.
|
||||||
func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegistryAuth string) (registrytypes.DistributionInspect, error) {
|
func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegistryAuth string) (registry.DistributionInspect, error) {
|
||||||
// Contact the registry to retrieve digest and platform information
|
// Contact the registry to retrieve digest and platform information
|
||||||
var distributionInspect registrytypes.DistributionInspect
|
var distributionInspect registry.DistributionInspect
|
||||||
if image == "" {
|
if image == "" {
|
||||||
return distributionInspect, objectNotFoundError{object: "distribution", id: image}
|
return distributionInspect, objectNotFoundError{object: "distribution", id: image}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegist
|
||||||
|
|
||||||
if encodedRegistryAuth != "" {
|
if encodedRegistryAuth != "" {
|
||||||
headers = map[string][]string{
|
headers = map[string][]string{
|
||||||
"X-Registry-Auth": {encodedRegistryAuth},
|
registry.AuthHeader: {encodedRegistryAuth},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,31 +58,6 @@ func (e objectNotFoundError) Error() string {
|
||||||
return fmt.Sprintf("Error: No such %s: %s", e.object, e.id)
|
return fmt.Sprintf("Error: No such %s: %s", e.object, e.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsErrUnauthorized returns true if the error is caused
|
|
||||||
// when a remote registry authentication fails
|
|
||||||
//
|
|
||||||
// Deprecated: use errdefs.IsUnauthorized
|
|
||||||
func IsErrUnauthorized(err error) bool {
|
|
||||||
return errdefs.IsUnauthorized(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
type pluginPermissionDenied struct {
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e pluginPermissionDenied) Error() string {
|
|
||||||
return "Permission denied while installing plugin " + e.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsErrNotImplemented returns true if the error is a NotImplemented error.
|
|
||||||
// This is returned by the API when a requested feature has not been
|
|
||||||
// implemented.
|
|
||||||
//
|
|
||||||
// Deprecated: use errdefs.IsNotImplemented
|
|
||||||
func IsErrNotImplemented(err error) bool {
|
|
||||||
return errdefs.IsNotImplemented(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewVersionError returns an error if the APIVersion required
|
// NewVersionError returns an error if the APIVersion required
|
||||||
// if less than the current supported version
|
// if less than the current supported version
|
||||||
func (cli *Client) NewVersionError(APIrequired, feature string) error {
|
func (cli *Client) NewVersionError(APIrequired, feature string) error {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ImageCreate creates a new image based on the parent options.
|
// ImageCreate creates a new image based on the parent options.
|
||||||
|
@ -32,6 +33,6 @@ func (cli *Client) ImageCreate(ctx context.Context, parentReference string, opti
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
|
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
|
||||||
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
|
headers := map[string][]string{registry.AuthHeader: {registryAuth}}
|
||||||
return cli.post(ctx, "/images/create", query, nil, headers)
|
return cli.post(ctx, "/images/create", query, nil, headers)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,6 +50,6 @@ func (cli *Client) ImagePush(ctx context.Context, image string, options types.Im
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, registryAuth string) (serverResponse, error) {
|
func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, registryAuth string) (serverResponse, error) {
|
||||||
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
|
headers := map[string][]string{registry.AuthHeader: {registryAuth}}
|
||||||
return cli.post(ctx, "/images/"+imageID+"/push", query, nil, headers)
|
return cli.post(ctx, "/images/"+imageID+"/push", query, nil, headers)
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,6 @@ func (cli *Client) ImageSearch(ctx context.Context, term string, options types.I
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) tryImageSearch(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
|
func (cli *Client) tryImageSearch(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
|
||||||
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
|
headers := map[string][]string{registry.AuthHeader: {registryAuth}}
|
||||||
return cli.get(ctx, "/images/search", query, headers)
|
return cli.get(ctx, "/images/search", query, headers)
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,7 +166,7 @@ type SwarmAPIClient interface {
|
||||||
type SystemAPIClient interface {
|
type SystemAPIClient interface {
|
||||||
Events(ctx context.Context, options types.EventsOptions) (<-chan events.Message, <-chan error)
|
Events(ctx context.Context, options types.EventsOptions) (<-chan events.Message, <-chan error)
|
||||||
Info(ctx context.Context) (types.Info, error)
|
Info(ctx context.Context) (types.Info, error)
|
||||||
RegistryLogin(ctx context.Context, auth types.AuthConfig) (registry.AuthenticateOKBody, error)
|
RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error)
|
||||||
DiskUsage(ctx context.Context, options types.DiskUsageOptions) (types.DiskUsage, error)
|
DiskUsage(ctx context.Context, options types.DiskUsageOptions) (types.DiskUsage, error)
|
||||||
Ping(ctx context.Context) (types.Ping, error)
|
Ping(ctx context.Context) (types.Ping, error)
|
||||||
}
|
}
|
||||||
|
@ -176,7 +176,7 @@ type VolumeAPIClient interface {
|
||||||
VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error)
|
VolumeCreate(ctx context.Context, options volume.CreateOptions) (volume.Volume, error)
|
||||||
VolumeInspect(ctx context.Context, volumeID string) (volume.Volume, error)
|
VolumeInspect(ctx context.Context, volumeID string) (volume.Volume, error)
|
||||||
VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error)
|
VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error)
|
||||||
VolumeList(ctx context.Context, filter filters.Args) (volume.ListResponse, error)
|
VolumeList(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error)
|
||||||
VolumeRemove(ctx context.Context, volumeID string, force bool) error
|
VolumeRemove(ctx context.Context, volumeID string, force bool) error
|
||||||
VolumesPrune(ctx context.Context, pruneFilter filters.Args) (types.VolumesPruneReport, error)
|
VolumesPrune(ctx context.Context, pruneFilter filters.Args) (types.VolumesPruneReport, error)
|
||||||
VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options volume.UpdateOptions) error
|
VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options volume.UpdateOptions) error
|
||||||
|
|
|
@ -5,13 +5,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/api/types/registry"
|
"github.com/docker/docker/api/types/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegistryLogin authenticates the docker server with a given docker registry.
|
// RegistryLogin authenticates the docker server with a given docker registry.
|
||||||
// It returns unauthorizedError when the authentication fails.
|
// It returns unauthorizedError when the authentication fails.
|
||||||
func (cli *Client) RegistryLogin(ctx context.Context, auth types.AuthConfig) (registry.AuthenticateOKBody, error) {
|
func (cli *Client) RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error) {
|
||||||
resp, err := cli.post(ctx, "/auth", url.Values{}, auth, nil)
|
resp, err := cli.post(ctx, "/auth", url.Values{}, auth, nil)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
|
|
||||||
|
|
|
@ -64,10 +64,10 @@ func parsePingResponse(cli *Client, resp serverResponse) (types.Ping, error) {
|
||||||
ping.BuilderVersion = types.BuilderVersion(bv)
|
ping.BuilderVersion = types.BuilderVersion(bv)
|
||||||
}
|
}
|
||||||
if si := resp.header.Get("Swarm"); si != "" {
|
if si := resp.header.Get("Swarm"); si != "" {
|
||||||
parts := strings.SplitN(si, "/", 2)
|
state, role, _ := strings.Cut(si, "/")
|
||||||
ping.SwarmStatus = &swarm.Status{
|
ping.SwarmStatus = &swarm.Status{
|
||||||
NodeState: swarm.LocalNodeState(parts[0]),
|
NodeState: swarm.LocalNodeState(state),
|
||||||
ControlAvailable: len(parts) == 2 && parts[1] == "manager",
|
ControlAvailable: role == "manager",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := cli.checkResponseErr(resp)
|
err := cli.checkResponseErr(resp)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -67,12 +68,12 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options types
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) tryPluginPrivileges(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
|
func (cli *Client) tryPluginPrivileges(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
|
||||||
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
|
headers := map[string][]string{registry.AuthHeader: {registryAuth}}
|
||||||
return cli.get(ctx, "/plugins/privileges", query, headers)
|
return cli.get(ctx, "/plugins/privileges", query, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, privileges types.PluginPrivileges, registryAuth string) (serverResponse, error) {
|
func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, privileges types.PluginPrivileges, registryAuth string) (serverResponse, error) {
|
||||||
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
|
headers := map[string][]string{registry.AuthHeader: {registryAuth}}
|
||||||
return cli.post(ctx, "/plugins/pull", query, privileges, headers)
|
return cli.post(ctx, "/plugins/pull", query, privileges, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +107,7 @@ func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !accept {
|
if !accept {
|
||||||
return nil, pluginPermissionDenied{options.RemoteRef}
|
return nil, errors.Errorf("permission denied while installing plugin %s", options.RemoteRef)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return privileges, nil
|
return privileges, nil
|
||||||
|
|
|
@ -3,11 +3,13 @@ package client // import "github.com/docker/docker/client"
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PluginPush pushes a plugin to a registry
|
// PluginPush pushes a plugin to a registry
|
||||||
func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) {
|
func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) {
|
||||||
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
|
headers := map[string][]string{registry.AuthHeader: {registryAuth}}
|
||||||
resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, headers)
|
resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/registry"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,6 +35,6 @@ func (cli *Client) PluginUpgrade(ctx context.Context, name string, options types
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) tryPluginUpgrade(ctx context.Context, query url.Values, privileges types.PluginPrivileges, name, registryAuth string) (serverResponse, error) {
|
func (cli *Client) tryPluginUpgrade(ctx context.Context, query url.Values, privileges types.PluginPrivileges, name, registryAuth string) (serverResponse, error) {
|
||||||
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
|
headers := map[string][]string{registry.AuthHeader: {registryAuth}}
|
||||||
return cli.post(ctx, "/plugins/"+name+"/upgrade", query, privileges, headers)
|
return cli.post(ctx, "/plugins/"+name+"/upgrade", query, privileges, headers)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -21,7 +22,7 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec,
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.EncodedRegistryAuth != "" {
|
if options.EncodedRegistryAuth != "" {
|
||||||
headers["X-Registry-Auth"] = []string{options.EncodedRegistryAuth}
|
headers[registry.AuthHeader] = []string{options.EncodedRegistryAuth}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure containerSpec is not nil when no runtime is set or the runtime is set to container
|
// Make sure containerSpec is not nil when no runtime is set or the runtime is set to container
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.EncodedRegistryAuth != "" {
|
if options.EncodedRegistryAuth != "" {
|
||||||
headers["X-Registry-Auth"] = []string{options.EncodedRegistryAuth}
|
headers[registry.AuthHeader] = []string{options.EncodedRegistryAuth}
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.RegistryAuthFrom != "" {
|
if options.RegistryAuthFrom != "" {
|
||||||
|
|
|
@ -10,13 +10,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// VolumeList returns the volumes configured in the docker host.
|
// VolumeList returns the volumes configured in the docker host.
|
||||||
func (cli *Client) VolumeList(ctx context.Context, filter filters.Args) (volume.ListResponse, error) {
|
func (cli *Client) VolumeList(ctx context.Context, options volume.ListOptions) (volume.ListResponse, error) {
|
||||||
var volumes volume.ListResponse
|
var volumes volume.ListResponse
|
||||||
query := url.Values{}
|
query := url.Values{}
|
||||||
|
|
||||||
if filter.Len() > 0 {
|
if options.Filters.Len() > 0 {
|
||||||
//nolint:staticcheck // ignore SA1019 for old code
|
//nolint:staticcheck // ignore SA1019 for old code
|
||||||
filterJSON, err := filters.ToParamWithVersion(cli.version, filter)
|
filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return volumes, err
|
return volumes, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
This code provides helper functions for dealing with archive files.
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// Package archive provides helper functions for dealing with archive files.
|
||||||
package archive // import "github.com/docker/docker/pkg/archive"
|
package archive // import "github.com/docker/docker/pkg/archive"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -11,6 +12,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -28,7 +30,6 @@ import (
|
||||||
"github.com/moby/sys/sequential"
|
"github.com/moby/sys/sequential"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
exec "golang.org/x/sys/execabs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ImpliedDirectoryMode represents the mode (Unix permissions) applied to directories that are implied by files in a
|
// ImpliedDirectoryMode represents the mode (Unix permissions) applied to directories that are implied by files in a
|
||||||
|
@ -111,16 +112,6 @@ const (
|
||||||
OverlayWhiteoutFormat
|
OverlayWhiteoutFormat
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
modeISDIR = 040000 // Directory
|
|
||||||
modeISFIFO = 010000 // FIFO
|
|
||||||
modeISREG = 0100000 // Regular file
|
|
||||||
modeISLNK = 0120000 // Symbolic link
|
|
||||||
modeISBLK = 060000 // Block special file
|
|
||||||
modeISCHR = 020000 // Character special file
|
|
||||||
modeISSOCK = 0140000 // Socket
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsArchivePath checks if the (possibly compressed) file at the given path
|
// IsArchivePath checks if the (possibly compressed) file at the given path
|
||||||
// starts with a tar file header.
|
// starts with a tar file header.
|
||||||
func IsArchivePath(path string) bool {
|
func IsArchivePath(path string) bool {
|
||||||
|
@ -469,9 +460,7 @@ func FileInfoHeaderNoLookups(fi os.FileInfo, link string) (*tar.Header, error) {
|
||||||
// but is safe to call from a chrooted process. The AccessTime and ChangeTime
|
// but is safe to call from a chrooted process. The AccessTime and ChangeTime
|
||||||
// fields are not set in the returned header, ModTime is truncated to one-second
|
// fields are not set in the returned header, ModTime is truncated to one-second
|
||||||
// precision, and the Uname and Gname fields are only set when fi is a FileInfo
|
// precision, and the Uname and Gname fields are only set when fi is a FileInfo
|
||||||
// value returned from tar.Header.FileInfo(). Also, regardless of Go version,
|
// value returned from tar.Header.FileInfo().
|
||||||
// this function fills file type bits (e.g. hdr.Mode |= modeISDIR), which have
|
|
||||||
// been deleted since Go 1.9 archive/tar.
|
|
||||||
func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, error) {
|
func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, error) {
|
||||||
hdr, err := FileInfoHeaderNoLookups(fi, link)
|
hdr, err := FileInfoHeaderNoLookups(fi, link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -481,36 +470,11 @@ func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, erro
|
||||||
hdr.ModTime = hdr.ModTime.Truncate(time.Second)
|
hdr.ModTime = hdr.ModTime.Truncate(time.Second)
|
||||||
hdr.AccessTime = time.Time{}
|
hdr.AccessTime = time.Time{}
|
||||||
hdr.ChangeTime = time.Time{}
|
hdr.ChangeTime = time.Time{}
|
||||||
hdr.Mode = fillGo18FileTypeBits(int64(chmodTarEntry(os.FileMode(hdr.Mode))), fi)
|
hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
|
||||||
hdr.Name = canonicalTarName(name, fi.IsDir())
|
hdr.Name = canonicalTarName(name, fi.IsDir())
|
||||||
return hdr, nil
|
return hdr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fillGo18FileTypeBits fills type bits which have been removed on Go 1.9 archive/tar
|
|
||||||
// https://github.com/golang/go/commit/66b5a2f
|
|
||||||
func fillGo18FileTypeBits(mode int64, fi os.FileInfo) int64 {
|
|
||||||
fm := fi.Mode()
|
|
||||||
switch {
|
|
||||||
case fm.IsRegular():
|
|
||||||
mode |= modeISREG
|
|
||||||
case fi.IsDir():
|
|
||||||
mode |= modeISDIR
|
|
||||||
case fm&os.ModeSymlink != 0:
|
|
||||||
mode |= modeISLNK
|
|
||||||
case fm&os.ModeDevice != 0:
|
|
||||||
if fm&os.ModeCharDevice != 0 {
|
|
||||||
mode |= modeISCHR
|
|
||||||
} else {
|
|
||||||
mode |= modeISBLK
|
|
||||||
}
|
|
||||||
case fm&os.ModeNamedPipe != 0:
|
|
||||||
mode |= modeISFIFO
|
|
||||||
case fm&os.ModeSocket != 0:
|
|
||||||
mode |= modeISSOCK
|
|
||||||
}
|
|
||||||
return mode
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem
|
// ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem
|
||||||
// to a tar header
|
// to a tar header
|
||||||
func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error {
|
func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error {
|
||||||
|
@ -567,10 +531,17 @@ func newTarAppender(idMapping idtools.IdentityMapping, writer io.Writer, chownOp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// canonicalTarName provides a platform-independent and consistent posix-style
|
// CanonicalTarNameForPath canonicalizes relativePath to a POSIX-style path using
|
||||||
|
// forward slashes. It is an alias for filepath.ToSlash, which is a no-op on
|
||||||
|
// Linux and Unix.
|
||||||
|
func CanonicalTarNameForPath(relativePath string) string {
|
||||||
|
return filepath.ToSlash(relativePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// canonicalTarName provides a platform-independent and consistent POSIX-style
|
||||||
// path for files and directories to be archived regardless of the platform.
|
// path for files and directories to be archived regardless of the platform.
|
||||||
func canonicalTarName(name string, isDir bool) string {
|
func canonicalTarName(name string, isDir bool) string {
|
||||||
name = CanonicalTarNameForPath(name)
|
name = filepath.ToSlash(name)
|
||||||
|
|
||||||
// suffix with '/' for directories
|
// suffix with '/' for directories
|
||||||
if isDir && !strings.HasSuffix(name, "/") {
|
if isDir && !strings.HasSuffix(name, "/") {
|
||||||
|
@ -850,10 +821,29 @@ func Tar(path string, compression Compression) (io.ReadCloser, error) {
|
||||||
// TarWithOptions creates an archive from the directory at `path`, only including files whose relative
|
// TarWithOptions creates an archive from the directory at `path`, only including files whose relative
|
||||||
// paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`.
|
// paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`.
|
||||||
func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) {
|
func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) {
|
||||||
// Fix the source path to work with long path names. This is a no-op
|
tb, err := NewTarballer(srcPath, options)
|
||||||
// on platforms other than Windows.
|
if err != nil {
|
||||||
srcPath = fixVolumePathPrefix(srcPath)
|
return nil, err
|
||||||
|
}
|
||||||
|
go tb.Do()
|
||||||
|
return tb.Reader(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tarballer is a lower-level interface to TarWithOptions which gives the caller
|
||||||
|
// control over which goroutine the archiving operation executes on.
|
||||||
|
type Tarballer struct {
|
||||||
|
srcPath string
|
||||||
|
options *TarOptions
|
||||||
|
pm *patternmatcher.PatternMatcher
|
||||||
|
pipeReader *io.PipeReader
|
||||||
|
pipeWriter *io.PipeWriter
|
||||||
|
compressWriter io.WriteCloser
|
||||||
|
whiteoutConverter tarWhiteoutConverter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTarballer constructs a new tarballer. The arguments are the same as for
|
||||||
|
// TarWithOptions.
|
||||||
|
func NewTarballer(srcPath string, options *TarOptions) (*Tarballer, error) {
|
||||||
pm, err := patternmatcher.New(options.ExcludePatterns)
|
pm, err := patternmatcher.New(options.ExcludePatterns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -871,183 +861,201 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
return &Tarballer{
|
||||||
ta := newTarAppender(
|
// Fix the source path to work with long path names. This is a no-op
|
||||||
options.IDMap,
|
// on platforms other than Windows.
|
||||||
compressWriter,
|
srcPath: fixVolumePathPrefix(srcPath),
|
||||||
options.ChownOpts,
|
options: options,
|
||||||
)
|
pm: pm,
|
||||||
ta.WhiteoutConverter = whiteoutConverter
|
pipeReader: pipeReader,
|
||||||
|
pipeWriter: pipeWriter,
|
||||||
|
compressWriter: compressWriter,
|
||||||
|
whiteoutConverter: whiteoutConverter,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
defer func() {
|
// Reader returns the reader for the created archive.
|
||||||
// Make sure to check the error on Close.
|
func (t *Tarballer) Reader() io.ReadCloser {
|
||||||
if err := ta.TarWriter.Close(); err != nil {
|
return t.pipeReader
|
||||||
logrus.Errorf("Can't close tar writer: %s", err)
|
}
|
||||||
}
|
|
||||||
if err := compressWriter.Close(); err != nil {
|
|
||||||
logrus.Errorf("Can't close compress writer: %s", err)
|
|
||||||
}
|
|
||||||
if err := pipeWriter.Close(); err != nil {
|
|
||||||
logrus.Errorf("Can't close pipe writer: %s", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// this buffer is needed for the duration of this piped stream
|
// Do performs the archiving operation in the background. The resulting archive
|
||||||
defer pools.BufioWriter32KPool.Put(ta.Buffer)
|
// can be read from t.Reader(). Do should only be called once on each Tarballer
|
||||||
|
// instance.
|
||||||
|
func (t *Tarballer) Do() {
|
||||||
|
ta := newTarAppender(
|
||||||
|
t.options.IDMap,
|
||||||
|
t.compressWriter,
|
||||||
|
t.options.ChownOpts,
|
||||||
|
)
|
||||||
|
ta.WhiteoutConverter = t.whiteoutConverter
|
||||||
|
|
||||||
// In general we log errors here but ignore them because
|
defer func() {
|
||||||
// during e.g. a diff operation the container can continue
|
// Make sure to check the error on Close.
|
||||||
// mutating the filesystem and we can see transient errors
|
if err := ta.TarWriter.Close(); err != nil {
|
||||||
// from this
|
logrus.Errorf("Can't close tar writer: %s", err)
|
||||||
|
|
||||||
stat, err := os.Lstat(srcPath)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
if err := t.compressWriter.Close(); err != nil {
|
||||||
if !stat.IsDir() {
|
logrus.Errorf("Can't close compress writer: %s", err)
|
||||||
// We can't later join a non-dir with any includes because the
|
|
||||||
// 'walk' will error if "file/." is stat-ed and "file" is not a
|
|
||||||
// directory. So, we must split the source path and use the
|
|
||||||
// basename as the include.
|
|
||||||
if len(options.IncludeFiles) > 0 {
|
|
||||||
logrus.Warn("Tar: Can't archive a file with includes")
|
|
||||||
}
|
|
||||||
|
|
||||||
dir, base := SplitPathDirEntry(srcPath)
|
|
||||||
srcPath = dir
|
|
||||||
options.IncludeFiles = []string{base}
|
|
||||||
}
|
}
|
||||||
|
if err := t.pipeWriter.Close(); err != nil {
|
||||||
if len(options.IncludeFiles) == 0 {
|
logrus.Errorf("Can't close pipe writer: %s", err)
|
||||||
options.IncludeFiles = []string{"."}
|
|
||||||
}
|
|
||||||
|
|
||||||
seen := make(map[string]bool)
|
|
||||||
|
|
||||||
for _, include := range options.IncludeFiles {
|
|
||||||
rebaseName := options.RebaseNames[include]
|
|
||||||
|
|
||||||
var (
|
|
||||||
parentMatchInfo []patternmatcher.MatchInfo
|
|
||||||
parentDirs []string
|
|
||||||
)
|
|
||||||
|
|
||||||
walkRoot := getWalkRoot(srcPath, include)
|
|
||||||
filepath.Walk(walkRoot, func(filePath string, f os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("Tar: Can't stat file %s to tar: %s", srcPath, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
relFilePath, err := filepath.Rel(srcPath, filePath)
|
|
||||||
if err != nil || (!options.IncludeSourceDir && relFilePath == "." && f.IsDir()) {
|
|
||||||
// Error getting relative path OR we are looking
|
|
||||||
// at the source directory path. Skip in both situations.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.IncludeSourceDir && include == "." && relFilePath != "." {
|
|
||||||
relFilePath = strings.Join([]string{".", relFilePath}, string(filepath.Separator))
|
|
||||||
}
|
|
||||||
|
|
||||||
skip := false
|
|
||||||
|
|
||||||
// If "include" is an exact match for the current file
|
|
||||||
// then even if there's an "excludePatterns" pattern that
|
|
||||||
// matches it, don't skip it. IOW, assume an explicit 'include'
|
|
||||||
// is asking for that file no matter what - which is true
|
|
||||||
// for some files, like .dockerignore and Dockerfile (sometimes)
|
|
||||||
if include != relFilePath {
|
|
||||||
for len(parentDirs) != 0 {
|
|
||||||
lastParentDir := parentDirs[len(parentDirs)-1]
|
|
||||||
if strings.HasPrefix(relFilePath, lastParentDir+string(os.PathSeparator)) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
parentDirs = parentDirs[:len(parentDirs)-1]
|
|
||||||
parentMatchInfo = parentMatchInfo[:len(parentMatchInfo)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
var matchInfo patternmatcher.MatchInfo
|
|
||||||
if len(parentMatchInfo) != 0 {
|
|
||||||
skip, matchInfo, err = pm.MatchesUsingParentResults(relFilePath, parentMatchInfo[len(parentMatchInfo)-1])
|
|
||||||
} else {
|
|
||||||
skip, matchInfo, err = pm.MatchesUsingParentResults(relFilePath, patternmatcher.MatchInfo{})
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("Error matching %s: %v", relFilePath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.IsDir() {
|
|
||||||
parentDirs = append(parentDirs, relFilePath)
|
|
||||||
parentMatchInfo = append(parentMatchInfo, matchInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if skip {
|
|
||||||
// If we want to skip this file and its a directory
|
|
||||||
// then we should first check to see if there's an
|
|
||||||
// excludes pattern (e.g. !dir/file) that starts with this
|
|
||||||
// dir. If so then we can't skip this dir.
|
|
||||||
|
|
||||||
// Its not a dir then so we can just return/skip.
|
|
||||||
if !f.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// No exceptions (!...) in patterns so just skip dir
|
|
||||||
if !pm.Exclusions() {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
|
|
||||||
dirSlash := relFilePath + string(filepath.Separator)
|
|
||||||
|
|
||||||
for _, pat := range pm.Patterns() {
|
|
||||||
if !pat.Exclusion() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(pat.String()+string(filepath.Separator), dirSlash) {
|
|
||||||
// found a match - so can't skip this dir
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// No matching exclusion dir so just skip dir
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
|
|
||||||
if seen[relFilePath] {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
seen[relFilePath] = true
|
|
||||||
|
|
||||||
// Rename the base resource.
|
|
||||||
if rebaseName != "" {
|
|
||||||
var replacement string
|
|
||||||
if rebaseName != string(filepath.Separator) {
|
|
||||||
// Special case the root directory to replace with an
|
|
||||||
// empty string instead so that we don't end up with
|
|
||||||
// double slashes in the paths.
|
|
||||||
replacement = rebaseName
|
|
||||||
}
|
|
||||||
|
|
||||||
relFilePath = strings.Replace(relFilePath, include, replacement, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ta.addTarFile(filePath, relFilePath); err != nil {
|
|
||||||
logrus.Errorf("Can't add file %s to tar: %s", filePath, err)
|
|
||||||
// if pipe is broken, stop writing tar stream to it
|
|
||||||
if err == io.ErrClosedPipe {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return pipeReader, nil
|
// this buffer is needed for the duration of this piped stream
|
||||||
|
defer pools.BufioWriter32KPool.Put(ta.Buffer)
|
||||||
|
|
||||||
|
// In general we log errors here but ignore them because
|
||||||
|
// during e.g. a diff operation the container can continue
|
||||||
|
// mutating the filesystem and we can see transient errors
|
||||||
|
// from this
|
||||||
|
|
||||||
|
stat, err := os.Lstat(t.srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !stat.IsDir() {
|
||||||
|
// We can't later join a non-dir with any includes because the
|
||||||
|
// 'walk' will error if "file/." is stat-ed and "file" is not a
|
||||||
|
// directory. So, we must split the source path and use the
|
||||||
|
// basename as the include.
|
||||||
|
if len(t.options.IncludeFiles) > 0 {
|
||||||
|
logrus.Warn("Tar: Can't archive a file with includes")
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, base := SplitPathDirEntry(t.srcPath)
|
||||||
|
t.srcPath = dir
|
||||||
|
t.options.IncludeFiles = []string{base}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(t.options.IncludeFiles) == 0 {
|
||||||
|
t.options.IncludeFiles = []string{"."}
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, include := range t.options.IncludeFiles {
|
||||||
|
rebaseName := t.options.RebaseNames[include]
|
||||||
|
|
||||||
|
var (
|
||||||
|
parentMatchInfo []patternmatcher.MatchInfo
|
||||||
|
parentDirs []string
|
||||||
|
)
|
||||||
|
|
||||||
|
walkRoot := getWalkRoot(t.srcPath, include)
|
||||||
|
filepath.WalkDir(walkRoot, func(filePath string, f os.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("Tar: Can't stat file %s to tar: %s", t.srcPath, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
relFilePath, err := filepath.Rel(t.srcPath, filePath)
|
||||||
|
if err != nil || (!t.options.IncludeSourceDir && relFilePath == "." && f.IsDir()) {
|
||||||
|
// Error getting relative path OR we are looking
|
||||||
|
// at the source directory path. Skip in both situations.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.options.IncludeSourceDir && include == "." && relFilePath != "." {
|
||||||
|
relFilePath = strings.Join([]string{".", relFilePath}, string(filepath.Separator))
|
||||||
|
}
|
||||||
|
|
||||||
|
skip := false
|
||||||
|
|
||||||
|
// If "include" is an exact match for the current file
|
||||||
|
// then even if there's an "excludePatterns" pattern that
|
||||||
|
// matches it, don't skip it. IOW, assume an explicit 'include'
|
||||||
|
// is asking for that file no matter what - which is true
|
||||||
|
// for some files, like .dockerignore and Dockerfile (sometimes)
|
||||||
|
if include != relFilePath {
|
||||||
|
for len(parentDirs) != 0 {
|
||||||
|
lastParentDir := parentDirs[len(parentDirs)-1]
|
||||||
|
if strings.HasPrefix(relFilePath, lastParentDir+string(os.PathSeparator)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
parentDirs = parentDirs[:len(parentDirs)-1]
|
||||||
|
parentMatchInfo = parentMatchInfo[:len(parentMatchInfo)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchInfo patternmatcher.MatchInfo
|
||||||
|
if len(parentMatchInfo) != 0 {
|
||||||
|
skip, matchInfo, err = t.pm.MatchesUsingParentResults(relFilePath, parentMatchInfo[len(parentMatchInfo)-1])
|
||||||
|
} else {
|
||||||
|
skip, matchInfo, err = t.pm.MatchesUsingParentResults(relFilePath, patternmatcher.MatchInfo{})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("Error matching %s: %v", relFilePath, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.IsDir() {
|
||||||
|
parentDirs = append(parentDirs, relFilePath)
|
||||||
|
parentMatchInfo = append(parentMatchInfo, matchInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if skip {
|
||||||
|
// If we want to skip this file and its a directory
|
||||||
|
// then we should first check to see if there's an
|
||||||
|
// excludes pattern (e.g. !dir/file) that starts with this
|
||||||
|
// dir. If so then we can't skip this dir.
|
||||||
|
|
||||||
|
// Its not a dir then so we can just return/skip.
|
||||||
|
if !f.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// No exceptions (!...) in patterns so just skip dir
|
||||||
|
if !t.pm.Exclusions() {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
|
||||||
|
dirSlash := relFilePath + string(filepath.Separator)
|
||||||
|
|
||||||
|
for _, pat := range t.pm.Patterns() {
|
||||||
|
if !pat.Exclusion() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(pat.String()+string(filepath.Separator), dirSlash) {
|
||||||
|
// found a match - so can't skip this dir
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No matching exclusion dir so just skip dir
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
|
||||||
|
if seen[relFilePath] {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
seen[relFilePath] = true
|
||||||
|
|
||||||
|
// Rename the base resource.
|
||||||
|
if rebaseName != "" {
|
||||||
|
var replacement string
|
||||||
|
if rebaseName != string(filepath.Separator) {
|
||||||
|
// Special case the root directory to replace with an
|
||||||
|
// empty string instead so that we don't end up with
|
||||||
|
// double slashes in the paths.
|
||||||
|
replacement = rebaseName
|
||||||
|
}
|
||||||
|
|
||||||
|
relFilePath = strings.Replace(relFilePath, include, replacement, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ta.addTarFile(filePath, relFilePath); err != nil {
|
||||||
|
logrus.Errorf("Can't add file %s to tar: %s", filePath, err)
|
||||||
|
// if pipe is broken, stop writing tar stream to it
|
||||||
|
if err == io.ErrClosedPipe {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unpack unpacks the decompressedArchive to dest with options.
|
// Unpack unpacks the decompressedArchive to dest with options.
|
||||||
|
|
|
@ -35,16 +35,8 @@ func getWalkRoot(srcPath string, include string) string {
|
||||||
return strings.TrimSuffix(srcPath, string(filepath.Separator)) + string(filepath.Separator) + include
|
return strings.TrimSuffix(srcPath, string(filepath.Separator)) + string(filepath.Separator) + include
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanonicalTarNameForPath returns platform-specific filepath
|
|
||||||
// to canonical posix-style path for tar archival. p is relative
|
|
||||||
// path.
|
|
||||||
func CanonicalTarNameForPath(p string) string {
|
|
||||||
return p // already unix-style
|
|
||||||
}
|
|
||||||
|
|
||||||
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
||||||
// on the platform the archival is done.
|
// on the platform the archival is done.
|
||||||
|
|
||||||
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
||||||
return perm // noop for unix as golang APIs provide perm bits correctly
|
return perm // noop for unix as golang APIs provide perm bits correctly
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,24 +21,14 @@ func getWalkRoot(srcPath string, include string) string {
|
||||||
return filepath.Join(srcPath, include)
|
return filepath.Join(srcPath, include)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanonicalTarNameForPath returns platform-specific filepath
|
|
||||||
// to canonical posix-style path for tar archival. p is relative
|
|
||||||
// path.
|
|
||||||
func CanonicalTarNameForPath(p string) string {
|
|
||||||
return filepath.ToSlash(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
||||||
// on the platform the archival is done.
|
// on the platform the archival is done.
|
||||||
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
||||||
// perm &= 0755 // this 0-ed out tar flags (like link, regular file, directory marker etc.)
|
// Remove group- and world-writable bits.
|
||||||
permPart := perm & os.ModePerm
|
perm &= 0o755
|
||||||
noPermPart := perm &^ os.ModePerm
|
|
||||||
// Add the x bit: make everything +x from windows
|
|
||||||
permPart |= 0111
|
|
||||||
permPart &= 0755
|
|
||||||
|
|
||||||
return noPermPart | permPart
|
// Add the x bit: make everything +x on Windows
|
||||||
|
return perm | 0o111
|
||||||
}
|
}
|
||||||
|
|
||||||
func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) (err error) {
|
func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) (err error) {
|
||||||
|
|
|
@ -41,7 +41,7 @@ func collectFileInfoForChanges(oldDir, newDir string) (*FileInfo, *FileInfo, err
|
||||||
func collectFileInfo(sourceDir string) (*FileInfo, error) {
|
func collectFileInfo(sourceDir string) (*FileInfo, error) {
|
||||||
root := newRootFileInfo()
|
root := newRootFileInfo()
|
||||||
|
|
||||||
err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error {
|
err := filepath.WalkDir(sourceDir, func(path string, _ os.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,23 +26,23 @@ var (
|
||||||
// path (from before being processed by utility functions from the path or
|
// path (from before being processed by utility functions from the path or
|
||||||
// filepath stdlib packages) ends with a trailing `/.` or `/`. If the cleaned
|
// filepath stdlib packages) ends with a trailing `/.` or `/`. If the cleaned
|
||||||
// path already ends in a `.` path segment, then another is not added. If the
|
// path already ends in a `.` path segment, then another is not added. If the
|
||||||
// clean path already ends in the separator, then another is not added.
|
// clean path already ends in a path separator, then another is not added.
|
||||||
func PreserveTrailingDotOrSeparator(cleanedPath string, originalPath string, sep byte) string {
|
func PreserveTrailingDotOrSeparator(cleanedPath string, originalPath string) string {
|
||||||
// Ensure paths are in platform semantics
|
// Ensure paths are in platform semantics
|
||||||
cleanedPath = strings.ReplaceAll(cleanedPath, "/", string(sep))
|
cleanedPath = normalizePath(cleanedPath)
|
||||||
originalPath = strings.ReplaceAll(originalPath, "/", string(sep))
|
originalPath = normalizePath(originalPath)
|
||||||
|
|
||||||
if !specifiesCurrentDir(cleanedPath) && specifiesCurrentDir(originalPath) {
|
if !specifiesCurrentDir(cleanedPath) && specifiesCurrentDir(originalPath) {
|
||||||
if !hasTrailingPathSeparator(cleanedPath, sep) {
|
if !hasTrailingPathSeparator(cleanedPath) {
|
||||||
// Add a separator if it doesn't already end with one (a cleaned
|
// Add a separator if it doesn't already end with one (a cleaned
|
||||||
// path would only end in a separator if it is the root).
|
// path would only end in a separator if it is the root).
|
||||||
cleanedPath += string(sep)
|
cleanedPath += string(filepath.Separator)
|
||||||
}
|
}
|
||||||
cleanedPath += "."
|
cleanedPath += "."
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasTrailingPathSeparator(cleanedPath, sep) && hasTrailingPathSeparator(originalPath, sep) {
|
if !hasTrailingPathSeparator(cleanedPath) && hasTrailingPathSeparator(originalPath) {
|
||||||
cleanedPath += string(sep)
|
cleanedPath += string(filepath.Separator)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cleanedPath
|
return cleanedPath
|
||||||
|
@ -51,14 +51,14 @@ func PreserveTrailingDotOrSeparator(cleanedPath string, originalPath string, sep
|
||||||
// assertsDirectory returns whether the given path is
|
// assertsDirectory returns whether the given path is
|
||||||
// asserted to be a directory, i.e., the path ends with
|
// asserted to be a directory, i.e., the path ends with
|
||||||
// a trailing '/' or `/.`, assuming a path separator of `/`.
|
// a trailing '/' or `/.`, assuming a path separator of `/`.
|
||||||
func assertsDirectory(path string, sep byte) bool {
|
func assertsDirectory(path string) bool {
|
||||||
return hasTrailingPathSeparator(path, sep) || specifiesCurrentDir(path)
|
return hasTrailingPathSeparator(path) || specifiesCurrentDir(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasTrailingPathSeparator returns whether the given
|
// hasTrailingPathSeparator returns whether the given
|
||||||
// path ends with the system's path separator character.
|
// path ends with the system's path separator character.
|
||||||
func hasTrailingPathSeparator(path string, sep byte) bool {
|
func hasTrailingPathSeparator(path string) bool {
|
||||||
return len(path) > 0 && path[len(path)-1] == sep
|
return len(path) > 0 && path[len(path)-1] == filepath.Separator
|
||||||
}
|
}
|
||||||
|
|
||||||
// specifiesCurrentDir returns whether the given path specifies
|
// specifiesCurrentDir returns whether the given path specifies
|
||||||
|
@ -285,7 +285,7 @@ func PrepareArchiveCopy(srcContent io.Reader, srcInfo, dstInfo CopyInfo) (dstDir
|
||||||
srcBase = srcInfo.RebaseName
|
srcBase = srcInfo.RebaseName
|
||||||
}
|
}
|
||||||
return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil
|
return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil
|
||||||
case assertsDirectory(dstInfo.Path, os.PathSeparator):
|
case assertsDirectory(dstInfo.Path):
|
||||||
// The destination does not exist and is asserted to be created as a
|
// The destination does not exist and is asserted to be created as a
|
||||||
// directory, but the source content is not a directory. This is an
|
// directory, but the source content is not a directory. This is an
|
||||||
// error condition since you cannot create a directory from a file
|
// error condition since you cannot create a directory from a file
|
||||||
|
@ -386,8 +386,8 @@ func CopyResource(srcPath, dstPath string, followLink bool) error {
|
||||||
dstPath = normalizePath(dstPath)
|
dstPath = normalizePath(dstPath)
|
||||||
|
|
||||||
// Clean the source and destination paths.
|
// Clean the source and destination paths.
|
||||||
srcPath = PreserveTrailingDotOrSeparator(filepath.Clean(srcPath), srcPath, os.PathSeparator)
|
srcPath = PreserveTrailingDotOrSeparator(filepath.Clean(srcPath), srcPath)
|
||||||
dstPath = PreserveTrailingDotOrSeparator(filepath.Clean(dstPath), dstPath, os.PathSeparator)
|
dstPath = PreserveTrailingDotOrSeparator(filepath.Clean(dstPath), dstPath)
|
||||||
|
|
||||||
if srcInfo, err = CopyInfoSourcePath(srcPath, followLink); err != nil {
|
if srcInfo, err = CopyInfoSourcePath(srcPath, followLink); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -450,7 +450,7 @@ func ResolveHostSourcePath(path string, followLink bool) (resolvedPath, rebaseNa
|
||||||
// resolvedDirPath will have been cleaned (no trailing path separators) so
|
// resolvedDirPath will have been cleaned (no trailing path separators) so
|
||||||
// we can manually join it with the base path element.
|
// we can manually join it with the base path element.
|
||||||
resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath
|
resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath
|
||||||
if hasTrailingPathSeparator(path, os.PathSeparator) &&
|
if hasTrailingPathSeparator(path) &&
|
||||||
filepath.Base(path) != filepath.Base(resolvedPath) {
|
filepath.Base(path) != filepath.Base(resolvedPath) {
|
||||||
rebaseName = filepath.Base(path)
|
rebaseName = filepath.Base(path)
|
||||||
}
|
}
|
||||||
|
@ -469,8 +469,8 @@ func GetRebaseName(path, resolvedPath string) (string, string) {
|
||||||
resolvedPath += string(filepath.Separator) + "."
|
resolvedPath += string(filepath.Separator) + "."
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasTrailingPathSeparator(path, os.PathSeparator) &&
|
if hasTrailingPathSeparator(path) &&
|
||||||
!hasTrailingPathSeparator(resolvedPath, os.PathSeparator) {
|
!hasTrailingPathSeparator(resolvedPath) {
|
||||||
resolvedPath += string(filepath.Separator)
|
resolvedPath += string(filepath.Separator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
||||||
basename := filepath.Base(hdr.Name)
|
basename := filepath.Base(hdr.Name)
|
||||||
aufsHardlinks[basename] = hdr
|
aufsHardlinks[basename] = hdr
|
||||||
if aufsTempdir == "" {
|
if aufsTempdir == "" {
|
||||||
if aufsTempdir, err = os.MkdirTemp("", "dockerplnk"); err != nil {
|
if aufsTempdir, err = os.MkdirTemp(dest, "dockerplnk"); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(aufsTempdir)
|
defer os.RemoveAll(aufsTempdir)
|
||||||
|
@ -121,7 +121,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
err = filepath.WalkDir(dir, func(path string, info os.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
err = nil // parent was deleted
|
err = nil // parent was deleted
|
||||||
|
@ -132,8 +132,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if _, exists := unpackedPaths[path]; !exists {
|
if _, exists := unpackedPaths[path]; !exists {
|
||||||
err := os.RemoveAll(path)
|
return os.RemoveAll(path)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package archive
|
||||||
|
|
||||||
|
// CheckSystemDriveAndRemoveDriveLetter verifies that a path, if it includes a drive letter,
|
||||||
|
// is the system drive.
|
||||||
|
// On Linux: this is a no-op.
|
||||||
|
// On Windows: this does the following>
|
||||||
|
// CheckSystemDriveAndRemoveDriveLetter verifies and manipulates a Windows path.
|
||||||
|
// This is used, for example, when validating a user provided path in docker cp.
|
||||||
|
// If a drive letter is supplied, it must be the system drive. The drive letter
|
||||||
|
// is always removed. Also, it translates it to OS semantics (IOW / to \). We
|
||||||
|
// need the path in this syntax so that it can ultimately be concatenated with
|
||||||
|
// a Windows long-path which doesn't support drive-letters. Examples:
|
||||||
|
// C: --> Fail
|
||||||
|
// C:\ --> \
|
||||||
|
// a --> a
|
||||||
|
// /a --> \a
|
||||||
|
// d:\ --> Fail
|
||||||
|
func CheckSystemDriveAndRemoveDriveLetter(path string) (string, error) {
|
||||||
|
return checkSystemDriveAndRemoveDriveLetter(path)
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package archive
|
||||||
|
|
||||||
|
// checkSystemDriveAndRemoveDriveLetter is the non-Windows implementation
|
||||||
|
// of CheckSystemDriveAndRemoveDriveLetter
|
||||||
|
func checkSystemDriveAndRemoveDriveLetter(path string) (string, error) {
|
||||||
|
return path, nil
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package archive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// checkSystemDriveAndRemoveDriveLetter is the Windows implementation
|
||||||
|
// of CheckSystemDriveAndRemoveDriveLetter
|
||||||
|
func checkSystemDriveAndRemoveDriveLetter(path string) (string, error) {
|
||||||
|
if len(path) == 2 && string(path[1]) == ":" {
|
||||||
|
return "", fmt.Errorf("no relative path specified in %q", path)
|
||||||
|
}
|
||||||
|
if !filepath.IsAbs(path) || len(path) < 2 {
|
||||||
|
return filepath.FromSlash(path), nil
|
||||||
|
}
|
||||||
|
if string(path[1]) == ":" && !strings.EqualFold(string(path[0]), "c") {
|
||||||
|
return "", fmt.Errorf("the specified path is not on the system drive (C:)")
|
||||||
|
}
|
||||||
|
return filepath.FromSlash(path[2:]), nil
|
||||||
|
}
|
|
@ -64,13 +64,14 @@ func stick(f string) error {
|
||||||
|
|
||||||
// GetDataHome returns XDG_DATA_HOME.
|
// GetDataHome returns XDG_DATA_HOME.
|
||||||
// GetDataHome returns $HOME/.local/share and nil error if XDG_DATA_HOME is not set.
|
// GetDataHome returns $HOME/.local/share and nil error if XDG_DATA_HOME is not set.
|
||||||
|
// If HOME and XDG_DATA_HOME are not set, getpwent(3) is consulted to determine the users home directory.
|
||||||
//
|
//
|
||||||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||||
func GetDataHome() (string, error) {
|
func GetDataHome() (string, error) {
|
||||||
if xdgDataHome := os.Getenv("XDG_DATA_HOME"); xdgDataHome != "" {
|
if xdgDataHome := os.Getenv("XDG_DATA_HOME"); xdgDataHome != "" {
|
||||||
return xdgDataHome, nil
|
return xdgDataHome, nil
|
||||||
}
|
}
|
||||||
home := os.Getenv("HOME")
|
home := Get()
|
||||||
if home == "" {
|
if home == "" {
|
||||||
return "", errors.New("could not get either XDG_DATA_HOME or HOME")
|
return "", errors.New("could not get either XDG_DATA_HOME or HOME")
|
||||||
}
|
}
|
||||||
|
@ -79,13 +80,14 @@ func GetDataHome() (string, error) {
|
||||||
|
|
||||||
// GetConfigHome returns XDG_CONFIG_HOME.
|
// GetConfigHome returns XDG_CONFIG_HOME.
|
||||||
// GetConfigHome returns $HOME/.config and nil error if XDG_CONFIG_HOME is not set.
|
// GetConfigHome returns $HOME/.config and nil error if XDG_CONFIG_HOME is not set.
|
||||||
|
// If HOME and XDG_CONFIG_HOME are not set, getpwent(3) is consulted to determine the users home directory.
|
||||||
//
|
//
|
||||||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||||
func GetConfigHome() (string, error) {
|
func GetConfigHome() (string, error) {
|
||||||
if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
|
if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
|
||||||
return xdgConfigHome, nil
|
return xdgConfigHome, nil
|
||||||
}
|
}
|
||||||
home := os.Getenv("HOME")
|
home := Get()
|
||||||
if home == "" {
|
if home == "" {
|
||||||
return "", errors.New("could not get either XDG_CONFIG_HOME or HOME")
|
return "", errors.New("could not get either XDG_CONFIG_HOME or HOME")
|
||||||
}
|
}
|
||||||
|
@ -93,8 +95,9 @@ func GetConfigHome() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLibHome returns $HOME/.local/lib
|
// GetLibHome returns $HOME/.local/lib
|
||||||
|
// If HOME is not set, getpwent(3) is consulted to determine the users home directory.
|
||||||
func GetLibHome() (string, error) {
|
func GetLibHome() (string, error) {
|
||||||
home := os.Getenv("HOME")
|
home := Get()
|
||||||
if home == "" {
|
if home == "" {
|
||||||
return "", errors.New("could not get HOME")
|
return "", errors.New("could not get HOME")
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,20 +162,6 @@ func (i IdentityMapping) Empty() bool {
|
||||||
return len(i.UIDMaps) == 0 && len(i.GIDMaps) == 0
|
return len(i.UIDMaps) == 0 && len(i.GIDMaps) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// UIDs returns the mapping for UID.
|
|
||||||
//
|
|
||||||
// Deprecated: reference the UIDMaps field directly.
|
|
||||||
func (i IdentityMapping) UIDs() []IDMap {
|
|
||||||
return i.UIDMaps
|
|
||||||
}
|
|
||||||
|
|
||||||
// GIDs returns the mapping for GID.
|
|
||||||
//
|
|
||||||
// Deprecated: reference the GIDMaps field directly.
|
|
||||||
func (i IdentityMapping) GIDs() []IDMap {
|
|
||||||
return i.GIDMaps
|
|
||||||
}
|
|
||||||
|
|
||||||
func createIDMap(subidRanges ranges) []IDMap {
|
func createIDMap(subidRanges ranges) []IDMap {
|
||||||
idMap := []IDMap{}
|
idMap := []IDMap{}
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
"github.com/opencontainers/runc/libcontainer/user"
|
"github.com/opencontainers/runc/libcontainer/user"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -25,18 +23,12 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error {
|
func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error {
|
||||||
// make an array containing the original path asked for, plus (for mkAll == true)
|
|
||||||
// all path components leading up to the complete path that don't exist before we MkdirAll
|
|
||||||
// so that we can chown all of them properly at the end. If chownExisting is false, we won't
|
|
||||||
// chown the full directory path if it exists
|
|
||||||
|
|
||||||
var paths []string
|
|
||||||
path, err := filepath.Abs(path)
|
path, err := filepath.Abs(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stat, err := system.Stat(path)
|
stat, err := os.Stat(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if !stat.IsDir() {
|
if !stat.IsDir() {
|
||||||
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
|
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
|
||||||
|
@ -45,10 +37,15 @@ func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// short-circuit--we were called with an existing directory and chown was requested
|
// short-circuit -- we were called with an existing directory and chown was requested
|
||||||
return setPermissions(path, mode, owner.UID, owner.GID, stat)
|
return setPermissions(path, mode, owner, stat)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make an array containing the original path asked for, plus (for mkAll == true)
|
||||||
|
// all path components leading up to the complete path that don't exist before we MkdirAll
|
||||||
|
// so that we can chown all of them properly at the end. If chownExisting is false, we won't
|
||||||
|
// chown the full directory path if it exists
|
||||||
|
var paths []string
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
paths = []string{path}
|
paths = []string{path}
|
||||||
}
|
}
|
||||||
|
@ -62,54 +59,26 @@ func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting
|
||||||
if dirPath == "/" {
|
if dirPath == "/" {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(dirPath); err != nil && os.IsNotExist(err) {
|
if _, err = os.Stat(dirPath); err != nil && os.IsNotExist(err) {
|
||||||
paths = append(paths, dirPath)
|
paths = append(paths, dirPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := system.MkdirAll(path, mode); err != nil {
|
if err = os.MkdirAll(path, mode); err != nil {
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := os.Mkdir(path, mode); err != nil && !os.IsExist(err) {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else if err = os.Mkdir(path, mode); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
// even if it existed, we will chown the requested path + any subpaths that
|
// even if it existed, we will chown the requested path + any subpaths that
|
||||||
// didn't exist when we called MkdirAll
|
// didn't exist when we called MkdirAll
|
||||||
for _, pathComponent := range paths {
|
for _, pathComponent := range paths {
|
||||||
if err := setPermissions(pathComponent, mode, owner.UID, owner.GID, nil); err != nil {
|
if err = setPermissions(pathComponent, mode, owner, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanAccess takes a valid (existing) directory and a uid, gid pair and determines
|
|
||||||
// if that uid, gid pair has access (execute bit) to the directory
|
|
||||||
func CanAccess(path string, pair Identity) bool {
|
|
||||||
statInfo, err := system.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
fileMode := os.FileMode(statInfo.Mode())
|
|
||||||
permBits := fileMode.Perm()
|
|
||||||
return accessible(statInfo.UID() == uint32(pair.UID),
|
|
||||||
statInfo.GID() == uint32(pair.GID), permBits)
|
|
||||||
}
|
|
||||||
|
|
||||||
func accessible(isOwner, isGroup bool, perms os.FileMode) bool {
|
|
||||||
if isOwner && (perms&0100 == 0100) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if isGroup && (perms&0010 == 0010) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if perms&0001 == 0001 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// LookupUser uses traditional local system files lookup (from libcontainer/user) on a username,
|
// LookupUser uses traditional local system files lookup (from libcontainer/user) on a username,
|
||||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||||
func LookupUser(name string) (user.User, error) {
|
func LookupUser(name string) (user.User, error) {
|
||||||
|
@ -198,7 +167,7 @@ func callGetent(database, key string) (io.Reader, error) {
|
||||||
if getentCmd == "" {
|
if getentCmd == "" {
|
||||||
return nil, fmt.Errorf("unable to find getent command")
|
return nil, fmt.Errorf("unable to find getent command")
|
||||||
}
|
}
|
||||||
out, err := execCmd(getentCmd, database, key)
|
out, err := exec.Command(getentCmd, database, key).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exitCode, errC := getExitCode(err)
|
exitCode, errC := getExitCode(err)
|
||||||
if errC != nil {
|
if errC != nil {
|
||||||
|
@ -234,36 +203,24 @@ func getExitCode(err error) (int, error) {
|
||||||
// Normally a Chown is a no-op if uid/gid match, but in some cases this can still cause an error, e.g. if the
|
// Normally a Chown is a no-op if uid/gid match, but in some cases this can still cause an error, e.g. if the
|
||||||
// dir is on an NFS share, so don't call chown unless we absolutely must.
|
// dir is on an NFS share, so don't call chown unless we absolutely must.
|
||||||
// Likewise for setting permissions.
|
// Likewise for setting permissions.
|
||||||
func setPermissions(p string, mode os.FileMode, uid, gid int, stat *system.StatT) error {
|
func setPermissions(p string, mode os.FileMode, owner Identity, stat os.FileInfo) error {
|
||||||
if stat == nil {
|
if stat == nil {
|
||||||
var err error
|
var err error
|
||||||
stat, err = system.Stat(p)
|
stat, err = os.Stat(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if os.FileMode(stat.Mode()).Perm() != mode.Perm() {
|
if stat.Mode().Perm() != mode.Perm() {
|
||||||
if err := os.Chmod(p, mode.Perm()); err != nil {
|
if err := os.Chmod(p, mode.Perm()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) {
|
ssi := stat.Sys().(*syscall.Stat_t)
|
||||||
|
if ssi.Uid == uint32(owner.UID) && ssi.Gid == uint32(owner.GID) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return os.Chown(p, uid, gid)
|
return os.Chown(p, owner.UID, owner.GID)
|
||||||
}
|
|
||||||
|
|
||||||
// NewIdentityMapping takes a requested username and
|
|
||||||
// using the data from /etc/sub{uid,gid} ranges, creates the
|
|
||||||
// proper uid and gid remapping ranges for that user/group pair
|
|
||||||
//
|
|
||||||
// Deprecated: Use LoadIdentityMapping.
|
|
||||||
func NewIdentityMapping(name string) (*IdentityMapping, error) {
|
|
||||||
m, err := LoadIdentityMapping(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &m, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadIdentityMapping takes a requested username and
|
// LoadIdentityMapping takes a requested username and
|
||||||
|
@ -272,7 +229,7 @@ func NewIdentityMapping(name string) (*IdentityMapping, error) {
|
||||||
func LoadIdentityMapping(name string) (IdentityMapping, error) {
|
func LoadIdentityMapping(name string) (IdentityMapping, error) {
|
||||||
usr, err := LookupUser(name)
|
usr, err := LookupUser(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return IdentityMapping{}, fmt.Errorf("Could not get user for username %s: %v", name, err)
|
return IdentityMapping{}, fmt.Errorf("could not get user for username %s: %v", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
subuidRanges, err := lookupSubUIDRanges(usr)
|
subuidRanges, err := lookupSubUIDRanges(usr)
|
||||||
|
@ -302,7 +259,7 @@ func lookupSubUIDRanges(usr user.User) ([]IDMap, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(rangeList) == 0 {
|
if len(rangeList) == 0 {
|
||||||
return nil, errors.Errorf("no subuid ranges found for user %q", usr.Name)
|
return nil, fmt.Errorf("no subuid ranges found for user %q", usr.Name)
|
||||||
}
|
}
|
||||||
return createIDMap(rangeList), nil
|
return createIDMap(rangeList), nil
|
||||||
}
|
}
|
||||||
|
@ -319,7 +276,7 @@ func lookupSubGIDRanges(usr user.User) ([]IDMap, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(rangeList) == 0 {
|
if len(rangeList) == 0 {
|
||||||
return nil, errors.Errorf("no subgid ranges found for user %q", usr.Name)
|
return nil, fmt.Errorf("no subgid ranges found for user %q", usr.Name)
|
||||||
}
|
}
|
||||||
return createIDMap(rangeList), nil
|
return createIDMap(rangeList), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,16 +19,6 @@ const (
|
||||||
// permissions aren't set through this path, the identity isn't utilized.
|
// permissions aren't set through this path, the identity isn't utilized.
|
||||||
// Ownership is handled elsewhere, but in the future could be support here
|
// Ownership is handled elsewhere, but in the future could be support here
|
||||||
// too.
|
// too.
|
||||||
func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error {
|
func mkdirAs(path string, _ os.FileMode, _ Identity, _, _ bool) error {
|
||||||
if err := system.MkdirAll(path, mode); err != nil {
|
return system.MkdirAll(path, 0)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanAccess takes a valid (existing) directory and a uid, gid pair and determines
|
|
||||||
// if that uid, gid pair has access (execute bit) to the directory
|
|
||||||
// Windows does not require/support this function, so always return true
|
|
||||||
func CanAccess(path string, identity Identity) bool {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package idtools // import "github.com/docker/docker/pkg/idtools"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -32,21 +33,21 @@ const (
|
||||||
// mapping ranges in containers.
|
// mapping ranges in containers.
|
||||||
func AddNamespaceRangesUser(name string) (int, int, error) {
|
func AddNamespaceRangesUser(name string) (int, int, error) {
|
||||||
if err := addUser(name); err != nil {
|
if err := addUser(name); err != nil {
|
||||||
return -1, -1, fmt.Errorf("Error adding user %q: %v", name, err)
|
return -1, -1, fmt.Errorf("error adding user %q: %v", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query the system for the created uid and gid pair
|
// Query the system for the created uid and gid pair
|
||||||
out, err := execCmd("id", name)
|
out, err := exec.Command("id", name).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, -1, fmt.Errorf("Error trying to find uid/gid for new user %q: %v", name, err)
|
return -1, -1, fmt.Errorf("error trying to find uid/gid for new user %q: %v", name, err)
|
||||||
}
|
}
|
||||||
matches := idOutRegexp.FindStringSubmatch(strings.TrimSpace(string(out)))
|
matches := idOutRegexp.FindStringSubmatch(strings.TrimSpace(string(out)))
|
||||||
if len(matches) != 3 {
|
if len(matches) != 3 {
|
||||||
return -1, -1, fmt.Errorf("Can't find uid, gid from `id` output: %q", string(out))
|
return -1, -1, fmt.Errorf("can't find uid, gid from `id` output: %q", string(out))
|
||||||
}
|
}
|
||||||
uid, err := strconv.Atoi(matches[1])
|
uid, err := strconv.Atoi(matches[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, -1, fmt.Errorf("Can't convert found uid (%s) to int: %v", matches[1], err)
|
return -1, -1, fmt.Errorf("can't convert found uid (%s) to int: %v", matches[1], err)
|
||||||
}
|
}
|
||||||
gid, err := strconv.Atoi(matches[2])
|
gid, err := strconv.Atoi(matches[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -57,7 +58,7 @@ func AddNamespaceRangesUser(name string) (int, int, error) {
|
||||||
// do not get auto-created ranges in subuid/subgid)
|
// do not get auto-created ranges in subuid/subgid)
|
||||||
|
|
||||||
if err := createSubordinateRanges(name); err != nil {
|
if err := createSubordinateRanges(name); err != nil {
|
||||||
return -1, -1, fmt.Errorf("Couldn't create subordinate ID ranges: %v", err)
|
return -1, -1, fmt.Errorf("couldn't create subordinate ID ranges: %v", err)
|
||||||
}
|
}
|
||||||
return uid, gid, nil
|
return uid, gid, nil
|
||||||
}
|
}
|
||||||
|
@ -81,7 +82,7 @@ func addUser(name string) error {
|
||||||
return fmt.Errorf("cannot add user; no useradd/adduser binary found")
|
return fmt.Errorf("cannot add user; no useradd/adduser binary found")
|
||||||
}
|
}
|
||||||
|
|
||||||
if out, err := execCmd(userCommand, args...); err != nil {
|
if out, err := exec.Command(userCommand, args...).CombinedOutput(); err != nil {
|
||||||
return fmt.Errorf("failed to add user with error: %v; output: %q", err, string(out))
|
return fmt.Errorf("failed to add user with error: %v; output: %q", err, string(out))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -92,33 +93,35 @@ func createSubordinateRanges(name string) error {
|
||||||
// by the distro tooling
|
// by the distro tooling
|
||||||
ranges, err := parseSubuid(name)
|
ranges, err := parseSubuid(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error while looking for subuid ranges for user %q: %v", name, err)
|
return fmt.Errorf("error while looking for subuid ranges for user %q: %v", name, err)
|
||||||
}
|
}
|
||||||
if len(ranges) == 0 {
|
if len(ranges) == 0 {
|
||||||
// no UID ranges; let's create one
|
// no UID ranges; let's create one
|
||||||
startID, err := findNextUIDRange()
|
startID, err := findNextUIDRange()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Can't find available subuid range: %v", err)
|
return fmt.Errorf("can't find available subuid range: %v", err)
|
||||||
}
|
}
|
||||||
out, err := execCmd("usermod", "-v", fmt.Sprintf("%d-%d", startID, startID+defaultRangeLen-1), name)
|
idRange := fmt.Sprintf("%d-%d", startID, startID+defaultRangeLen-1)
|
||||||
|
out, err := exec.Command("usermod", "-v", idRange, name).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to add subuid range to user: %q; output: %s, err: %v", name, out, err)
|
return fmt.Errorf("unable to add subuid range to user: %q; output: %s, err: %v", name, out, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ranges, err = parseSubgid(name)
|
ranges, err = parseSubgid(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error while looking for subgid ranges for user %q: %v", name, err)
|
return fmt.Errorf("error while looking for subgid ranges for user %q: %v", name, err)
|
||||||
}
|
}
|
||||||
if len(ranges) == 0 {
|
if len(ranges) == 0 {
|
||||||
// no GID ranges; let's create one
|
// no GID ranges; let's create one
|
||||||
startID, err := findNextGIDRange()
|
startID, err := findNextGIDRange()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Can't find available subgid range: %v", err)
|
return fmt.Errorf("can't find available subgid range: %v", err)
|
||||||
}
|
}
|
||||||
out, err := execCmd("usermod", "-w", fmt.Sprintf("%d-%d", startID, startID+defaultRangeLen-1), name)
|
idRange := fmt.Sprintf("%d-%d", startID, startID+defaultRangeLen-1)
|
||||||
|
out, err := exec.Command("usermod", "-w", idRange, name).CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Unable to add subgid range to user: %q; output: %s, err: %v", name, out, err)
|
return fmt.Errorf("unable to add subgid range to user: %q; output: %s, err: %v", name, out, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -127,7 +130,7 @@ func createSubordinateRanges(name string) error {
|
||||||
func findNextUIDRange() (int, error) {
|
func findNextUIDRange() (int, error) {
|
||||||
ranges, err := parseSubuid("ALL")
|
ranges, err := parseSubuid("ALL")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, fmt.Errorf("Couldn't parse all ranges in /etc/subuid file: %v", err)
|
return -1, fmt.Errorf("couldn't parse all ranges in /etc/subuid file: %v", err)
|
||||||
}
|
}
|
||||||
sort.Sort(ranges)
|
sort.Sort(ranges)
|
||||||
return findNextRangeStart(ranges)
|
return findNextRangeStart(ranges)
|
||||||
|
@ -136,7 +139,7 @@ func findNextUIDRange() (int, error) {
|
||||||
func findNextGIDRange() (int, error) {
|
func findNextGIDRange() (int, error) {
|
||||||
ranges, err := parseSubgid("ALL")
|
ranges, err := parseSubgid("ALL")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, fmt.Errorf("Couldn't parse all ranges in /etc/subgid file: %v", err)
|
return -1, fmt.Errorf("couldn't parse all ranges in /etc/subgid file: %v", err)
|
||||||
}
|
}
|
||||||
sort.Sort(ranges)
|
sort.Sort(ranges)
|
||||||
return findNextRangeStart(ranges)
|
return findNextRangeStart(ranges)
|
||||||
|
|
|
@ -25,8 +25,3 @@ func resolveBinary(binname string) (string, error) {
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("Binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath)
|
return "", fmt.Errorf("Binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func execCmd(cmd string, arg ...string) ([]byte, error) {
|
|
||||||
execCmd := exec.Command(cmd, arg...)
|
|
||||||
return execCmd.CombinedOutput()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
//go:build !windows
|
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package ioutils // import "github.com/docker/docker/pkg/ioutils"
|
|
||||||
|
|
||||||
import "os"
|
|
||||||
|
|
||||||
// TempDir on Unix systems is equivalent to os.MkdirTemp.
|
|
||||||
func TempDir(dir, prefix string) (string, error) {
|
|
||||||
return os.MkdirTemp(dir, prefix)
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
package ioutils // import "github.com/docker/docker/pkg/ioutils"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/longpath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TempDir is the equivalent of os.MkdirTemp, except that the result is in Windows longpath format.
|
|
||||||
func TempDir(dir, prefix string) (string, error) {
|
|
||||||
tempDir, err := os.MkdirTemp(dir, prefix)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return longpath.AddPrefix(tempDir), nil
|
|
||||||
}
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package ioutils
|
||||||
|
|
||||||
|
import "github.com/docker/docker/pkg/longpath"
|
||||||
|
|
||||||
|
// TempDir is the equivalent of [os.MkdirTemp], except that on Windows
|
||||||
|
// the result is in Windows longpath format. On Unix systems it is
|
||||||
|
// equivalent to [os.MkdirTemp].
|
||||||
|
//
|
||||||
|
// Deprecated: use [longpath.MkdirTemp].
|
||||||
|
var TempDir = longpath.MkdirTemp
|
|
@ -271,13 +271,19 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type stream interface {
|
// Stream is an io.Writer for output with utilities to get the output's file
|
||||||
|
// descriptor and to detect wether it's a terminal.
|
||||||
|
//
|
||||||
|
// it is subset of the streams.Out type in
|
||||||
|
// https://pkg.go.dev/github.com/docker/cli@v20.10.17+incompatible/cli/streams#Out
|
||||||
|
type Stream interface {
|
||||||
io.Writer
|
io.Writer
|
||||||
FD() uintptr
|
FD() uintptr
|
||||||
IsTerminal() bool
|
IsTerminal() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisplayJSONMessagesToStream prints json messages to the output stream
|
// DisplayJSONMessagesToStream prints json messages to the output Stream. It is
|
||||||
func DisplayJSONMessagesToStream(in io.Reader, stream stream, auxCallback func(JSONMessage)) error {
|
// used by the Docker CLI to print JSONMessage streams.
|
||||||
|
func DisplayJSONMessagesToStream(in io.Reader, stream Stream, auxCallback func(JSONMessage)) error {
|
||||||
return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback)
|
return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
// longpath introduces some constants and helper functions for handling long paths
|
// Package longpath introduces some constants and helper functions for handling
|
||||||
// in Windows, which are expected to be prepended with `\\?\` and followed by either
|
// long paths in Windows.
|
||||||
// a drive letter, a UNC server\share, or a volume identifier.
|
//
|
||||||
|
// Long paths are expected to be prepended with "\\?\" and followed by either a
|
||||||
|
// drive letter, a UNC server\share, or a volume identifier.
|
||||||
package longpath // import "github.com/docker/docker/pkg/longpath"
|
package longpath // import "github.com/docker/docker/pkg/longpath"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Prefix is the longpath prefix for Windows file paths.
|
// Prefix is the longpath prefix for Windows file paths.
|
||||||
const Prefix = `\\?\`
|
const Prefix = `\\?\`
|
||||||
|
|
||||||
// AddPrefix will add the Windows long path prefix to the path provided if
|
// AddPrefix adds the Windows long path prefix to the path provided if
|
||||||
// it does not already have it.
|
// it does not already have it.
|
||||||
func AddPrefix(path string) string {
|
func AddPrefix(path string) string {
|
||||||
if !strings.HasPrefix(path, Prefix) {
|
if !strings.HasPrefix(path, Prefix) {
|
||||||
|
@ -24,3 +27,17 @@ func AddPrefix(path string) string {
|
||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MkdirTemp is the equivalent of [os.MkdirTemp], except that on Windows
|
||||||
|
// the result is in Windows longpath format. On Unix systems it is
|
||||||
|
// equivalent to [os.MkdirTemp].
|
||||||
|
func MkdirTemp(dir, prefix string) (string, error) {
|
||||||
|
tempDir, err := os.MkdirTemp(dir, prefix)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
return tempDir, nil
|
||||||
|
}
|
||||||
|
return AddPrefix(tempDir), nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Package meminfo provides utilites to retrieve memory statistics of
|
||||||
|
// the host system.
|
||||||
|
package meminfo
|
||||||
|
|
||||||
|
// Read retrieves memory statistics of the host system and returns a
|
||||||
|
// Memory type. It is only supported on Linux and Windows, and returns an
|
||||||
|
// error on other platforms.
|
||||||
|
func Read() (*Memory, error) {
|
||||||
|
return readMemInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Memory contains memory statistics of the host system.
|
||||||
|
type Memory struct {
|
||||||
|
// Total usable RAM (i.e. physical RAM minus a few reserved bits and the
|
||||||
|
// kernel binary code).
|
||||||
|
MemTotal int64
|
||||||
|
|
||||||
|
// Amount of free memory.
|
||||||
|
MemFree int64
|
||||||
|
|
||||||
|
// Total amount of swap space available.
|
||||||
|
SwapTotal int64
|
||||||
|
|
||||||
|
// Amount of swap space that is currently unused.
|
||||||
|
SwapFree int64
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package system // import "github.com/docker/docker/pkg/system"
|
package meminfo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
@ -8,9 +8,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReadMemInfo retrieves memory statistics of the host system and returns a
|
// readMemInfo retrieves memory statistics of the host system and returns a
|
||||||
// MemInfo type.
|
// Memory type.
|
||||||
func ReadMemInfo() (*MemInfo, error) {
|
func readMemInfo() (*Memory, error) {
|
||||||
file, err := os.Open("/proc/meminfo")
|
file, err := os.Open("/proc/meminfo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -20,10 +20,10 @@ func ReadMemInfo() (*MemInfo, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseMemInfo parses the /proc/meminfo file into
|
// parseMemInfo parses the /proc/meminfo file into
|
||||||
// a MemInfo object given an io.Reader to the file.
|
// a Memory object given an io.Reader to the file.
|
||||||
// Throws error if there are problems reading from the file
|
// Throws error if there are problems reading from the file
|
||||||
func parseMemInfo(reader io.Reader) (*MemInfo, error) {
|
func parseMemInfo(reader io.Reader) (*Memory, error) {
|
||||||
meminfo := &MemInfo{}
|
meminfo := &Memory{}
|
||||||
scanner := bufio.NewScanner(reader)
|
scanner := bufio.NewScanner(reader)
|
||||||
memAvailable := int64(-1)
|
memAvailable := int64(-1)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
11
vendor/github.com/docker/docker/pkg/meminfo/meminfo_unsupported.go
generated
vendored
Normal file
11
vendor/github.com/docker/docker/pkg/meminfo/meminfo_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
//go:build !linux && !windows
|
||||||
|
// +build !linux,!windows
|
||||||
|
|
||||||
|
package meminfo
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// readMemInfo is not supported on platforms other than linux and windows.
|
||||||
|
func readMemInfo() (*Memory, error) {
|
||||||
|
return nil, errors.New("platform and architecture is not supported")
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package system // import "github.com/docker/docker/pkg/system"
|
package meminfo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
@ -26,17 +26,17 @@ type memorystatusex struct {
|
||||||
ullAvailExtendedVirtual uint64
|
ullAvailExtendedVirtual uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadMemInfo retrieves memory statistics of the host system and returns a
|
// readMemInfo retrieves memory statistics of the host system and returns a
|
||||||
// MemInfo type.
|
// Memory type.
|
||||||
func ReadMemInfo() (*MemInfo, error) {
|
func readMemInfo() (*Memory, error) {
|
||||||
msi := &memorystatusex{
|
msi := &memorystatusex{
|
||||||
dwLength: 64,
|
dwLength: 64,
|
||||||
}
|
}
|
||||||
r1, _, _ := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(msi)))
|
r1, _, _ := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(msi)))
|
||||||
if r1 == 0 {
|
if r1 == 0 {
|
||||||
return &MemInfo{}, nil
|
return &Memory{}, nil
|
||||||
}
|
}
|
||||||
return &MemInfo{
|
return &Memory{
|
||||||
MemTotal: int64(msi.ullTotalPhys),
|
MemTotal: int64(msi.ullTotalPhys),
|
||||||
MemFree: int64(msi.ullAvailPhys),
|
MemFree: int64(msi.ullAvailPhys),
|
||||||
SwapTotal: int64(msi.ullTotalPageFile),
|
SwapTotal: int64(msi.ullTotalPageFile),
|
|
@ -0,0 +1,3 @@
|
||||||
|
// Package process provides a set of basic functions to manage individual
|
||||||
|
// processes.
|
||||||
|
package process
|
|
@ -0,0 +1,82 @@
|
||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package process
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Alive returns true if process with a given pid is running. It only considers
|
||||||
|
// positive PIDs; 0 (all processes in the current process group), -1 (all processes
|
||||||
|
// with a PID larger than 1), and negative (-n, all processes in process group
|
||||||
|
// "n") values for pid are never considered to be alive.
|
||||||
|
func Alive(pid int) bool {
|
||||||
|
if pid < 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin":
|
||||||
|
// OS X does not have a proc filesystem. Use kill -0 pid to judge if the
|
||||||
|
// process exists. From KILL(2): https://www.freebsd.org/cgi/man.cgi?query=kill&sektion=2&manpath=OpenDarwin+7.2.1
|
||||||
|
//
|
||||||
|
// Sig may be one of the signals specified in sigaction(2) or it may
|
||||||
|
// be 0, in which case error checking is performed but no signal is
|
||||||
|
// actually sent. This can be used to check the validity of pid.
|
||||||
|
err := unix.Kill(pid, 0)
|
||||||
|
|
||||||
|
// Either the PID was found (no error) or we get an EPERM, which means
|
||||||
|
// the PID exists, but we don't have permissions to signal it.
|
||||||
|
return err == nil || err == unix.EPERM
|
||||||
|
default:
|
||||||
|
_, err := os.Stat(filepath.Join("/proc", strconv.Itoa(pid)))
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill force-stops a process. It only considers positive PIDs; 0 (all processes
|
||||||
|
// in the current process group), -1 (all processes with a PID larger than 1),
|
||||||
|
// and negative (-n, all processes in process group "n") values for pid are
|
||||||
|
// ignored. Refer to [KILL(2)] for details.
|
||||||
|
//
|
||||||
|
// [KILL(2)]: https://man7.org/linux/man-pages/man2/kill.2.html
|
||||||
|
func Kill(pid int) error {
|
||||||
|
if pid < 1 {
|
||||||
|
return fmt.Errorf("invalid PID (%d): only positive PIDs are allowed", pid)
|
||||||
|
}
|
||||||
|
err := unix.Kill(pid, unix.SIGKILL)
|
||||||
|
if err != nil && err != unix.ESRCH {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zombie return true if process has a state with "Z". It only considers positive
|
||||||
|
// PIDs; 0 (all processes in the current process group), -1 (all processes with
|
||||||
|
// a PID larger than 1), and negative (-n, all processes in process group "n")
|
||||||
|
// values for pid are ignored. Refer to [PROC(5)] for details.
|
||||||
|
//
|
||||||
|
// [PROC(5)]: https://man7.org/linux/man-pages/man5/proc.5.html
|
||||||
|
func Zombie(pid int) (bool, error) {
|
||||||
|
if pid < 1 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid))
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if cols := bytes.SplitN(data, []byte(" "), 4); len(cols) >= 3 && string(cols[2]) == "Z" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package process
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Alive returns true if process with a given pid is running.
|
||||||
|
func Alive(pid int) bool {
|
||||||
|
h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var c uint32
|
||||||
|
err = windows.GetExitCodeProcess(h, &c)
|
||||||
|
_ = windows.CloseHandle(h)
|
||||||
|
if err != nil {
|
||||||
|
// From the GetExitCodeProcess function (processthreadsapi.h) API docs:
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess
|
||||||
|
//
|
||||||
|
// The GetExitCodeProcess function returns a valid error code defined by the
|
||||||
|
// application only after the thread terminates. Therefore, an application should
|
||||||
|
// not use STILL_ACTIVE (259) as an error code (STILL_ACTIVE is a macro for
|
||||||
|
// STATUS_PENDING (minwinbase.h)). If a thread returns STILL_ACTIVE (259) as
|
||||||
|
// an error code, then applications that test for that value could interpret it
|
||||||
|
// to mean that the thread is still running, and continue to test for the
|
||||||
|
// completion of the thread after the thread has terminated, which could put
|
||||||
|
// the application into an infinite loop.
|
||||||
|
return c == uint32(windows.STATUS_PENDING)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill force-stops a process.
|
||||||
|
func Kill(pid int) error {
|
||||||
|
p, err := os.FindProcess(pid)
|
||||||
|
if err == nil {
|
||||||
|
err = p.Kill()
|
||||||
|
if err != nil && err != os.ErrProcessDone {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zombie is not supported on Windows.
|
||||||
|
//
|
||||||
|
// TODO(thaJeztah): remove once we remove the stubs from pkg/system.
|
||||||
|
func Zombie(_ int) (bool, error) {
|
||||||
|
return false, nil
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
This package provides helper functions for dealing with string identifiers
|
|
|
@ -4,21 +4,28 @@ package stringid // import "github.com/docker/docker/pkg/stringid"
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"errors"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const shortLen = 12
|
const (
|
||||||
|
shortLen = 12
|
||||||
|
fullLen = 64
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
validShortID = regexp.MustCompile("^[a-f0-9]{12}$")
|
validShortID = regexp.MustCompile("^[a-f0-9]{12}$")
|
||||||
validHex = regexp.MustCompile(`^[a-f0-9]{64}$`)
|
validHex = regexp.MustCompile(`^[a-f0-9]{64}$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsShortID determines if an arbitrary string *looks like* a short ID.
|
// IsShortID determines if id has the correct format and length for a short ID.
|
||||||
|
// It checks the IDs length and if it consists of valid characters for IDs (a-f0-9).
|
||||||
func IsShortID(id string) bool {
|
func IsShortID(id string) bool {
|
||||||
|
if len(id) != shortLen {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return validShortID.MatchString(id)
|
return validShortID.MatchString(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,10 +61,13 @@ func GenerateRandomID() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateID checks whether an ID string is a valid image ID.
|
// ValidateID checks whether an ID string is a valid, full-length image ID.
|
||||||
func ValidateID(id string) error {
|
func ValidateID(id string) error {
|
||||||
if ok := validHex.MatchString(id); !ok {
|
if len(id) != fullLen {
|
||||||
return fmt.Errorf("image ID %q is invalid", id)
|
return errors.New("image ID '" + id + "' is invalid")
|
||||||
|
}
|
||||||
|
if !validHex.MatchString(id) {
|
||||||
|
return errors.New("image ID '" + id + "' is invalid")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,24 +2,41 @@ package system // import "github.com/docker/docker/pkg/system"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Chtimes changes the access time and modified time of a file at the given path
|
// Used by Chtimes
|
||||||
|
var unixEpochTime, unixMaxTime time.Time
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
unixEpochTime = time.Unix(0, 0)
|
||||||
|
if unsafe.Sizeof(syscall.Timespec{}.Nsec) == 8 {
|
||||||
|
// This is a 64 bit timespec
|
||||||
|
// os.Chtimes limits time to the following
|
||||||
|
//
|
||||||
|
// Note that this intentionally sets nsec (not sec), which sets both sec
|
||||||
|
// and nsec internally in time.Unix();
|
||||||
|
// https://github.com/golang/go/blob/go1.19.2/src/time/time.go#L1364-L1380
|
||||||
|
unixMaxTime = time.Unix(0, 1<<63-1)
|
||||||
|
} else {
|
||||||
|
// This is a 32 bit timespec
|
||||||
|
unixMaxTime = time.Unix(1<<31-1, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chtimes changes the access time and modified time of a file at the given path.
|
||||||
|
// If the modified time is prior to the Unix Epoch (unixMinTime), or after the
|
||||||
|
// end of Unix Time (unixEpochTime), os.Chtimes has undefined behavior. In this
|
||||||
|
// case, Chtimes defaults to Unix Epoch, just in case.
|
||||||
func Chtimes(name string, atime time.Time, mtime time.Time) error {
|
func Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||||
unixMinTime := time.Unix(0, 0)
|
if atime.Before(unixEpochTime) || atime.After(unixMaxTime) {
|
||||||
unixMaxTime := maxTime
|
atime = unixEpochTime
|
||||||
|
|
||||||
// If the modified time is prior to the Unix Epoch, or after the
|
|
||||||
// end of Unix Time, os.Chtimes has undefined behavior
|
|
||||||
// default to Unix Epoch in this case, just in case
|
|
||||||
|
|
||||||
if atime.Before(unixMinTime) || atime.After(unixMaxTime) {
|
|
||||||
atime = unixMinTime
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if mtime.Before(unixMinTime) || mtime.After(unixMaxTime) {
|
if mtime.Before(unixEpochTime) || mtime.After(unixMaxTime) {
|
||||||
mtime = unixMinTime
|
mtime = unixEpochTime
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Chtimes(name, atime, mtime); err != nil {
|
if err := os.Chtimes(name, atime, mtime); err != nil {
|
||||||
|
|
|
@ -9,18 +9,17 @@ import (
|
||||||
// setCTime will set the create time on a file. On Windows, this requires
|
// setCTime will set the create time on a file. On Windows, this requires
|
||||||
// calling SetFileTime and explicitly including the create time.
|
// calling SetFileTime and explicitly including the create time.
|
||||||
func setCTime(path string, ctime time.Time) error {
|
func setCTime(path string, ctime time.Time) error {
|
||||||
ctimespec := windows.NsecToTimespec(ctime.UnixNano())
|
pathp, err := windows.UTF16PtrFromString(path)
|
||||||
pathp, e := windows.UTF16PtrFromString(path)
|
if err != nil {
|
||||||
if e != nil {
|
return err
|
||||||
return e
|
|
||||||
}
|
}
|
||||||
h, e := windows.CreateFile(pathp,
|
h, err := windows.CreateFile(pathp,
|
||||||
windows.FILE_WRITE_ATTRIBUTES, windows.FILE_SHARE_WRITE, nil,
|
windows.FILE_WRITE_ATTRIBUTES, windows.FILE_SHARE_WRITE, nil,
|
||||||
windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
||||||
if e != nil {
|
if err != nil {
|
||||||
return e
|
return err
|
||||||
}
|
}
|
||||||
defer windows.Close(h)
|
defer windows.Close(h)
|
||||||
c := windows.NsecToFiletime(windows.TimespecToNsec(ctimespec))
|
c := windows.NsecToFiletime(ctime.UnixNano())
|
||||||
return windows.SetFileTime(h, &c, nil, nil)
|
return windows.SetFileTime(h, &c, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/moby/sys/sequential"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateSequential is deprecated.
|
|
||||||
//
|
|
||||||
// Deprecated: use os.Create or github.com/moby/sys/sequential.Create()
|
|
||||||
func CreateSequential(name string) (*os.File, error) {
|
|
||||||
return sequential.Create(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenSequential is deprecated.
|
|
||||||
//
|
|
||||||
// Deprecated: use os.Open or github.com/moby/sys/sequential.Open
|
|
||||||
func OpenSequential(name string) (*os.File, error) {
|
|
||||||
return sequential.Open(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenFileSequential is deprecated.
|
|
||||||
//
|
|
||||||
// Deprecated: use github.com/moby/sys/sequential.OpenFile()
|
|
||||||
func OpenFileSequential(name string, flag int, perm os.FileMode) (*os.File, error) {
|
|
||||||
return sequential.OpenFile(name, flag, perm)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TempFileSequential is deprecated.
|
|
||||||
//
|
|
||||||
// Deprecated: use os.CreateTemp or github.com/moby/sys/sequential.CreateTemp
|
|
||||||
func TempFileSequential(dir, prefix string) (f *os.File, err error) {
|
|
||||||
return sequential.CreateTemp(dir, prefix)
|
|
||||||
}
|
|
|
@ -9,28 +9,36 @@ import (
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// SddlAdministratorsLocalSystem is local administrators plus NT AUTHORITY\System.
|
||||||
// SddlAdministratorsLocalSystem is local administrators plus NT AUTHORITY\System
|
const SddlAdministratorsLocalSystem = "D:P(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)"
|
||||||
SddlAdministratorsLocalSystem = "D:P(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MkdirAllWithACL is a wrapper for MkdirAll that creates a directory
|
// volumePath is a regular expression to check if a path is a Windows
|
||||||
// with an appropriate SDDL defined ACL.
|
// volume path (e.g., "\\?\Volume{4c1b02c1-d990-11dc-99ae-806e6f6e6963}"
|
||||||
func MkdirAllWithACL(path string, perm os.FileMode, sddl string) error {
|
// or "\\?\Volume{4c1b02c1-d990-11dc-99ae-806e6f6e6963}\").
|
||||||
return mkdirall(path, true, sddl)
|
var volumePath = regexp.MustCompile(`^\\\\\?\\Volume{[a-z0-9-]+}\\?$`)
|
||||||
|
|
||||||
|
// MkdirAllWithACL is a custom version of os.MkdirAll modified for use on Windows
|
||||||
|
// so that it is both volume path aware, and can create a directory with
|
||||||
|
// an appropriate SDDL defined ACL.
|
||||||
|
func MkdirAllWithACL(path string, _ os.FileMode, sddl string) error {
|
||||||
|
sa, err := makeSecurityAttributes(sddl)
|
||||||
|
if err != nil {
|
||||||
|
return &os.PathError{Op: "mkdirall", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
return mkdirall(path, sa)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MkdirAll implementation that is volume path aware for Windows. It can be used
|
// MkdirAll is a custom version of os.MkdirAll that is volume path aware for
|
||||||
// as a drop-in replacement for os.MkdirAll()
|
// Windows. It can be used as a drop-in replacement for os.MkdirAll.
|
||||||
func MkdirAll(path string, _ os.FileMode) error {
|
func MkdirAll(path string, _ os.FileMode) error {
|
||||||
return mkdirall(path, false, "")
|
return mkdirall(path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mkdirall is a custom version of os.MkdirAll modified for use on Windows
|
// mkdirall is a custom version of os.MkdirAll modified for use on Windows
|
||||||
// so that it is both volume path aware, and can create a directory with
|
// so that it is both volume path aware, and can create a directory with
|
||||||
// a DACL.
|
// a DACL.
|
||||||
func mkdirall(path string, applyACL bool, sddl string) error {
|
func mkdirall(path string, perm *windows.SecurityAttributes) error {
|
||||||
if re := regexp.MustCompile(`^\\\\\?\\Volume{[a-z0-9-]+}$`); re.MatchString(path) {
|
if volumePath.MatchString(path) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,11 +51,7 @@ func mkdirall(path string, applyACL bool, sddl string) error {
|
||||||
if dir.IsDir() {
|
if dir.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &os.PathError{
|
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
|
||||||
Op: "mkdir",
|
|
||||||
Path: path,
|
|
||||||
Err: syscall.ENOTDIR,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slow path: make sure parent exists and then call Mkdir for path.
|
// Slow path: make sure parent exists and then call Mkdir for path.
|
||||||
|
@ -62,20 +66,15 @@ func mkdirall(path string, applyACL bool, sddl string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if j > 1 {
|
if j > 1 {
|
||||||
// Create parent
|
// Create parent.
|
||||||
err = mkdirall(path[0:j-1], false, sddl)
|
err = mkdirall(fixRootDirectory(path[:j-1]), perm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parent now exists; invoke os.Mkdir or mkdirWithACL and use its result.
|
// Parent now exists; invoke Mkdir and use its result.
|
||||||
if applyACL {
|
err = mkdirWithACL(path, perm)
|
||||||
err = mkdirWithACL(path, sddl)
|
|
||||||
} else {
|
|
||||||
err = os.Mkdir(path, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Handle arguments like "foo/." by
|
// Handle arguments like "foo/." by
|
||||||
// double-checking that directory doesn't exist.
|
// double-checking that directory doesn't exist.
|
||||||
|
@ -95,24 +94,42 @@ func mkdirall(path string, applyACL bool, sddl string) error {
|
||||||
// in golang to cater for creating a directory am ACL permitting full
|
// in golang to cater for creating a directory am ACL permitting full
|
||||||
// access, with inheritance, to any subfolder/file for Built-in Administrators
|
// access, with inheritance, to any subfolder/file for Built-in Administrators
|
||||||
// and Local System.
|
// and Local System.
|
||||||
func mkdirWithACL(name string, sddl string) error {
|
func mkdirWithACL(name string, sa *windows.SecurityAttributes) error {
|
||||||
sa := windows.SecurityAttributes{Length: 0}
|
if sa == nil {
|
||||||
sd, err := windows.SecurityDescriptorFromString(sddl)
|
return os.Mkdir(name, 0)
|
||||||
if err != nil {
|
|
||||||
return &os.PathError{Op: "mkdir", Path: name, Err: err}
|
|
||||||
}
|
}
|
||||||
sa.Length = uint32(unsafe.Sizeof(sa))
|
|
||||||
sa.InheritHandle = 1
|
|
||||||
sa.SecurityDescriptor = sd
|
|
||||||
|
|
||||||
namep, err := windows.UTF16PtrFromString(name)
|
namep, err := windows.UTF16PtrFromString(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &os.PathError{Op: "mkdir", Path: name, Err: err}
|
return &os.PathError{Op: "mkdir", Path: name, Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
e := windows.CreateDirectory(namep, &sa)
|
err = windows.CreateDirectory(namep, sa)
|
||||||
if e != nil {
|
if err != nil {
|
||||||
return &os.PathError{Op: "mkdir", Path: name, Err: e}
|
return &os.PathError{Op: "mkdir", Path: name, Err: err}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fixRootDirectory fixes a reference to a drive's root directory to
|
||||||
|
// have the required trailing slash.
|
||||||
|
func fixRootDirectory(p string) string {
|
||||||
|
if len(p) == len(`\\?\c:`) {
|
||||||
|
if os.IsPathSeparator(p[0]) && os.IsPathSeparator(p[1]) && p[2] == '?' && os.IsPathSeparator(p[3]) && p[5] == ':' {
|
||||||
|
return p + `\`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSecurityAttributes(sddl string) (*windows.SecurityAttributes, error) {
|
||||||
|
var sa windows.SecurityAttributes
|
||||||
|
sa.Length = uint32(unsafe.Sizeof(sa))
|
||||||
|
sa.InheritHandle = 1
|
||||||
|
var err error
|
||||||
|
sa.SecurityDescriptor, err = windows.SecurityDescriptorFromString(sddl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &sa, nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
package system // import "github.com/docker/docker/pkg/system"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Used by chtimes
|
|
||||||
var maxTime time.Time
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// chtimes initialization
|
|
||||||
if unsafe.Sizeof(syscall.Timespec{}.Nsec) == 8 {
|
|
||||||
// This is a 64 bit timespec
|
|
||||||
// os.Chtimes limits time to the following
|
|
||||||
maxTime = time.Unix(0, 1<<63-1)
|
|
||||||
} else {
|
|
||||||
// This is a 32 bit timespec
|
|
||||||
maxTime = time.Unix(1<<31-1, 0)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package system // import "github.com/docker/docker/pkg/system"
|
|
||||||
|
|
||||||
// MemInfo contains memory statistics of the host system.
|
|
||||||
type MemInfo struct {
|
|
||||||
// Total usable RAM (i.e. physical RAM minus a few reserved bits and the
|
|
||||||
// kernel binary code).
|
|
||||||
MemTotal int64
|
|
||||||
|
|
||||||
// Amount of free memory.
|
|
||||||
MemFree int64
|
|
||||||
|
|
||||||
// Total amount of swap space available.
|
|
||||||
SwapTotal int64
|
|
||||||
|
|
||||||
// Amount of swap space that is currently unused.
|
|
||||||
SwapFree int64
|
|
||||||
}
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import "github.com/docker/docker/pkg/meminfo"
|
||||||
|
|
||||||
|
// MemInfo contains memory statistics of the host system.
|
||||||
|
//
|
||||||
|
// Deprecated: use [meminfo.Memory].
|
||||||
|
type MemInfo = meminfo.Memory
|
||||||
|
|
||||||
|
// ReadMemInfo retrieves memory statistics of the host system and returns a
|
||||||
|
// MemInfo type.
|
||||||
|
//
|
||||||
|
// Deprecated: use [meminfo.Read].
|
||||||
|
func ReadMemInfo() (*meminfo.Memory, error) {
|
||||||
|
return meminfo.Read()
|
||||||
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
//go:build !linux && !windows
|
|
||||||
// +build !linux,!windows
|
|
||||||
|
|
||||||
package system // import "github.com/docker/docker/pkg/system"
|
|
||||||
|
|
||||||
// ReadMemInfo is not supported on platforms other than linux and windows.
|
|
||||||
func ReadMemInfo() (*MemInfo, error) {
|
|
||||||
return nil, ErrNotSupportedPlatform
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
package system // import "github.com/docker/docker/pkg/system"
|
|
||||||
|
|
||||||
const defaultUnixPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
||||||
|
|
||||||
// DefaultPathEnv is unix style list of directories to search for
|
|
||||||
// executables. Each directory is separated from the next by a colon
|
|
||||||
// ':' character .
|
|
||||||
// For Windows containers, an empty string is returned as the default
|
|
||||||
// path will be set by the container, and Docker has no context of what the
|
|
||||||
// default path should be.
|
|
||||||
func DefaultPathEnv(os string) string {
|
|
||||||
if os == "windows" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return defaultUnixPathEnv
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathVerifier defines the subset of a PathDriver that CheckSystemDriveAndRemoveDriveLetter
|
|
||||||
// actually uses in order to avoid system depending on containerd/continuity.
|
|
||||||
type PathVerifier interface {
|
|
||||||
IsAbs(string) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckSystemDriveAndRemoveDriveLetter verifies that a path, if it includes a drive letter,
|
|
||||||
// is the system drive.
|
|
||||||
// On Linux: this is a no-op.
|
|
||||||
// On Windows: this does the following>
|
|
||||||
// CheckSystemDriveAndRemoveDriveLetter verifies and manipulates a Windows path.
|
|
||||||
// This is used, for example, when validating a user provided path in docker cp.
|
|
||||||
// If a drive letter is supplied, it must be the system drive. The drive letter
|
|
||||||
// is always removed. Also, it translates it to OS semantics (IOW / to \). We
|
|
||||||
// need the path in this syntax so that it can ultimately be concatenated with
|
|
||||||
// a Windows long-path which doesn't support drive-letters. Examples:
|
|
||||||
// C: --> Fail
|
|
||||||
// C:\ --> \
|
|
||||||
// a --> a
|
|
||||||
// /a --> \a
|
|
||||||
// d:\ --> Fail
|
|
||||||
func CheckSystemDriveAndRemoveDriveLetter(path string, driver PathVerifier) (string, error) {
|
|
||||||
return checkSystemDriveAndRemoveDriveLetter(path, driver)
|
|
||||||
}
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
const defaultUnixPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
|
||||||
|
// DefaultPathEnv is unix style list of directories to search for
|
||||||
|
// executables. Each directory is separated from the next by a colon
|
||||||
|
// ':' character .
|
||||||
|
// For Windows containers, an empty string is returned as the default
|
||||||
|
// path will be set by the container, and Docker has no context of what the
|
||||||
|
// default path should be.
|
||||||
|
//
|
||||||
|
// Deprecated: use oci.DefaultPathEnv
|
||||||
|
func DefaultPathEnv(os string) string {
|
||||||
|
if os == "windows" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return defaultUnixPathEnv
|
||||||
|
}
|
|
@ -1,17 +0,0 @@
|
||||||
//go:build !windows
|
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package system // import "github.com/docker/docker/pkg/system"
|
|
||||||
|
|
||||||
// GetLongPathName converts Windows short pathnames to full pathnames.
|
|
||||||
// For example C:\Users\ADMIN~1 --> C:\Users\Administrator.
|
|
||||||
// It is a no-op on non-Windows platforms
|
|
||||||
func GetLongPathName(path string) (string, error) {
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkSystemDriveAndRemoveDriveLetter is the non-Windows implementation
|
|
||||||
// of CheckSystemDriveAndRemoveDriveLetter
|
|
||||||
func checkSystemDriveAndRemoveDriveLetter(path string, driver PathVerifier) (string, error) {
|
|
||||||
return path, nil
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
package system // import "github.com/docker/docker/pkg/system"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetLongPathName converts Windows short pathnames to full pathnames.
|
|
||||||
// For example C:\Users\ADMIN~1 --> C:\Users\Administrator.
|
|
||||||
// It is a no-op on non-Windows platforms
|
|
||||||
func GetLongPathName(path string) (string, error) {
|
|
||||||
// See https://groups.google.com/forum/#!topic/golang-dev/1tufzkruoTg
|
|
||||||
p, err := windows.UTF16FromString(path)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
b := p // GetLongPathName says we can reuse buffer
|
|
||||||
n, err := windows.GetLongPathName(&p[0], &b[0], uint32(len(b)))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if n > uint32(len(b)) {
|
|
||||||
b = make([]uint16, n)
|
|
||||||
_, err = windows.GetLongPathName(&p[0], &b[0], uint32(len(b)))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return windows.UTF16ToString(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkSystemDriveAndRemoveDriveLetter is the Windows implementation
|
|
||||||
// of CheckSystemDriveAndRemoveDriveLetter
|
|
||||||
func checkSystemDriveAndRemoveDriveLetter(path string, driver PathVerifier) (string, error) {
|
|
||||||
if len(path) == 2 && string(path[1]) == ":" {
|
|
||||||
return "", fmt.Errorf("No relative path specified in %q", path)
|
|
||||||
}
|
|
||||||
if !driver.IsAbs(path) || len(path) < 2 {
|
|
||||||
return filepath.FromSlash(path), nil
|
|
||||||
}
|
|
||||||
if string(path[1]) == ":" && !strings.EqualFold(string(path[0]), "c") {
|
|
||||||
return "", fmt.Errorf("The specified path is not on the system drive (C:)")
|
|
||||||
}
|
|
||||||
return filepath.FromSlash(path[2:]), nil
|
|
||||||
}
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
//go:build linux || freebsd || darwin || windows
|
||||||
|
// +build linux freebsd darwin windows
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import "github.com/docker/docker/pkg/process"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// IsProcessAlive returns true if process with a given pid is running.
|
||||||
|
//
|
||||||
|
// Deprecated: use [process.Alive].
|
||||||
|
IsProcessAlive = process.Alive
|
||||||
|
|
||||||
|
// IsProcessZombie return true if process has a state with "Z"
|
||||||
|
//
|
||||||
|
// Deprecated: use [process.Zombie].
|
||||||
|
//
|
||||||
|
// TODO(thaJeztah): remove the Windows implementation in process once we remove this stub.
|
||||||
|
IsProcessZombie = process.Zombie
|
||||||
|
)
|
||||||
|
|
||||||
|
// KillProcess force-stops a process.
|
||||||
|
//
|
||||||
|
// Deprecated: use [process.Kill].
|
||||||
|
func KillProcess(pid int) {
|
||||||
|
_ = process.Kill(pid)
|
||||||
|
}
|
|
@ -1,46 +0,0 @@
|
||||||
//go:build linux || freebsd || darwin
|
|
||||||
// +build linux freebsd darwin
|
|
||||||
|
|
||||||
package system // import "github.com/docker/docker/pkg/system"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsProcessAlive returns true if process with a given pid is running.
|
|
||||||
func IsProcessAlive(pid int) bool {
|
|
||||||
err := unix.Kill(pid, syscall.Signal(0))
|
|
||||||
if err == nil || err == unix.EPERM {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// KillProcess force-stops a process.
|
|
||||||
func KillProcess(pid int) {
|
|
||||||
unix.Kill(pid, unix.SIGKILL)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsProcessZombie return true if process has a state with "Z"
|
|
||||||
// http://man7.org/linux/man-pages/man5/proc.5.html
|
|
||||||
func IsProcessZombie(pid int) (bool, error) {
|
|
||||||
statPath := fmt.Sprintf("/proc/%d/stat", pid)
|
|
||||||
dataBytes, err := os.ReadFile(statPath)
|
|
||||||
if err != nil {
|
|
||||||
// TODO(thaJeztah) should we ignore os.IsNotExist() here? ("/proc/<pid>/stat" will be gone if the process exited)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
data := string(dataBytes)
|
|
||||||
sdata := strings.SplitN(data, " ", 4)
|
|
||||||
if len(sdata) >= 3 && sdata[2] == "Z" {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package system // import "github.com/docker/docker/pkg/system"
|
|
||||||
|
|
||||||
import "os"
|
|
||||||
|
|
||||||
// IsProcessAlive returns true if process with a given pid is running.
|
|
||||||
func IsProcessAlive(pid int) bool {
|
|
||||||
_, err := os.FindProcess(pid)
|
|
||||||
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// KillProcess force-stops a process.
|
|
||||||
func KillProcess(pid int) {
|
|
||||||
p, err := os.FindProcess(pid)
|
|
||||||
if err == nil {
|
|
||||||
_ = p.Kill()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package system // import "github.com/docker/docker/pkg/system"
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
|
|
||||||
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
|
|
||||||
return &StatT{size: s.Size,
|
|
||||||
mode: s.Mode,
|
|
||||||
uid: s.Uid,
|
|
||||||
gid: s.Gid,
|
|
||||||
rdev: s.Rdev,
|
|
||||||
mtim: s.Mtim}, nil
|
|
||||||
}
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"github.com/docker/distribution/registry/client/auth"
|
"github.com/docker/distribution/registry/client/auth"
|
||||||
"github.com/docker/distribution/registry/client/auth/challenge"
|
"github.com/docker/distribution/registry/client/auth/challenge"
|
||||||
"github.com/docker/distribution/registry/client/transport"
|
"github.com/docker/distribution/registry/client/transport"
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/api/types/registry"
|
"github.com/docker/docker/api/types/registry"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
@ -19,7 +18,7 @@ import (
|
||||||
const AuthClientID = "docker"
|
const AuthClientID = "docker"
|
||||||
|
|
||||||
type loginCredentialStore struct {
|
type loginCredentialStore struct {
|
||||||
authConfig *types.AuthConfig
|
authConfig *registry.AuthConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lcs loginCredentialStore) Basic(*url.URL) (string, string) {
|
func (lcs loginCredentialStore) Basic(*url.URL) (string, string) {
|
||||||
|
@ -35,12 +34,12 @@ func (lcs loginCredentialStore) SetRefreshToken(u *url.URL, service, token strin
|
||||||
}
|
}
|
||||||
|
|
||||||
type staticCredentialStore struct {
|
type staticCredentialStore struct {
|
||||||
auth *types.AuthConfig
|
auth *registry.AuthConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStaticCredentialStore returns a credential store
|
// NewStaticCredentialStore returns a credential store
|
||||||
// which always returns the same credential values.
|
// which always returns the same credential values.
|
||||||
func NewStaticCredentialStore(auth *types.AuthConfig) auth.CredentialStore {
|
func NewStaticCredentialStore(auth *registry.AuthConfig) auth.CredentialStore {
|
||||||
return staticCredentialStore{
|
return staticCredentialStore{
|
||||||
auth: auth,
|
auth: auth,
|
||||||
}
|
}
|
||||||
|
@ -66,7 +65,7 @@ func (scs staticCredentialStore) SetRefreshToken(*url.URL, string, string) {
|
||||||
// loginV2 tries to login to the v2 registry server. The given registry
|
// loginV2 tries to login to the v2 registry server. The given registry
|
||||||
// endpoint will be pinged to get authorization challenges. These challenges
|
// endpoint will be pinged to get authorization challenges. These challenges
|
||||||
// will be used to authenticate against the registry to validate credentials.
|
// will be used to authenticate against the registry to validate credentials.
|
||||||
func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent string) (string, string, error) {
|
func loginV2(authConfig *registry.AuthConfig, endpoint APIEndpoint, userAgent string) (string, string, error) {
|
||||||
var (
|
var (
|
||||||
endpointStr = strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
|
endpointStr = strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
|
||||||
modifiers = Headers(userAgent, nil)
|
modifiers = Headers(userAgent, nil)
|
||||||
|
@ -138,7 +137,7 @@ func ConvertToHostname(url string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveAuthConfig matches an auth configuration to a server address or a URL
|
// ResolveAuthConfig matches an auth configuration to a server address or a URL
|
||||||
func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registry.IndexInfo) types.AuthConfig {
|
func ResolveAuthConfig(authConfigs map[string]registry.AuthConfig, index *registry.IndexInfo) registry.AuthConfig {
|
||||||
configKey := GetAuthConfigKey(index)
|
configKey := GetAuthConfigKey(index)
|
||||||
// First try the happy case
|
// First try the happy case
|
||||||
if c, found := authConfigs[configKey]; found || index.Official {
|
if c, found := authConfigs[configKey]; found || index.Official {
|
||||||
|
@ -154,7 +153,7 @@ func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registry.
|
||||||
}
|
}
|
||||||
|
|
||||||
// When all else fails, return an empty auth config
|
// When all else fails, return an empty auth config
|
||||||
return types.AuthConfig{}
|
return registry.AuthConfig{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PingResponseError is used when the response from a ping
|
// PingResponseError is used when the response from a ping
|
||||||
|
|
|
@ -35,13 +35,13 @@ type v1Endpoint struct {
|
||||||
|
|
||||||
// newV1Endpoint parses the given address to return a registry endpoint.
|
// newV1Endpoint parses the given address to return a registry endpoint.
|
||||||
// TODO: remove. This is only used by search.
|
// TODO: remove. This is only used by search.
|
||||||
func newV1Endpoint(index *registry.IndexInfo, userAgent string, metaHeaders http.Header) (*v1Endpoint, error) {
|
func newV1Endpoint(index *registry.IndexInfo, headers http.Header) (*v1Endpoint, error) {
|
||||||
tlsConfig, err := newTLSConfig(index.Name, index.Secure)
|
tlsConfig, err := newTLSConfig(index.Name, index.Secure)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint, err := newV1EndpointFromStr(GetAuthConfigKey(index), tlsConfig, userAgent, metaHeaders)
|
endpoint, err := newV1EndpointFromStr(GetAuthConfigKey(index), tlsConfig, headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ func trimV1Address(address string) (string, error) {
|
||||||
return address, nil
|
return address, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newV1EndpointFromStr(address string, tlsConfig *tls.Config, userAgent string, metaHeaders http.Header) (*v1Endpoint, error) {
|
func newV1EndpointFromStr(address string, tlsConfig *tls.Config, headers http.Header) (*v1Endpoint, error) {
|
||||||
if !strings.HasPrefix(address, "http://") && !strings.HasPrefix(address, "https://") {
|
if !strings.HasPrefix(address, "http://") && !strings.HasPrefix(address, "https://") {
|
||||||
address = "https://" + address
|
address = "https://" + address
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,7 @@ func newV1EndpointFromStr(address string, tlsConfig *tls.Config, userAgent strin
|
||||||
return &v1Endpoint{
|
return &v1Endpoint{
|
||||||
IsSecure: tlsConfig == nil || !tlsConfig.InsecureSkipVerify,
|
IsSecure: tlsConfig == nil || !tlsConfig.InsecureSkipVerify,
|
||||||
URL: uri,
|
URL: uri,
|
||||||
client: httpClient(transport.NewTransport(tr, Headers(userAgent, metaHeaders)...)),
|
client: httpClient(transport.NewTransport(tr, Headers("", headers)...)),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
package registry // import "github.com/docker/docker/registry"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/api/types/registry"
|
||||||
|
"github.com/docker/docker/errdefs"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/registry/client/auth"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var acceptedSearchFilterTags = map[string]bool{
|
||||||
|
"is-automated": true,
|
||||||
|
"is-official": true,
|
||||||
|
"stars": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search queries the public registry for repositories matching the specified
|
||||||
|
// search term and filters.
|
||||||
|
func (s *Service) Search(ctx context.Context, searchFilters filters.Args, term string, limit int, authConfig *registry.AuthConfig, headers map[string][]string) ([]registry.SearchResult, error) {
|
||||||
|
if err := searchFilters.Validate(acceptedSearchFilterTags); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
isAutomated, err := searchFilters.GetBoolOrDefault("is-automated", false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
isOfficial, err := searchFilters.GetBoolOrDefault("is-official", false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hasStarFilter := 0
|
||||||
|
if searchFilters.Contains("stars") {
|
||||||
|
hasStars := searchFilters.Get("stars")
|
||||||
|
for _, hasStar := range hasStars {
|
||||||
|
iHasStar, err := strconv.Atoi(hasStar)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errdefs.InvalidParameter(errors.Wrapf(err, "invalid filter 'stars=%s'", hasStar))
|
||||||
|
}
|
||||||
|
if iHasStar > hasStarFilter {
|
||||||
|
hasStarFilter = iHasStar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unfilteredResult, err := s.searchUnfiltered(ctx, term, limit, authConfig, headers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredResults := []registry.SearchResult{}
|
||||||
|
for _, result := range unfilteredResult.Results {
|
||||||
|
if searchFilters.Contains("is-automated") {
|
||||||
|
if isAutomated != result.IsAutomated {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if searchFilters.Contains("is-official") {
|
||||||
|
if isOfficial != result.IsOfficial {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if searchFilters.Contains("stars") {
|
||||||
|
if result.StarCount < hasStarFilter {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filteredResults = append(filteredResults, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredResults, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) searchUnfiltered(ctx context.Context, term string, limit int, authConfig *registry.AuthConfig, headers http.Header) (*registry.SearchResults, error) {
|
||||||
|
// TODO Use ctx when searching for repositories
|
||||||
|
if hasScheme(term) {
|
||||||
|
return nil, invalidParamf("invalid repository name: repository name (%s) should not have a scheme", term)
|
||||||
|
}
|
||||||
|
|
||||||
|
indexName, remoteName := splitReposSearchTerm(term)
|
||||||
|
|
||||||
|
// Search is a long-running operation, just lock s.config to avoid block others.
|
||||||
|
s.mu.RLock()
|
||||||
|
index, err := newIndexInfo(s.config, indexName)
|
||||||
|
s.mu.RUnlock()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if index.Official {
|
||||||
|
// If pull "library/foo", it's stored locally under "foo"
|
||||||
|
remoteName = strings.TrimPrefix(remoteName, "library/")
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint, err := newV1Endpoint(index, headers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var client *http.Client
|
||||||
|
if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" {
|
||||||
|
creds := NewStaticCredentialStore(authConfig)
|
||||||
|
scopes := []auth.Scope{
|
||||||
|
auth.RegistryScope{
|
||||||
|
Name: "catalog",
|
||||||
|
Actions: []string{"search"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(thaJeztah); is there a reason not to include other headers here? (originally added in 19d48f0b8ba59eea9f2cac4ad1c7977712a6b7ac)
|
||||||
|
modifiers := Headers(headers.Get("User-Agent"), nil)
|
||||||
|
v2Client, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Copy non transport http client features
|
||||||
|
v2Client.Timeout = endpoint.client.Timeout
|
||||||
|
v2Client.CheckRedirect = endpoint.client.CheckRedirect
|
||||||
|
v2Client.Jar = endpoint.client.Jar
|
||||||
|
|
||||||
|
logrus.Debugf("using v2 client for search to %s", endpoint.URL)
|
||||||
|
client = v2Client
|
||||||
|
} else {
|
||||||
|
client = endpoint.client
|
||||||
|
if err := authorizeClient(client, authConfig, endpoint); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSession(client, endpoint).searchRepositories(remoteName, limit)
|
||||||
|
}
|
|
@ -3,56 +3,40 @@ package registry // import "github.com/docker/docker/registry"
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/distribution/registry/client/auth"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/api/types/registry"
|
"github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service is the interface defining what a registry service should implement.
|
// Service is a registry service. It tracks configuration data such as a list
|
||||||
type Service interface {
|
|
||||||
Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error)
|
|
||||||
LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error)
|
|
||||||
LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error)
|
|
||||||
ResolveRepository(name reference.Named) (*RepositoryInfo, error)
|
|
||||||
Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registry.SearchResults, error)
|
|
||||||
ServiceConfig() *registry.ServiceConfig
|
|
||||||
LoadAllowNondistributableArtifacts([]string) error
|
|
||||||
LoadMirrors([]string) error
|
|
||||||
LoadInsecureRegistries([]string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// defaultService is a registry service. It tracks configuration data such as a list
|
|
||||||
// of mirrors.
|
// of mirrors.
|
||||||
type defaultService struct {
|
type Service struct {
|
||||||
config *serviceConfig
|
config *serviceConfig
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService returns a new instance of defaultService ready to be
|
// NewService returns a new instance of defaultService ready to be
|
||||||
// installed into an engine.
|
// installed into an engine.
|
||||||
func NewService(options ServiceOptions) (Service, error) {
|
func NewService(options ServiceOptions) (*Service, error) {
|
||||||
config, err := newServiceConfig(options)
|
config, err := newServiceConfig(options)
|
||||||
|
|
||||||
return &defaultService{config: config}, err
|
return &Service{config: config}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceConfig returns a copy of the public registry service's configuration.
|
// ServiceConfig returns a copy of the public registry service's configuration.
|
||||||
func (s *defaultService) ServiceConfig() *registry.ServiceConfig {
|
func (s *Service) ServiceConfig() *registry.ServiceConfig {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
return s.config.copy()
|
return s.config.copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries for Service.
|
// LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries for Service.
|
||||||
func (s *defaultService) LoadAllowNondistributableArtifacts(registries []string) error {
|
func (s *Service) LoadAllowNondistributableArtifacts(registries []string) error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
@ -60,7 +44,7 @@ func (s *defaultService) LoadAllowNondistributableArtifacts(registries []string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadMirrors loads registry mirrors for Service
|
// LoadMirrors loads registry mirrors for Service
|
||||||
func (s *defaultService) LoadMirrors(mirrors []string) error {
|
func (s *Service) LoadMirrors(mirrors []string) error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
@ -68,7 +52,7 @@ func (s *defaultService) LoadMirrors(mirrors []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadInsecureRegistries loads insecure registries for Service
|
// LoadInsecureRegistries loads insecure registries for Service
|
||||||
func (s *defaultService) LoadInsecureRegistries(registries []string) error {
|
func (s *Service) LoadInsecureRegistries(registries []string) error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
@ -78,7 +62,7 @@ func (s *defaultService) LoadInsecureRegistries(registries []string) error {
|
||||||
// Auth contacts the public registry with the provided credentials,
|
// Auth contacts the public registry with the provided credentials,
|
||||||
// and returns OK if authentication was successful.
|
// and returns OK if authentication was successful.
|
||||||
// It can be used to verify the validity of a client's credentials.
|
// It can be used to verify the validity of a client's credentials.
|
||||||
func (s *defaultService) Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) {
|
func (s *Service) Auth(ctx context.Context, authConfig *registry.AuthConfig, userAgent string) (status, token string, err error) {
|
||||||
// TODO Use ctx when searching for repositories
|
// TODO Use ctx when searching for repositories
|
||||||
var registryHostName = IndexHostname
|
var registryHostName = IndexHostname
|
||||||
|
|
||||||
|
@ -129,69 +113,9 @@ func splitReposSearchTerm(reposName string) (string, string) {
|
||||||
return nameParts[0], nameParts[1]
|
return nameParts[0], nameParts[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search queries the public registry for images matching the specified
|
|
||||||
// search terms, and returns the results.
|
|
||||||
func (s *defaultService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registry.SearchResults, error) {
|
|
||||||
// TODO Use ctx when searching for repositories
|
|
||||||
if hasScheme(term) {
|
|
||||||
return nil, invalidParamf("invalid repository name: repository name (%s) should not have a scheme", term)
|
|
||||||
}
|
|
||||||
|
|
||||||
indexName, remoteName := splitReposSearchTerm(term)
|
|
||||||
|
|
||||||
// Search is a long-running operation, just lock s.config to avoid block others.
|
|
||||||
s.mu.RLock()
|
|
||||||
index, err := newIndexInfo(s.config, indexName)
|
|
||||||
s.mu.RUnlock()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if index.Official {
|
|
||||||
// If pull "library/foo", it's stored locally under "foo"
|
|
||||||
remoteName = strings.TrimPrefix(remoteName, "library/")
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoint, err := newV1Endpoint(index, userAgent, headers)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var client *http.Client
|
|
||||||
if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" {
|
|
||||||
creds := NewStaticCredentialStore(authConfig)
|
|
||||||
scopes := []auth.Scope{
|
|
||||||
auth.RegistryScope{
|
|
||||||
Name: "catalog",
|
|
||||||
Actions: []string{"search"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
modifiers := Headers(userAgent, nil)
|
|
||||||
v2Client, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Copy non transport http client features
|
|
||||||
v2Client.Timeout = endpoint.client.Timeout
|
|
||||||
v2Client.CheckRedirect = endpoint.client.CheckRedirect
|
|
||||||
v2Client.Jar = endpoint.client.Jar
|
|
||||||
|
|
||||||
logrus.Debugf("using v2 client for search to %s", endpoint.URL)
|
|
||||||
client = v2Client
|
|
||||||
} else {
|
|
||||||
client = endpoint.client
|
|
||||||
if err := authorizeClient(client, authConfig, endpoint); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newSession(client, endpoint).searchRepositories(remoteName, limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveRepository splits a repository name into its components
|
// ResolveRepository splits a repository name into its components
|
||||||
// and configuration of the associated registry.
|
// and configuration of the associated registry.
|
||||||
func (s *defaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
|
func (s *Service) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
return newRepositoryInfo(s.config, name)
|
return newRepositoryInfo(s.config, name)
|
||||||
|
@ -210,7 +134,7 @@ type APIEndpoint struct {
|
||||||
|
|
||||||
// LookupPullEndpoints creates a list of v2 endpoints to try to pull from, in order of preference.
|
// LookupPullEndpoints creates a list of v2 endpoints to try to pull from, in order of preference.
|
||||||
// It gives preference to mirrors over the actual registry, and HTTPS over plain HTTP.
|
// It gives preference to mirrors over the actual registry, and HTTPS over plain HTTP.
|
||||||
func (s *defaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
func (s *Service) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
|
|
||||||
|
@ -219,7 +143,7 @@ func (s *defaultService) LookupPullEndpoints(hostname string) (endpoints []APIEn
|
||||||
|
|
||||||
// LookupPushEndpoints creates a list of v2 endpoints to try to push to, in order of preference.
|
// LookupPushEndpoints creates a list of v2 endpoints to try to push to, in order of preference.
|
||||||
// It gives preference to HTTPS over plain HTTP. Mirrors are not included.
|
// It gives preference to HTTPS over plain HTTP. Mirrors are not included.
|
||||||
func (s *defaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
func (s *Service) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
|
|
||||||
|
@ -233,3 +157,11 @@ func (s *defaultService) LookupPushEndpoints(hostname string) (endpoints []APIEn
|
||||||
}
|
}
|
||||||
return endpoints, err
|
return endpoints, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsInsecureRegistry returns true if the registry at given host is configured as
|
||||||
|
// insecure registry.
|
||||||
|
func (s *Service) IsInsecureRegistry(host string) bool {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
return !s.config.isSecureIndex(host)
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/docker/go-connections/tlsconfig"
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *defaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
func (s *Service) lookupV2Endpoints(hostname string) (endpoints []APIEndpoint, err error) {
|
||||||
ana := s.config.allowNondistributableArtifacts(hostname)
|
ana := s.config.allowNondistributableArtifacts(hostname)
|
||||||
|
|
||||||
if hostname == DefaultNamespace || hostname == IndexHostname {
|
if hostname == DefaultNamespace || hostname == IndexHostname {
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/api/types/registry"
|
"github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/errdefs"
|
"github.com/docker/docker/errdefs"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
|
@ -28,7 +27,7 @@ type session struct {
|
||||||
|
|
||||||
type authTransport struct {
|
type authTransport struct {
|
||||||
http.RoundTripper
|
http.RoundTripper
|
||||||
*types.AuthConfig
|
*registry.AuthConfig
|
||||||
|
|
||||||
alwaysSetBasicAuth bool
|
alwaysSetBasicAuth bool
|
||||||
token []string
|
token []string
|
||||||
|
@ -50,7 +49,7 @@ type authTransport struct {
|
||||||
// If the server sends a token without the client having requested it, it is ignored.
|
// If the server sends a token without the client having requested it, it is ignored.
|
||||||
//
|
//
|
||||||
// This RoundTripper also has a CancelRequest method important for correct timeout handling.
|
// This RoundTripper also has a CancelRequest method important for correct timeout handling.
|
||||||
func newAuthTransport(base http.RoundTripper, authConfig *types.AuthConfig, alwaysSetBasicAuth bool) *authTransport {
|
func newAuthTransport(base http.RoundTripper, authConfig *registry.AuthConfig, alwaysSetBasicAuth bool) *authTransport {
|
||||||
if base == nil {
|
if base == nil {
|
||||||
base = http.DefaultTransport
|
base = http.DefaultTransport
|
||||||
}
|
}
|
||||||
|
@ -145,7 +144,7 @@ func (tr *authTransport) CancelRequest(req *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func authorizeClient(client *http.Client, authConfig *types.AuthConfig, endpoint *v1Endpoint) error {
|
func authorizeClient(client *http.Client, authConfig *registry.AuthConfig, endpoint *v1Endpoint) error {
|
||||||
var alwaysSetBasicAuth bool
|
var alwaysSetBasicAuth bool
|
||||||
|
|
||||||
// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
|
// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
|
||||||
|
@ -207,10 +206,10 @@ func (r *session) searchRepositories(term string, limit int) (*registry.SearchRe
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
return nil, &jsonmessage.JSONError{
|
return nil, errdefs.Unknown(&jsonmessage.JSONError{
|
||||||
Message: fmt.Sprintf("Unexpected status code %d", res.StatusCode),
|
Message: fmt.Sprintf("Unexpected status code %d", res.StatusCode),
|
||||||
Code: res.StatusCode,
|
Code: res.StatusCode,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
result := new(registry.SearchResults)
|
result := new(registry.SearchResults)
|
||||||
return result, errors.Wrap(json.NewDecoder(res.Body).Decode(result), "error decoding registry search results")
|
return result, errors.Wrap(json.NewDecoder(res.Body).Decode(result), "error decoding registry search results")
|
||||||
|
|
|
@ -40,7 +40,7 @@ github.com/docker/distribution/registry/client/transport
|
||||||
github.com/docker/distribution/registry/storage/cache
|
github.com/docker/distribution/registry/storage/cache
|
||||||
github.com/docker/distribution/registry/storage/cache/memory
|
github.com/docker/distribution/registry/storage/cache/memory
|
||||||
github.com/docker/distribution/uuid
|
github.com/docker/distribution/uuid
|
||||||
# github.com/docker/docker v23.0.2+incompatible
|
# github.com/docker/docker v23.0.2+incompatible => github.com/docker/docker v20.10.3-0.20230327175735-54130b542db4+incompatible
|
||||||
## explicit
|
## explicit
|
||||||
github.com/docker/docker/api
|
github.com/docker/docker/api
|
||||||
github.com/docker/docker/api/types
|
github.com/docker/docker/api/types
|
||||||
|
@ -68,7 +68,9 @@ github.com/docker/docker/pkg/idtools
|
||||||
github.com/docker/docker/pkg/ioutils
|
github.com/docker/docker/pkg/ioutils
|
||||||
github.com/docker/docker/pkg/jsonmessage
|
github.com/docker/docker/pkg/jsonmessage
|
||||||
github.com/docker/docker/pkg/longpath
|
github.com/docker/docker/pkg/longpath
|
||||||
|
github.com/docker/docker/pkg/meminfo
|
||||||
github.com/docker/docker/pkg/pools
|
github.com/docker/docker/pkg/pools
|
||||||
|
github.com/docker/docker/pkg/process
|
||||||
github.com/docker/docker/pkg/progress
|
github.com/docker/docker/pkg/progress
|
||||||
github.com/docker/docker/pkg/stdcopy
|
github.com/docker/docker/pkg/stdcopy
|
||||||
github.com/docker/docker/pkg/streamformatter
|
github.com/docker/docker/pkg/streamformatter
|
||||||
|
@ -404,3 +406,4 @@ gotest.tools/v3/internal/format
|
||||||
gotest.tools/v3/internal/source
|
gotest.tools/v3/internal/source
|
||||||
gotest.tools/v3/poll
|
gotest.tools/v3/poll
|
||||||
gotest.tools/v3/skip
|
gotest.tools/v3/skip
|
||||||
|
# github.com/docker/docker => github.com/docker/docker v20.10.3-0.20230327175735-54130b542db4+incompatible
|
||||||
|
|
Loading…
Reference in New Issue