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:
Sebastiaan van Stijn 2023-02-07 16:52:18 +01:00
parent e86d2f4113
commit bfa79fd75a
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
92 changed files with 1311 additions and 1137 deletions

View File

@ -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
} }

View File

@ -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) {

View File

@ -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
} }

View File

@ -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++
} }
} }

View File

@ -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

View File

@ -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=

View File

@ -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>

View File

@ -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.

View File

@ -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:

View File

@ -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"`
}

View File

@ -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

View File

@ -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

View File

@ -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 != ""
}

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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 }

View File

@ -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
} }

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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"

View File

@ -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},
} }
} }

View File

@ -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 {

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)
} }

View File

@ -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

View File

@ -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 != "" {

View File

@ -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
} }

View File

@ -1 +0,0 @@
This code provides helper functions for dealing with archive files.

View File

@ -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.

View File

@ -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
} }

View File

@ -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) {

View File

@ -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
} }

View File

@ -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)
} }

View File

@ -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
}) })

20
vendor/github.com/docker/docker/pkg/archive/path.go generated vendored Normal file
View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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")
} }

View File

@ -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{}

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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)

View File

@ -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()
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
} }

View File

@ -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
}

26
vendor/github.com/docker/docker/pkg/meminfo/meminfo.go generated vendored Normal file
View File

@ -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
}

View File

@ -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() {

View 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")
}

View File

@ -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),

3
vendor/github.com/docker/docker/pkg/process/doc.go generated vendored Normal file
View File

@ -0,0 +1,3 @@
// Package process provides a set of basic functions to manage individual
// processes.
package process

View File

@ -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
}

View File

@ -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
}

View File

@ -1 +0,0 @@
This package provides helper functions for dealing with string identifiers

View File

@ -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
} }

View File

@ -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 {

View File

@ -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)
} }

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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
} }

139
vendor/github.com/docker/docker/registry/search.go generated vendored Normal file
View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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")

5
vendor/modules.txt vendored
View File

@ -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