mirror of https://github.com/docker/cli.git
Merge pull request #231 from tonistiigi/client-session-fssession
Incrementally sending build context
This commit is contained in:
commit
0133e13353
|
@ -15,7 +15,6 @@ import (
|
||||||
dopts "github.com/docker/cli/opts"
|
dopts "github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/versions"
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/go-connections/sockets"
|
"github.com/docker/go-connections/sockets"
|
||||||
"github.com/docker/go-connections/tlsconfig"
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
|
@ -193,15 +192,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
|
||||||
OSType: ping.OSType,
|
OSType: ping.OSType,
|
||||||
}
|
}
|
||||||
|
|
||||||
// since the new header was added in 1.25, assume server is 1.24 if header is not present.
|
cli.client.NegotiateAPIVersionPing(ping)
|
||||||
if ping.APIVersion == "" {
|
|
||||||
ping.APIVersion = "1.24"
|
|
||||||
}
|
|
||||||
|
|
||||||
// if server version is lower than the current cli, downgrade
|
|
||||||
if versions.LessThan(ping.APIVersion, cli.client.ClientVersion()) {
|
|
||||||
cli.client.UpdateClientVersion(ping.APIVersion)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Default to true if we fail to connect to daemon
|
// Default to true if we fail to connect to daemon
|
||||||
cli.server = ServerInfo{HasExperimental: true}
|
cli.server = ServerInfo{HasExperimental: true}
|
||||||
|
|
|
@ -29,11 +29,12 @@ const (
|
||||||
// DiskUsageContext contains disk usage specific information required by the formatter, encapsulate a Context struct.
|
// DiskUsageContext contains disk usage specific information required by the formatter, encapsulate a Context struct.
|
||||||
type DiskUsageContext struct {
|
type DiskUsageContext struct {
|
||||||
Context
|
Context
|
||||||
Verbose bool
|
Verbose bool
|
||||||
LayersSize int64
|
LayersSize int64
|
||||||
Images []*types.ImageSummary
|
Images []*types.ImageSummary
|
||||||
Containers []*types.Container
|
Containers []*types.Container
|
||||||
Volumes []*types.Volume
|
Volumes []*types.Volume
|
||||||
|
BuilderSize int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template, error) {
|
func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template, error) {
|
||||||
|
@ -65,49 +66,59 @@ reclaimable: {{.Reclaimable}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *DiskUsageContext) Write() (err error) {
|
func (ctx *DiskUsageContext) Write() (err error) {
|
||||||
if !ctx.Verbose {
|
if ctx.Verbose {
|
||||||
ctx.buffer = bytes.NewBufferString("")
|
return ctx.verboseWrite()
|
||||||
ctx.preFormat()
|
}
|
||||||
|
ctx.buffer = bytes.NewBufferString("")
|
||||||
tmpl, err := ctx.parseFormat()
|
ctx.preFormat()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ctx.contextFormat(tmpl, &diskUsageImagesContext{
|
|
||||||
totalSize: ctx.LayersSize,
|
|
||||||
images: ctx.Images,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = ctx.contextFormat(tmpl, &diskUsageContainersContext{
|
|
||||||
containers: ctx.Containers,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ctx.contextFormat(tmpl, &diskUsageVolumesContext{
|
|
||||||
volumes: ctx.Volumes,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
diskUsageContainersCtx := diskUsageContainersContext{containers: []*types.Container{}}
|
|
||||||
diskUsageContainersCtx.header = map[string]string{
|
|
||||||
"Type": typeHeader,
|
|
||||||
"TotalCount": totalHeader,
|
|
||||||
"Active": activeHeader,
|
|
||||||
"Size": sizeHeader,
|
|
||||||
"Reclaimable": reclaimableHeader,
|
|
||||||
}
|
|
||||||
ctx.postFormat(tmpl, &diskUsageContainersCtx)
|
|
||||||
|
|
||||||
|
tmpl, err := ctx.parseFormat()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = ctx.contextFormat(tmpl, &diskUsageImagesContext{
|
||||||
|
totalSize: ctx.LayersSize,
|
||||||
|
images: ctx.Images,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ctx.contextFormat(tmpl, &diskUsageContainersContext{
|
||||||
|
containers: ctx.Containers,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ctx.contextFormat(tmpl, &diskUsageVolumesContext{
|
||||||
|
volumes: ctx.Volumes,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ctx.contextFormat(tmpl, &diskUsageBuilderContext{
|
||||||
|
builderSize: ctx.BuilderSize,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
diskUsageContainersCtx := diskUsageContainersContext{containers: []*types.Container{}}
|
||||||
|
diskUsageContainersCtx.header = map[string]string{
|
||||||
|
"Type": typeHeader,
|
||||||
|
"TotalCount": totalHeader,
|
||||||
|
"Active": activeHeader,
|
||||||
|
"Size": sizeHeader,
|
||||||
|
"Reclaimable": reclaimableHeader,
|
||||||
|
}
|
||||||
|
ctx.postFormat(tmpl, &diskUsageContainersCtx)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *DiskUsageContext) verboseWrite() (err error) {
|
||||||
// First images
|
// First images
|
||||||
tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat)
|
tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -176,6 +187,9 @@ func (ctx *DiskUsageContext) Write() (err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.postFormat(tmpl, newVolumeContext())
|
ctx.postFormat(tmpl, newVolumeContext())
|
||||||
|
|
||||||
|
// And build cache
|
||||||
|
fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,3 +368,32 @@ func (c *diskUsageVolumesContext) Reclaimable() string {
|
||||||
|
|
||||||
return fmt.Sprintf("%s", units.HumanSize(float64(reclaimable)))
|
return fmt.Sprintf("%s", units.HumanSize(float64(reclaimable)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type diskUsageBuilderContext struct {
|
||||||
|
HeaderContext
|
||||||
|
builderSize int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *diskUsageBuilderContext) MarshalJSON() ([]byte, error) {
|
||||||
|
return marshalJSON(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *diskUsageBuilderContext) Type() string {
|
||||||
|
return "Build Cache"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *diskUsageBuilderContext) TotalCount() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *diskUsageBuilderContext) Active() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *diskUsageBuilderContext) Size() string {
|
||||||
|
return units.HumanSize(float64(c.builderSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *diskUsageBuilderContext) Reclaimable() string {
|
||||||
|
return c.Size()
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ func TestDiskUsageContextFormatWrite(t *testing.T) {
|
||||||
Images 0 0 0B 0B
|
Images 0 0 0B 0B
|
||||||
Containers 0 0 0B 0B
|
Containers 0 0 0B 0B
|
||||||
Local Volumes 0 0 0B 0B
|
Local Volumes 0 0 0B 0B
|
||||||
|
Build Cache 0B 0B
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -38,6 +39,9 @@ CONTAINER ID IMAGE COMMAND LOCAL VOLUMES
|
||||||
Local Volumes space usage:
|
Local Volumes space usage:
|
||||||
|
|
||||||
VOLUME NAME LINKS SIZE
|
VOLUME NAME LINKS SIZE
|
||||||
|
|
||||||
|
Build cache usage: 0B
|
||||||
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
// Errors
|
// Errors
|
||||||
|
@ -70,6 +74,7 @@ VOLUME NAME LINKS SIZE
|
||||||
Images 0 0 0B 0B
|
Images 0 0 0B 0B
|
||||||
Containers 0 0 0B 0B
|
Containers 0 0 0B 0B
|
||||||
Local Volumes 0 0 0B 0B
|
Local Volumes 0 0 0B 0B
|
||||||
|
Build Cache 0B 0B
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -82,6 +87,7 @@ Local Volumes 0 0 0B
|
||||||
Images 0
|
Images 0
|
||||||
Containers 0
|
Containers 0
|
||||||
Local Volumes 0
|
Local Volumes 0
|
||||||
|
Build Cache
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
// Raw Format
|
// Raw Format
|
||||||
|
@ -109,6 +115,12 @@ active: 0
|
||||||
size: 0B
|
size: 0B
|
||||||
reclaimable: 0B
|
reclaimable: 0B
|
||||||
|
|
||||||
|
type: Build Cache
|
||||||
|
total:
|
||||||
|
active:
|
||||||
|
size: 0B
|
||||||
|
reclaimable: 0B
|
||||||
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/image/build"
|
"github.com/docker/cli/cli/command/image/build"
|
||||||
|
@ -61,6 +62,7 @@ type buildOptions struct {
|
||||||
squash bool
|
squash bool
|
||||||
target string
|
target string
|
||||||
imageIDFile string
|
imageIDFile string
|
||||||
|
stream bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// dockerfileFromStdin returns true when the user specified that the Dockerfile
|
// dockerfileFromStdin returns true when the user specified that the Dockerfile
|
||||||
|
@ -133,6 +135,10 @@ func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
flags.SetAnnotation("squash", "experimental", nil)
|
flags.SetAnnotation("squash", "experimental", nil)
|
||||||
flags.SetAnnotation("squash", "version", []string{"1.25"})
|
flags.SetAnnotation("squash", "version", []string{"1.25"})
|
||||||
|
|
||||||
|
flags.BoolVar(&options.stream, "stream", false, "Stream attaches to server to negotiate build context")
|
||||||
|
flags.SetAnnotation("stream", "experimental", nil)
|
||||||
|
flags.SetAnnotation("stream", "version", []string{"1.31"})
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,6 +169,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
||||||
relDockerfile string
|
relDockerfile string
|
||||||
progBuff io.Writer
|
progBuff io.Writer
|
||||||
buildBuff io.Writer
|
buildBuff io.Writer
|
||||||
|
remote string
|
||||||
)
|
)
|
||||||
|
|
||||||
if options.dockerfileFromStdin() {
|
if options.dockerfileFromStdin() {
|
||||||
|
@ -188,6 +195,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case options.contextFromStdin():
|
case options.contextFromStdin():
|
||||||
|
// buildCtx is tar archive. if stdin was dockerfile then it is wrapped
|
||||||
buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName)
|
buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName)
|
||||||
case isLocalDir(specifiedContext):
|
case isLocalDir(specifiedContext):
|
||||||
contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, options.dockerfileName)
|
contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, options.dockerfileName)
|
||||||
|
@ -211,7 +219,8 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
||||||
contextDir = tempDir
|
contextDir = tempDir
|
||||||
}
|
}
|
||||||
|
|
||||||
if buildCtx == nil {
|
// read from a directory into tar archive
|
||||||
|
if buildCtx == nil && !options.stream {
|
||||||
excludes, err := build.ReadDockerignore(contextDir)
|
excludes, err := build.ReadDockerignore(contextDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -242,24 +251,45 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// replace Dockerfile if added dynamically
|
// replace Dockerfile if it was added from stdin and there is archive context
|
||||||
if dockerfileCtx != nil {
|
if dockerfileCtx != nil && buildCtx != nil {
|
||||||
buildCtx, relDockerfile, err = build.AddDockerfileToBuildContext(dockerfileCtx, buildCtx)
|
buildCtx, relDockerfile, err = build.AddDockerfileToBuildContext(dockerfileCtx, buildCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
// if streaming and dockerfile was not from stdin then read from file
|
||||||
|
// to the same reader that is usually stdin
|
||||||
|
if options.stream && dockerfileCtx == nil {
|
||||||
|
dockerfileCtx, err = os.Open(relDockerfile)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to open %s", relDockerfile)
|
||||||
|
}
|
||||||
|
defer dockerfileCtx.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
var resolvedTags []*resolvedTag
|
var resolvedTags []*resolvedTag
|
||||||
if command.IsTrusted() {
|
if command.IsTrusted() {
|
||||||
translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) {
|
translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) {
|
||||||
return TrustedReference(ctx, dockerCli, ref, nil)
|
return TrustedReference(ctx, dockerCli, ref, nil)
|
||||||
}
|
}
|
||||||
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
|
// if there is a tar wrapper, the dockerfile needs to be replaced inside it
|
||||||
// Dockerfile which uses trusted pulls.
|
if buildCtx != nil {
|
||||||
buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, translator, &resolvedTags)
|
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
|
||||||
|
// Dockerfile which uses trusted pulls.
|
||||||
|
buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, translator, &resolvedTags)
|
||||||
|
} else if dockerfileCtx != nil {
|
||||||
|
// if there was not archive context still do the possible replacements in Dockerfile
|
||||||
|
newDockerfile, _, err := rewriteDockerfileFrom(ctx, dockerfileCtx, translator)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dockerfileCtx = ioutil.NopCloser(bytes.NewBuffer(newDockerfile))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup an upload progress bar
|
// Setup an upload progress bar
|
||||||
|
@ -268,7 +298,43 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
||||||
progressOutput = &lastProgressOutput{output: progressOutput}
|
progressOutput = &lastProgressOutput{output: progressOutput}
|
||||||
}
|
}
|
||||||
|
|
||||||
var body io.Reader = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
|
// if up to this point nothing has set the context then we must have have
|
||||||
|
// another way for sending it(streaming) and set the context to the Dockerfile
|
||||||
|
if dockerfileCtx != nil && buildCtx == nil {
|
||||||
|
buildCtx = dockerfileCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := trySession(dockerCli, contextDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var body io.Reader
|
||||||
|
if buildCtx != nil && !options.stream {
|
||||||
|
body = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
|
||||||
|
}
|
||||||
|
|
||||||
|
// add context stream to the session
|
||||||
|
if options.stream && s != nil {
|
||||||
|
syncDone := make(chan error) // used to signal first progress reporting completed.
|
||||||
|
// progress would also send errors but don't need it here as errors
|
||||||
|
// are handled by session.Run() and ImageBuild()
|
||||||
|
if err := addDirToSession(s, contextDir, progressOutput, syncDone); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := newBufferedWriter(syncDone, buildBuff)
|
||||||
|
defer func() {
|
||||||
|
select {
|
||||||
|
case <-buf.flushed:
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
buildBuff = buf
|
||||||
|
|
||||||
|
remote = clientSessionRemote
|
||||||
|
body = buildCtx
|
||||||
|
}
|
||||||
|
|
||||||
authConfigs, _ := dockerCli.GetAllCredentials()
|
authConfigs, _ := dockerCli.GetAllCredentials()
|
||||||
buildOptions := types.ImageBuildOptions{
|
buildOptions := types.ImageBuildOptions{
|
||||||
|
@ -299,6 +365,18 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
||||||
Squash: options.squash,
|
Squash: options.squash,
|
||||||
ExtraHosts: options.extraHosts.GetAll(),
|
ExtraHosts: options.extraHosts.GetAll(),
|
||||||
Target: options.target,
|
Target: options.target,
|
||||||
|
RemoteContext: remote,
|
||||||
|
}
|
||||||
|
|
||||||
|
if s != nil {
|
||||||
|
go func() {
|
||||||
|
logrus.Debugf("running session: %v", s.UUID())
|
||||||
|
if err := s.Run(ctx, dockerCli.Client().DialSession); err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
|
cancel() // cancel progress context
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
buildOptions.SessionID = s.UUID()
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)
|
response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)
|
||||||
|
@ -306,6 +384,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
||||||
if options.quiet {
|
if options.quiet {
|
||||||
fmt.Fprintf(dockerCli.Err(), "%s", progBuff)
|
fmt.Fprintf(dockerCli.Err(), "%s", progBuff)
|
||||||
}
|
}
|
||||||
|
cancel()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
|
|
|
@ -0,0 +1,151 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/command/image/build"
|
||||||
|
cliconfig "github.com/docker/cli/cli/config"
|
||||||
|
"github.com/docker/docker/api/types/versions"
|
||||||
|
"github.com/docker/docker/client/session"
|
||||||
|
"github.com/docker/docker/client/session/filesync"
|
||||||
|
"github.com/docker/docker/pkg/progress"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
const clientSessionRemote = "client-session"
|
||||||
|
|
||||||
|
func isSessionSupported(dockerCli *command.DockerCli) bool {
|
||||||
|
return dockerCli.ServerInfo().HasExperimental && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.31")
|
||||||
|
}
|
||||||
|
|
||||||
|
func trySession(dockerCli *command.DockerCli, contextDir string) (*session.Session, error) {
|
||||||
|
var s *session.Session
|
||||||
|
if isSessionSupported(dockerCli) {
|
||||||
|
sharedKey, err := getBuildSharedKey(contextDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to get build shared key")
|
||||||
|
}
|
||||||
|
s, err = session.NewSession(filepath.Base(contextDir), sharedKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to create session")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDirToSession(session *session.Session, contextDir string, progressOutput progress.Output, done chan error) error {
|
||||||
|
excludes, err := build.ReadDockerignore(contextDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &sizeProgress{out: progressOutput, action: "Streaming build context to Docker daemon"}
|
||||||
|
|
||||||
|
workdirProvider := filesync.NewFSSyncProvider(contextDir, excludes)
|
||||||
|
session.Allow(workdirProvider)
|
||||||
|
|
||||||
|
// this will be replaced on parallel build jobs. keep the current
|
||||||
|
// progressbar for now
|
||||||
|
if snpc, ok := workdirProvider.(interface {
|
||||||
|
SetNextProgressCallback(func(int, bool), chan error)
|
||||||
|
}); ok {
|
||||||
|
snpc.SetNextProgressCallback(p.update, done)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type sizeProgress struct {
|
||||||
|
out progress.Output
|
||||||
|
action string
|
||||||
|
limiter *rate.Limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *sizeProgress) update(size int, last bool) {
|
||||||
|
if sp.limiter == nil {
|
||||||
|
sp.limiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 1)
|
||||||
|
}
|
||||||
|
if last || sp.limiter.Allow() {
|
||||||
|
sp.out.WriteProgress(progress.Progress{Action: sp.action, Current: int64(size), LastUpdate: last})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type bufferedWriter struct {
|
||||||
|
done chan error
|
||||||
|
io.Writer
|
||||||
|
buf *bytes.Buffer
|
||||||
|
flushed chan struct{}
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBufferedWriter(done chan error, w io.Writer) *bufferedWriter {
|
||||||
|
bw := &bufferedWriter{done: done, Writer: w, buf: new(bytes.Buffer), flushed: make(chan struct{})}
|
||||||
|
go func() {
|
||||||
|
<-done
|
||||||
|
bw.flushBuffer()
|
||||||
|
}()
|
||||||
|
return bw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bw *bufferedWriter) Write(dt []byte) (int, error) {
|
||||||
|
select {
|
||||||
|
case <-bw.done:
|
||||||
|
bw.flushBuffer()
|
||||||
|
return bw.Writer.Write(dt)
|
||||||
|
default:
|
||||||
|
return bw.buf.Write(dt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bw *bufferedWriter) flushBuffer() {
|
||||||
|
bw.mu.Lock()
|
||||||
|
select {
|
||||||
|
case <-bw.flushed:
|
||||||
|
default:
|
||||||
|
bw.Writer.Write(bw.buf.Bytes())
|
||||||
|
close(bw.flushed)
|
||||||
|
}
|
||||||
|
bw.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBuildSharedKey(dir string) (string, error) {
|
||||||
|
// build session is hash of build dir with node based randomness
|
||||||
|
s := sha256.Sum256([]byte(fmt.Sprintf("%s:%s", tryNodeIdentifier(), dir)))
|
||||||
|
return hex.EncodeToString(s[:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryNodeIdentifier() (out string) {
|
||||||
|
out = cliconfig.Dir() // return config dir as default on permission error
|
||||||
|
if err := os.MkdirAll(cliconfig.Dir(), 0700); err == nil {
|
||||||
|
sessionFile := filepath.Join(cliconfig.Dir(), ".buildNodeID")
|
||||||
|
if _, err := os.Lstat(sessionFile); err != nil {
|
||||||
|
if os.IsNotExist(err) { // create a new file with stored randomness
|
||||||
|
b := make([]byte, 32)
|
||||||
|
if _, err := rand.Read(b); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(sessionFile, []byte(hex.EncodeToString(b)), 0600); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err := ioutil.ReadFile(sessionFile)
|
||||||
|
if err == nil {
|
||||||
|
return string(dt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -57,11 +57,12 @@ func runDiskUsage(dockerCli *command.DockerCli, opts diskUsageOptions) error {
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: formatter.NewDiskUsageFormat(format),
|
Format: formatter.NewDiskUsageFormat(format),
|
||||||
},
|
},
|
||||||
LayersSize: du.LayersSize,
|
LayersSize: du.LayersSize,
|
||||||
Images: du.Images,
|
BuilderSize: du.BuilderSize,
|
||||||
Containers: du.Containers,
|
Images: du.Images,
|
||||||
Volumes: du.Volumes,
|
Containers: du.Containers,
|
||||||
Verbose: opts.verbose,
|
Volumes: du.Volumes,
|
||||||
|
Verbose: opts.verbose,
|
||||||
}
|
}
|
||||||
|
|
||||||
return duCtx.Write()
|
return duCtx.Write()
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pruneOptions struct {
|
type pruneOptions struct {
|
||||||
|
@ -49,6 +50,7 @@ const (
|
||||||
- all volumes not used by at least one container
|
- all volumes not used by at least one container
|
||||||
- all networks not used by at least one container
|
- all networks not used by at least one container
|
||||||
%s
|
%s
|
||||||
|
- all build cache
|
||||||
Are you sure you want to continue?`
|
Are you sure you want to continue?`
|
||||||
|
|
||||||
danglingImageDesc = "- all dangling images"
|
danglingImageDesc = "- all dangling images"
|
||||||
|
@ -97,6 +99,12 @@ func runPrune(dockerCli command.Cli, options pruneOptions) error {
|
||||||
fmt.Fprintln(dockerCli.Out(), output)
|
fmt.Fprintln(dockerCli.Out(), output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
report, err := dockerCli.Client().BuildCachePrune(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
spaceReclaimed += report.SpaceReclaimed
|
||||||
|
|
||||||
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
|
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -7,7 +7,7 @@ github.com/coreos/etcd 824277cb3a577a0e8c829ca9ec557b973fe06d20
|
||||||
github.com/cpuguy83/go-md2man a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa
|
github.com/cpuguy83/go-md2man a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa
|
||||||
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
|
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
|
||||||
github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621
|
github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621
|
||||||
github.com/docker/docker 4310f7da7e6bcd8185bf05e032f9b7321cfa6ea2
|
github.com/docker/docker 050c1bb17bd033e909cb653f5449b683608293d6
|
||||||
github.com/docker/docker-credential-helpers v0.5.1
|
github.com/docker/docker-credential-helpers v0.5.1
|
||||||
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06 #?
|
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06 #?
|
||||||
github.com/docker/go-connections e15c02316c12de00874640cd76311849de2aeed5
|
github.com/docker/go-connections e15c02316c12de00874640cd76311849de2aeed5
|
||||||
|
@ -46,3 +46,6 @@ golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756
|
||||||
golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb
|
golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb
|
||||||
google.golang.org/grpc v1.0.4
|
google.golang.org/grpc v1.0.4
|
||||||
gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6
|
gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6
|
||||||
|
github.com/tonistiigi/fsutil 0ac4c11b053b9c5c7c47558f81f96c7100ce50fb
|
||||||
|
github.com/stevvooe/continuity cd7a8e21e2b6f84799f5dd4b65faf49c8d3ee02d
|
||||||
|
golang.org/x/sync de49d9dcd27d4f764488181bea099dfe6179bcf0
|
||||||
|
|
|
@ -126,7 +126,7 @@ func MatchesContentType(contentType, expectedType string) bool {
|
||||||
// LoadOrCreateTrustKey attempts to load the libtrust key at the given path,
|
// LoadOrCreateTrustKey attempts to load the libtrust key at the given path,
|
||||||
// otherwise generates a new one
|
// otherwise generates a new one
|
||||||
func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
|
func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
|
||||||
err := system.MkdirAll(filepath.Dir(trustKeyPath), 0700)
|
err := system.MkdirAll(filepath.Dir(trustKeyPath), 0700, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +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/go-units"
|
units "github.com/docker/go-units"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CheckpointCreateOptions holds parameters to create a checkpoint from a container
|
// CheckpointCreateOptions holds parameters to create a checkpoint from a container
|
||||||
|
@ -178,6 +178,11 @@ type ImageBuildOptions struct {
|
||||||
SecurityOpt []string
|
SecurityOpt []string
|
||||||
ExtraHosts []string // List of extra hosts
|
ExtraHosts []string // List of extra hosts
|
||||||
Target string
|
Target string
|
||||||
|
SessionID string
|
||||||
|
|
||||||
|
// TODO @jhowardmsft LCOW Support: This will require extending to include
|
||||||
|
// `Platform string`, but is ommited for now as it's hard-coded temporarily
|
||||||
|
// to avoid API changes.
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImageBuildResponse holds information
|
// ImageBuildResponse holds information
|
||||||
|
|
|
@ -16,6 +16,7 @@ type ContainerCreateConfig struct {
|
||||||
HostConfig *container.HostConfig
|
HostConfig *container.HostConfig
|
||||||
NetworkingConfig *network.NetworkingConfig
|
NetworkingConfig *network.NetworkingConfig
|
||||||
AdjustCPUShares bool
|
AdjustCPUShares bool
|
||||||
|
Platform string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerRmConfig holds arguments for the container remove
|
// ContainerRmConfig holds arguments for the container remove
|
||||||
|
|
|
@ -320,6 +320,7 @@ type ContainerJSONBase struct {
|
||||||
Name string
|
Name string
|
||||||
RestartCount int
|
RestartCount int
|
||||||
Driver string
|
Driver string
|
||||||
|
Platform string
|
||||||
MountLabel string
|
MountLabel string
|
||||||
ProcessLabel string
|
ProcessLabel string
|
||||||
AppArmorProfile string
|
AppArmorProfile string
|
||||||
|
@ -488,10 +489,11 @@ type Runtime struct {
|
||||||
// DiskUsage contains response of Engine API:
|
// DiskUsage contains response of Engine API:
|
||||||
// GET "/system/df"
|
// GET "/system/df"
|
||||||
type DiskUsage struct {
|
type DiskUsage struct {
|
||||||
LayersSize int64
|
LayersSize int64
|
||||||
Images []*ImageSummary
|
Images []*ImageSummary
|
||||||
Containers []*Container
|
Containers []*Container
|
||||||
Volumes []*Volume
|
Volumes []*Volume
|
||||||
|
BuilderSize int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainersPruneReport contains the response for Engine API:
|
// ContainersPruneReport contains the response for Engine API:
|
||||||
|
@ -515,6 +517,12 @@ type ImagesPruneReport struct {
|
||||||
SpaceReclaimed uint64
|
SpaceReclaimed uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildCachePruneReport contains the response for Engine API:
|
||||||
|
// POST "/build/prune"
|
||||||
|
type BuildCachePruneReport struct {
|
||||||
|
SpaceReclaimed uint64
|
||||||
|
}
|
||||||
|
|
||||||
// NetworksPruneReport contains the response for Engine API:
|
// NetworksPruneReport contains the response for Engine API:
|
||||||
// POST "/networks/prune"
|
// POST "/networks/prune"
|
||||||
type NetworksPruneReport struct {
|
type NetworksPruneReport struct {
|
||||||
|
|
|
@ -38,8 +38,23 @@ func ReadAll(reader io.Reader) ([]string, error) {
|
||||||
if pattern == "" {
|
if pattern == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pattern = filepath.Clean(pattern)
|
// normalize absolute paths to paths relative to the context
|
||||||
pattern = filepath.ToSlash(pattern)
|
// (taking care of '!' prefix)
|
||||||
|
invert := pattern[0] == '!'
|
||||||
|
if invert {
|
||||||
|
pattern = strings.TrimSpace(pattern[1:])
|
||||||
|
}
|
||||||
|
if len(pattern) > 0 {
|
||||||
|
pattern = filepath.Clean(pattern)
|
||||||
|
pattern = filepath.ToSlash(pattern)
|
||||||
|
if len(pattern) > 1 && pattern[0] == '/' {
|
||||||
|
pattern = pattern[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if invert {
|
||||||
|
pattern = "!" + pattern
|
||||||
|
}
|
||||||
|
|
||||||
excludes = append(excludes, pattern)
|
excludes = append(excludes, pattern)
|
||||||
}
|
}
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BuildCachePrune requests the daemon to delete unused cache data
|
||||||
|
func (cli *Client) BuildCachePrune(ctx context.Context) (*types.BuildCachePruneReport, error) {
|
||||||
|
if err := cli.NewVersionError("1.31", "build prune"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
report := types.BuildCachePruneReport{}
|
||||||
|
|
||||||
|
serverResp, err := cli.post(ctx, "/build/prune", nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer ensureReaderClosed(serverResp)
|
||||||
|
|
||||||
|
if err := json.NewDecoder(serverResp.body).Decode(&report); err != nil {
|
||||||
|
return nil, fmt.Errorf("Error retrieving disk usage: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &report, nil
|
||||||
|
}
|
|
@ -55,8 +55,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/versions"
|
||||||
"github.com/docker/go-connections/sockets"
|
"github.com/docker/go-connections/sockets"
|
||||||
"github.com/docker/go-connections/tlsconfig"
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrRedirect is the error returned by checkRedirect when the request is non-GET.
|
// ErrRedirect is the error returned by checkRedirect when the request is non-GET.
|
||||||
|
@ -238,13 +241,29 @@ func (cli *Client) ClientVersion() string {
|
||||||
return cli.version
|
return cli.version
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateClientVersion updates the version string associated with this
|
// NegotiateAPIVersion updates the version string associated with this
|
||||||
// instance of the Client. This operation doesn't acquire a mutex.
|
// instance of the Client to match the latest version the server supports
|
||||||
func (cli *Client) UpdateClientVersion(v string) {
|
func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
|
||||||
if !cli.manualOverride {
|
ping, _ := cli.Ping(ctx)
|
||||||
cli.version = v
|
cli.NegotiateAPIVersionPing(ping)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NegotiateAPIVersionPing updates the version string associated with this
|
||||||
|
// instance of the Client to match the latest version the server supports
|
||||||
|
func (cli *Client) NegotiateAPIVersionPing(p types.Ping) {
|
||||||
|
if cli.manualOverride {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// try the latest version before versioning headers existed
|
||||||
|
if p.APIVersion == "" {
|
||||||
|
p.APIVersion = "1.24"
|
||||||
|
}
|
||||||
|
|
||||||
|
// if server version is lower than the current cli, downgrade
|
||||||
|
if versions.LessThan(p.APIVersion, cli.ClientVersion()) {
|
||||||
|
cli.version = p.APIVersion
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DaemonHost returns the host associated with this instance of the Client.
|
// DaemonHost returns the host associated with this instance of the Client.
|
||||||
|
|
|
@ -11,6 +11,9 @@ import (
|
||||||
// ConfigCreate creates a new Config.
|
// ConfigCreate creates a new Config.
|
||||||
func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
|
func (cli *Client) ConfigCreate(ctx context.Context, config swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
|
||||||
var response types.ConfigCreateResponse
|
var response types.ConfigCreateResponse
|
||||||
|
if err := cli.NewVersionError("1.30", "config create"); err != nil {
|
||||||
|
return response, err
|
||||||
|
}
|
||||||
resp, err := cli.post(ctx, "/configs/create", nil, config, nil)
|
resp, err := cli.post(ctx, "/configs/create", nil, config, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response, err
|
return response, err
|
||||||
|
|
|
@ -12,6 +12,9 @@ import (
|
||||||
|
|
||||||
// ConfigInspectWithRaw returns the config information with raw data
|
// ConfigInspectWithRaw returns the config information with raw data
|
||||||
func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error) {
|
func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error) {
|
||||||
|
if err := cli.NewVersionError("1.30", "config inspect"); err != nil {
|
||||||
|
return swarm.Config{}, nil, err
|
||||||
|
}
|
||||||
resp, err := cli.get(ctx, "/configs/"+id, nil, nil)
|
resp, err := cli.get(ctx, "/configs/"+id, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if resp.statusCode == http.StatusNotFound {
|
if resp.statusCode == http.StatusNotFound {
|
||||||
|
|
|
@ -12,6 +12,9 @@ import (
|
||||||
|
|
||||||
// ConfigList returns the list of configs.
|
// ConfigList returns the list of configs.
|
||||||
func (cli *Client) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
|
func (cli *Client) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
|
||||||
|
if err := cli.NewVersionError("1.30", "config list"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
query := url.Values{}
|
query := url.Values{}
|
||||||
|
|
||||||
if options.Filters.Len() > 0 {
|
if options.Filters.Len() > 0 {
|
||||||
|
|
|
@ -4,6 +4,9 @@ import "golang.org/x/net/context"
|
||||||
|
|
||||||
// ConfigRemove removes a Config.
|
// ConfigRemove removes a Config.
|
||||||
func (cli *Client) ConfigRemove(ctx context.Context, id string) error {
|
func (cli *Client) ConfigRemove(ctx context.Context, id string) error {
|
||||||
|
if err := cli.NewVersionError("1.30", "config remove"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
resp, err := cli.delete(ctx, "/configs/"+id, nil, nil)
|
resp, err := cli.delete(ctx, "/configs/"+id, nil, nil)
|
||||||
ensureReaderClosed(resp)
|
ensureReaderClosed(resp)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -10,6 +10,9 @@ import (
|
||||||
|
|
||||||
// ConfigUpdate attempts to update a Config
|
// ConfigUpdate attempts to update a Config
|
||||||
func (cli *Client) ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error {
|
func (cli *Client) ConfigUpdate(ctx context.Context, id string, version swarm.Version, config swarm.ConfigSpec) error {
|
||||||
|
if err := cli.NewVersionError("1.30", "config update"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
query := url.Values{}
|
query := url.Values{}
|
||||||
query.Set("version", strconv.FormatUint(version.Index, 10))
|
query.Set("version", strconv.FormatUint(version.Index, 10))
|
||||||
resp, err := cli.post(ctx, "/configs/"+id+"/update", query, config, nil)
|
resp, err := cli.post(ctx, "/configs/"+id+"/update", query, config, nil)
|
||||||
|
|
|
@ -10,6 +10,12 @@ import (
|
||||||
|
|
||||||
// DistributionInspect returns the image digest with full Manifest
|
// DistributionInspect returns the image digest with 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) (registrytypes.DistributionInspect, error) {
|
||||||
|
// Contact the registry to retrieve digest and platform information
|
||||||
|
var distributionInspect registrytypes.DistributionInspect
|
||||||
|
|
||||||
|
if err := cli.NewVersionError("1.30", "distribution inspect"); err != nil {
|
||||||
|
return distributionInspect, err
|
||||||
|
}
|
||||||
var headers map[string][]string
|
var headers map[string][]string
|
||||||
|
|
||||||
if encodedRegistryAuth != "" {
|
if encodedRegistryAuth != "" {
|
||||||
|
@ -18,8 +24,6 @@ func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegist
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contact the registry to retrieve digest and platform information
|
|
||||||
var distributionInspect registrytypes.DistributionInspect
|
|
||||||
resp, err := cli.get(ctx, "/distribution/"+image+"/json", url.Values{}, headers)
|
resp, err := cli.get(ctx, "/distribution/"+image+"/json", url.Values{}, headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return distributionInspect, err
|
return distributionInspect, err
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bufio"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
|
@ -16,6 +14,7 @@ import (
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/pkg/tlsconfig"
|
"github.com/docker/docker/pkg/tlsconfig"
|
||||||
"github.com/docker/go-connections/sockets"
|
"github.com/docker/go-connections/sockets"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,49 +47,12 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu
|
||||||
}
|
}
|
||||||
req = cli.addHeaders(req, headers)
|
req = cli.addHeaders(req, headers)
|
||||||
|
|
||||||
req.Host = cli.addr
|
conn, err := cli.setupHijackConn(req, "tcp")
|
||||||
req.Header.Set("Connection", "Upgrade")
|
|
||||||
req.Header.Set("Upgrade", "tcp")
|
|
||||||
|
|
||||||
conn, err := dial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
|
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(err.Error(), "connection refused") {
|
|
||||||
return types.HijackedResponse{}, fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
|
|
||||||
}
|
|
||||||
return types.HijackedResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// When we set up a TCP connection for hijack, there could be long periods
|
|
||||||
// of inactivity (a long running command with no output) that in certain
|
|
||||||
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
|
||||||
// state. Setting TCP KeepAlive on the socket connection will prohibit
|
|
||||||
// ECONNTIMEOUT unless the socket connection truly is broken
|
|
||||||
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
|
||||||
tcpConn.SetKeepAlive(true)
|
|
||||||
tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
clientconn := httputil.NewClientConn(conn, nil)
|
|
||||||
defer clientconn.Close()
|
|
||||||
|
|
||||||
// Server hijacks the connection, error 'connection closed' expected
|
|
||||||
resp, err := clientconn.Do(req)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.HijackedResponse{}, err
|
return types.HijackedResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err
|
||||||
switch resp.StatusCode {
|
|
||||||
case http.StatusOK, http.StatusSwitchingProtocols:
|
|
||||||
rwc, br := clientconn.Hijack()
|
|
||||||
return types.HijackedResponse{Conn: rwc, Reader: br}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
errbody, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return types.HijackedResponse{}, err
|
|
||||||
}
|
|
||||||
return types.HijackedResponse{}, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(errbody))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
|
func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
|
||||||
|
@ -189,3 +151,56 @@ func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
|
||||||
}
|
}
|
||||||
return net.Dial(proto, addr)
|
return net.Dial(proto, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cli *Client) setupHijackConn(req *http.Request, proto string) (net.Conn, error) {
|
||||||
|
req.Host = cli.addr
|
||||||
|
req.Header.Set("Connection", "Upgrade")
|
||||||
|
req.Header.Set("Upgrade", proto)
|
||||||
|
|
||||||
|
conn, err := dial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we set up a TCP connection for hijack, there could be long periods
|
||||||
|
// of inactivity (a long running command with no output) that in certain
|
||||||
|
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
||||||
|
// state. Setting TCP KeepAlive on the socket connection will prohibit
|
||||||
|
// ECONNTIMEOUT unless the socket connection truly is broken
|
||||||
|
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||||
|
tcpConn.SetKeepAlive(true)
|
||||||
|
tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientconn := httputil.NewClientConn(conn, nil)
|
||||||
|
defer clientconn.Close()
|
||||||
|
|
||||||
|
// Server hijacks the connection, error 'connection closed' expected
|
||||||
|
resp, err := clientconn.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusSwitchingProtocols {
|
||||||
|
resp.Body.Close()
|
||||||
|
return nil, fmt.Errorf("unable to upgrade to %s, received %d", proto, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, br := clientconn.Hijack()
|
||||||
|
if br.Buffered() > 0 {
|
||||||
|
// If there is buffered content, wrap the connection
|
||||||
|
c = &hijackedConn{c, br}
|
||||||
|
} else {
|
||||||
|
br.Reset(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type hijackedConn struct {
|
||||||
|
net.Conn
|
||||||
|
r *bufio.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *hijackedConn) Read(b []byte) (int, error) {
|
||||||
|
return c.r.Read(b)
|
||||||
|
}
|
||||||
|
|
|
@ -120,6 +120,9 @@ func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (ur
|
||||||
return query, err
|
return query, err
|
||||||
}
|
}
|
||||||
query.Set("cachefrom", string(cacheFromJSON))
|
query.Set("cachefrom", string(cacheFromJSON))
|
||||||
|
if options.SessionID != "" {
|
||||||
|
query.Set("session", options.SessionID)
|
||||||
|
}
|
||||||
|
|
||||||
return query, nil
|
return query, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
@ -33,7 +34,9 @@ type CommonAPIClient interface {
|
||||||
ClientVersion() string
|
ClientVersion() string
|
||||||
DaemonHost() string
|
DaemonHost() string
|
||||||
ServerVersion(ctx context.Context) (types.Version, error)
|
ServerVersion(ctx context.Context) (types.Version, error)
|
||||||
UpdateClientVersion(v string)
|
NegotiateAPIVersion(ctx context.Context)
|
||||||
|
NegotiateAPIVersionPing(types.Ping)
|
||||||
|
DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerAPIClient defines API client methods for the containers
|
// ContainerAPIClient defines API client methods for the containers
|
||||||
|
@ -79,6 +82,7 @@ type DistributionAPIClient interface {
|
||||||
// ImageAPIClient defines API client methods for the images
|
// ImageAPIClient defines API client methods for the images
|
||||||
type ImageAPIClient interface {
|
type ImageAPIClient interface {
|
||||||
ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error)
|
ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error)
|
||||||
|
BuildCachePrune(ctx context.Context) (*types.BuildCachePruneReport, error)
|
||||||
ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error)
|
ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error)
|
||||||
ImageHistory(ctx context.Context, image string) ([]image.HistoryResponseItem, error)
|
ImageHistory(ctx context.Context, image string) ([]image.HistoryResponseItem, error)
|
||||||
ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
|
ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
|
||||||
|
|
|
@ -12,6 +12,9 @@ import (
|
||||||
|
|
||||||
// PluginUpgrade upgrades a plugin
|
// PluginUpgrade upgrades a plugin
|
||||||
func (cli *Client) PluginUpgrade(ctx context.Context, name string, options types.PluginInstallOptions) (rc io.ReadCloser, err error) {
|
func (cli *Client) PluginUpgrade(ctx context.Context, name string, options types.PluginInstallOptions) (rc io.ReadCloser, err error) {
|
||||||
|
if err := cli.NewVersionError("1.26", "plugin upgrade"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
query := url.Values{}
|
query := url.Values{}
|
||||||
if _, err := reference.ParseNormalizedNamed(options.RemoteRef); err != nil {
|
if _, err := reference.ParseNormalizedNamed(options.RemoteRef); err != nil {
|
||||||
return nil, errors.Wrap(err, "invalid remote reference")
|
return nil, errors.Wrap(err, "invalid remote reference")
|
||||||
|
|
|
@ -11,6 +11,9 @@ import (
|
||||||
// SecretCreate creates a new Secret.
|
// SecretCreate creates a new Secret.
|
||||||
func (cli *Client) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (types.SecretCreateResponse, error) {
|
func (cli *Client) SecretCreate(ctx context.Context, secret swarm.SecretSpec) (types.SecretCreateResponse, error) {
|
||||||
var response types.SecretCreateResponse
|
var response types.SecretCreateResponse
|
||||||
|
if err := cli.NewVersionError("1.25", "secret create"); err != nil {
|
||||||
|
return response, err
|
||||||
|
}
|
||||||
resp, err := cli.post(ctx, "/secrets/create", nil, secret, nil)
|
resp, err := cli.post(ctx, "/secrets/create", nil, secret, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response, err
|
return response, err
|
||||||
|
|
|
@ -12,6 +12,9 @@ import (
|
||||||
|
|
||||||
// SecretInspectWithRaw returns the secret information with raw data
|
// SecretInspectWithRaw returns the secret information with raw data
|
||||||
func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.Secret, []byte, error) {
|
func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.Secret, []byte, error) {
|
||||||
|
if err := cli.NewVersionError("1.25", "secret inspect"); err != nil {
|
||||||
|
return swarm.Secret{}, nil, err
|
||||||
|
}
|
||||||
resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
|
resp, err := cli.get(ctx, "/secrets/"+id, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if resp.statusCode == http.StatusNotFound {
|
if resp.statusCode == http.StatusNotFound {
|
||||||
|
|
|
@ -12,6 +12,9 @@ import (
|
||||||
|
|
||||||
// SecretList returns the list of secrets.
|
// SecretList returns the list of secrets.
|
||||||
func (cli *Client) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
|
func (cli *Client) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
|
||||||
|
if err := cli.NewVersionError("1.25", "secret list"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
query := url.Values{}
|
query := url.Values{}
|
||||||
|
|
||||||
if options.Filters.Len() > 0 {
|
if options.Filters.Len() > 0 {
|
||||||
|
|
|
@ -4,6 +4,9 @@ import "golang.org/x/net/context"
|
||||||
|
|
||||||
// SecretRemove removes a Secret.
|
// SecretRemove removes a Secret.
|
||||||
func (cli *Client) SecretRemove(ctx context.Context, id string) error {
|
func (cli *Client) SecretRemove(ctx context.Context, id string) error {
|
||||||
|
if err := cli.NewVersionError("1.25", "secret remove"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)
|
resp, err := cli.delete(ctx, "/secrets/"+id, nil, nil)
|
||||||
ensureReaderClosed(resp)
|
ensureReaderClosed(resp)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -10,6 +10,9 @@ import (
|
||||||
|
|
||||||
// SecretUpdate attempts to update a Secret
|
// SecretUpdate attempts to update a Secret
|
||||||
func (cli *Client) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error {
|
func (cli *Client) SecretUpdate(ctx context.Context, id string, version swarm.Version, secret swarm.SecretSpec) error {
|
||||||
|
if err := cli.NewVersionError("1.25", "secret update"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
query := url.Values{}
|
query := url.Values{}
|
||||||
query.Set("version", strconv.FormatUint(version.Index, 10))
|
query.Set("version", strconv.FormatUint(version.Index, 10))
|
||||||
resp, err := cli.post(ctx, "/secrets/"+id+"/update", query, secret, nil)
|
resp, err := cli.post(ctx, "/secrets/"+id+"/update", query, secret, nil)
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DialSession returns a connection that can be used communication with daemon
|
||||||
|
func (cli *Client) DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
|
||||||
|
req, err := http.NewRequest("POST", "/session", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req = cli.addHeaders(req, meta)
|
||||||
|
|
||||||
|
return cli.setupHijackConn(req, proto)
|
||||||
|
}
|
30
vendor/github.com/docker/docker/client/session/filesync/diffcopy.go
generated
vendored
Normal file
30
vendor/github.com/docker/docker/client/session/filesync/diffcopy.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package filesync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/tonistiigi/fsutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sendDiffCopy(stream grpc.Stream, dir string, excludes []string, progress progressCb) error {
|
||||||
|
return fsutil.Send(stream.Context(), stream, dir, &fsutil.WalkOpt{
|
||||||
|
ExcludePatterns: excludes,
|
||||||
|
}, progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func recvDiffCopy(ds grpc.Stream, dest string, cu CacheUpdater) error {
|
||||||
|
st := time.Now()
|
||||||
|
defer func() {
|
||||||
|
logrus.Debugf("diffcopy took: %v", time.Since(st))
|
||||||
|
}()
|
||||||
|
var cf fsutil.ChangeFunc
|
||||||
|
if cu != nil {
|
||||||
|
cu.MarkSupported(true)
|
||||||
|
cf = cu.HandleChange
|
||||||
|
}
|
||||||
|
|
||||||
|
return fsutil.Receive(ds.Context(), ds, dest, cf)
|
||||||
|
}
|
173
vendor/github.com/docker/docker/client/session/filesync/filesync.go
generated
vendored
Normal file
173
vendor/github.com/docker/docker/client/session/filesync/filesync.go
generated
vendored
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
package filesync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/client/session"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/tonistiigi/fsutil"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fsSyncProvider struct {
|
||||||
|
root string
|
||||||
|
excludes []string
|
||||||
|
p progressCb
|
||||||
|
doneCh chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFSSyncProvider creates a new provider for sending files from client
|
||||||
|
func NewFSSyncProvider(root string, excludes []string) session.Attachable {
|
||||||
|
p := &fsSyncProvider{
|
||||||
|
root: root,
|
||||||
|
excludes: excludes,
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *fsSyncProvider) Register(server *grpc.Server) {
|
||||||
|
RegisterFileSyncServer(server, sp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *fsSyncProvider) DiffCopy(stream FileSync_DiffCopyServer) error {
|
||||||
|
return sp.handle("diffcopy", stream)
|
||||||
|
}
|
||||||
|
func (sp *fsSyncProvider) TarStream(stream FileSync_TarStreamServer) error {
|
||||||
|
return sp.handle("tarstream", stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *fsSyncProvider) handle(method string, stream grpc.ServerStream) error {
|
||||||
|
var pr *protocol
|
||||||
|
for _, p := range supportedProtocols {
|
||||||
|
if method == p.name && isProtoSupported(p.name) {
|
||||||
|
pr = &p
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pr == nil {
|
||||||
|
return errors.New("failed to negotiate protocol")
|
||||||
|
}
|
||||||
|
|
||||||
|
opts, _ := metadata.FromContext(stream.Context()) // if no metadata continue with empty object
|
||||||
|
|
||||||
|
var excludes []string
|
||||||
|
if len(opts["Override-Excludes"]) == 0 || opts["Override-Excludes"][0] != "true" {
|
||||||
|
excludes = sp.excludes
|
||||||
|
}
|
||||||
|
|
||||||
|
var progress progressCb
|
||||||
|
if sp.p != nil {
|
||||||
|
progress = sp.p
|
||||||
|
sp.p = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var doneCh chan error
|
||||||
|
if sp.doneCh != nil {
|
||||||
|
doneCh = sp.doneCh
|
||||||
|
sp.doneCh = nil
|
||||||
|
}
|
||||||
|
err := pr.sendFn(stream, sp.root, excludes, progress)
|
||||||
|
if doneCh != nil {
|
||||||
|
if err != nil {
|
||||||
|
doneCh <- err
|
||||||
|
}
|
||||||
|
close(doneCh)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sp *fsSyncProvider) SetNextProgressCallback(f func(int, bool), doneCh chan error) {
|
||||||
|
sp.p = f
|
||||||
|
sp.doneCh = doneCh
|
||||||
|
}
|
||||||
|
|
||||||
|
type progressCb func(int, bool)
|
||||||
|
|
||||||
|
type protocol struct {
|
||||||
|
name string
|
||||||
|
sendFn func(stream grpc.Stream, srcDir string, excludes []string, progress progressCb) error
|
||||||
|
recvFn func(stream grpc.Stream, destDir string, cu CacheUpdater) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func isProtoSupported(p string) bool {
|
||||||
|
// TODO: this should be removed after testing if stability is confirmed
|
||||||
|
if override := os.Getenv("BUILD_STREAM_PROTOCOL"); override != "" {
|
||||||
|
return strings.EqualFold(p, override)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var supportedProtocols = []protocol{
|
||||||
|
{
|
||||||
|
name: "diffcopy",
|
||||||
|
sendFn: sendDiffCopy,
|
||||||
|
recvFn: recvDiffCopy,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tarstream",
|
||||||
|
sendFn: sendTarStream,
|
||||||
|
recvFn: recvTarStream,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// FSSendRequestOpt defines options for FSSend request
|
||||||
|
type FSSendRequestOpt struct {
|
||||||
|
SrcPaths []string
|
||||||
|
OverrideExcludes bool
|
||||||
|
DestDir string
|
||||||
|
CacheUpdater CacheUpdater
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheUpdater is an object capable of sending notifications for the cache hash changes
|
||||||
|
type CacheUpdater interface {
|
||||||
|
MarkSupported(bool)
|
||||||
|
HandleChange(fsutil.ChangeKind, string, os.FileInfo, error) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// FSSync initializes a transfer of files
|
||||||
|
func FSSync(ctx context.Context, c session.Caller, opt FSSendRequestOpt) error {
|
||||||
|
var pr *protocol
|
||||||
|
for _, p := range supportedProtocols {
|
||||||
|
if isProtoSupported(p.name) && c.Supports(session.MethodURL(_FileSync_serviceDesc.ServiceName, p.name)) {
|
||||||
|
pr = &p
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pr == nil {
|
||||||
|
return errors.New("no fssync handlers")
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := make(map[string][]string)
|
||||||
|
if opt.OverrideExcludes {
|
||||||
|
opts["Override-Excludes"] = []string{"true"}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
client := NewFileSyncClient(c.Conn())
|
||||||
|
|
||||||
|
var stream grpc.ClientStream
|
||||||
|
|
||||||
|
ctx = metadata.NewContext(ctx, opts)
|
||||||
|
|
||||||
|
switch pr.name {
|
||||||
|
case "tarstream":
|
||||||
|
cc, err := client.TarStream(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stream = cc
|
||||||
|
case "diffcopy":
|
||||||
|
cc, err := client.DiffCopy(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stream = cc
|
||||||
|
}
|
||||||
|
|
||||||
|
return pr.recvFn(stream, opt.DestDir, opt.CacheUpdater)
|
||||||
|
}
|
575
vendor/github.com/docker/docker/client/session/filesync/filesync.pb.go
generated
vendored
Normal file
575
vendor/github.com/docker/docker/client/session/filesync/filesync.pb.go
generated
vendored
Normal file
|
@ -0,0 +1,575 @@
|
||||||
|
// Code generated by protoc-gen-gogo.
|
||||||
|
// source: filesync.proto
|
||||||
|
// DO NOT EDIT!
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package filesync is a generated protocol buffer package.
|
||||||
|
|
||||||
|
It is generated from these files:
|
||||||
|
filesync.proto
|
||||||
|
|
||||||
|
It has these top-level messages:
|
||||||
|
BytesMessage
|
||||||
|
*/
|
||||||
|
package filesync
|
||||||
|
|
||||||
|
import proto "github.com/gogo/protobuf/proto"
|
||||||
|
import fmt "fmt"
|
||||||
|
import math "math"
|
||||||
|
|
||||||
|
import bytes "bytes"
|
||||||
|
|
||||||
|
import strings "strings"
|
||||||
|
import reflect "reflect"
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "golang.org/x/net/context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
import io "io"
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ = proto.Marshal
|
||||||
|
var _ = fmt.Errorf
|
||||||
|
var _ = math.Inf
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the proto package it is being compiled against.
|
||||||
|
// A compilation error at this line likely means your copy of the
|
||||||
|
// proto package needs to be updated.
|
||||||
|
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
|
||||||
|
|
||||||
|
// BytesMessage contains a chunk of byte data
|
||||||
|
type BytesMessage struct {
|
||||||
|
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BytesMessage) Reset() { *m = BytesMessage{} }
|
||||||
|
func (*BytesMessage) ProtoMessage() {}
|
||||||
|
func (*BytesMessage) Descriptor() ([]byte, []int) { return fileDescriptorFilesync, []int{0} }
|
||||||
|
|
||||||
|
func (m *BytesMessage) GetData() []byte {
|
||||||
|
if m != nil {
|
||||||
|
return m.Data
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterType((*BytesMessage)(nil), "moby.filesync.v1.BytesMessage")
|
||||||
|
}
|
||||||
|
func (this *BytesMessage) Equal(that interface{}) bool {
|
||||||
|
if that == nil {
|
||||||
|
if this == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
that1, ok := that.(*BytesMessage)
|
||||||
|
if !ok {
|
||||||
|
that2, ok := that.(BytesMessage)
|
||||||
|
if ok {
|
||||||
|
that1 = &that2
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if that1 == nil {
|
||||||
|
if this == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} else if this == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !bytes.Equal(this.Data, that1.Data) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func (this *BytesMessage) GoString() string {
|
||||||
|
if this == nil {
|
||||||
|
return "nil"
|
||||||
|
}
|
||||||
|
s := make([]string, 0, 5)
|
||||||
|
s = append(s, "&filesync.BytesMessage{")
|
||||||
|
s = append(s, "Data: "+fmt.Sprintf("%#v", this.Data)+",\n")
|
||||||
|
s = append(s, "}")
|
||||||
|
return strings.Join(s, "")
|
||||||
|
}
|
||||||
|
func valueToGoStringFilesync(v interface{}, typ string) string {
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if rv.IsNil() {
|
||||||
|
return "nil"
|
||||||
|
}
|
||||||
|
pv := reflect.Indirect(rv).Interface()
|
||||||
|
return fmt.Sprintf("func(v %v) *%v { return &v } ( %#v )", typ, typ, pv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ context.Context
|
||||||
|
var _ grpc.ClientConn
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
const _ = grpc.SupportPackageIsVersion4
|
||||||
|
|
||||||
|
// Client API for FileSync service
|
||||||
|
|
||||||
|
type FileSyncClient interface {
|
||||||
|
DiffCopy(ctx context.Context, opts ...grpc.CallOption) (FileSync_DiffCopyClient, error)
|
||||||
|
TarStream(ctx context.Context, opts ...grpc.CallOption) (FileSync_TarStreamClient, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileSyncClient struct {
|
||||||
|
cc *grpc.ClientConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileSyncClient(cc *grpc.ClientConn) FileSyncClient {
|
||||||
|
return &fileSyncClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fileSyncClient) DiffCopy(ctx context.Context, opts ...grpc.CallOption) (FileSync_DiffCopyClient, error) {
|
||||||
|
stream, err := grpc.NewClientStream(ctx, &_FileSync_serviceDesc.Streams[0], c.cc, "/moby.filesync.v1.FileSync/DiffCopy", opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &fileSyncDiffCopyClient{stream}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileSync_DiffCopyClient interface {
|
||||||
|
Send(*BytesMessage) error
|
||||||
|
Recv() (*BytesMessage, error)
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileSyncDiffCopyClient struct {
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *fileSyncDiffCopyClient) Send(m *BytesMessage) error {
|
||||||
|
return x.ClientStream.SendMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *fileSyncDiffCopyClient) Recv() (*BytesMessage, error) {
|
||||||
|
m := new(BytesMessage)
|
||||||
|
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fileSyncClient) TarStream(ctx context.Context, opts ...grpc.CallOption) (FileSync_TarStreamClient, error) {
|
||||||
|
stream, err := grpc.NewClientStream(ctx, &_FileSync_serviceDesc.Streams[1], c.cc, "/moby.filesync.v1.FileSync/TarStream", opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &fileSyncTarStreamClient{stream}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileSync_TarStreamClient interface {
|
||||||
|
Send(*BytesMessage) error
|
||||||
|
Recv() (*BytesMessage, error)
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileSyncTarStreamClient struct {
|
||||||
|
grpc.ClientStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *fileSyncTarStreamClient) Send(m *BytesMessage) error {
|
||||||
|
return x.ClientStream.SendMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *fileSyncTarStreamClient) Recv() (*BytesMessage, error) {
|
||||||
|
m := new(BytesMessage)
|
||||||
|
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server API for FileSync service
|
||||||
|
|
||||||
|
type FileSyncServer interface {
|
||||||
|
DiffCopy(FileSync_DiffCopyServer) error
|
||||||
|
TarStream(FileSync_TarStreamServer) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterFileSyncServer(s *grpc.Server, srv FileSyncServer) {
|
||||||
|
s.RegisterService(&_FileSync_serviceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _FileSync_DiffCopy_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
return srv.(FileSyncServer).DiffCopy(&fileSyncDiffCopyServer{stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileSync_DiffCopyServer interface {
|
||||||
|
Send(*BytesMessage) error
|
||||||
|
Recv() (*BytesMessage, error)
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileSyncDiffCopyServer struct {
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *fileSyncDiffCopyServer) Send(m *BytesMessage) error {
|
||||||
|
return x.ServerStream.SendMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *fileSyncDiffCopyServer) Recv() (*BytesMessage, error) {
|
||||||
|
m := new(BytesMessage)
|
||||||
|
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func _FileSync_TarStream_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
return srv.(FileSyncServer).TarStream(&fileSyncTarStreamServer{stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileSync_TarStreamServer interface {
|
||||||
|
Send(*BytesMessage) error
|
||||||
|
Recv() (*BytesMessage, error)
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileSyncTarStreamServer struct {
|
||||||
|
grpc.ServerStream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *fileSyncTarStreamServer) Send(m *BytesMessage) error {
|
||||||
|
return x.ServerStream.SendMsg(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *fileSyncTarStreamServer) Recv() (*BytesMessage, error) {
|
||||||
|
m := new(BytesMessage)
|
||||||
|
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _FileSync_serviceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "moby.filesync.v1.FileSync",
|
||||||
|
HandlerType: (*FileSyncServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{},
|
||||||
|
Streams: []grpc.StreamDesc{
|
||||||
|
{
|
||||||
|
StreamName: "DiffCopy",
|
||||||
|
Handler: _FileSync_DiffCopy_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
ClientStreams: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
StreamName: "TarStream",
|
||||||
|
Handler: _FileSync_TarStream_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
ClientStreams: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Metadata: "filesync.proto",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BytesMessage) Marshal() (dAtA []byte, err error) {
|
||||||
|
size := m.Size()
|
||||||
|
dAtA = make([]byte, size)
|
||||||
|
n, err := m.MarshalTo(dAtA)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dAtA[:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BytesMessage) MarshalTo(dAtA []byte) (int, error) {
|
||||||
|
var i int
|
||||||
|
_ = i
|
||||||
|
var l int
|
||||||
|
_ = l
|
||||||
|
if len(m.Data) > 0 {
|
||||||
|
dAtA[i] = 0xa
|
||||||
|
i++
|
||||||
|
i = encodeVarintFilesync(dAtA, i, uint64(len(m.Data)))
|
||||||
|
i += copy(dAtA[i:], m.Data)
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeFixed64Filesync(dAtA []byte, offset int, v uint64) int {
|
||||||
|
dAtA[offset] = uint8(v)
|
||||||
|
dAtA[offset+1] = uint8(v >> 8)
|
||||||
|
dAtA[offset+2] = uint8(v >> 16)
|
||||||
|
dAtA[offset+3] = uint8(v >> 24)
|
||||||
|
dAtA[offset+4] = uint8(v >> 32)
|
||||||
|
dAtA[offset+5] = uint8(v >> 40)
|
||||||
|
dAtA[offset+6] = uint8(v >> 48)
|
||||||
|
dAtA[offset+7] = uint8(v >> 56)
|
||||||
|
return offset + 8
|
||||||
|
}
|
||||||
|
func encodeFixed32Filesync(dAtA []byte, offset int, v uint32) int {
|
||||||
|
dAtA[offset] = uint8(v)
|
||||||
|
dAtA[offset+1] = uint8(v >> 8)
|
||||||
|
dAtA[offset+2] = uint8(v >> 16)
|
||||||
|
dAtA[offset+3] = uint8(v >> 24)
|
||||||
|
return offset + 4
|
||||||
|
}
|
||||||
|
func encodeVarintFilesync(dAtA []byte, offset int, v uint64) int {
|
||||||
|
for v >= 1<<7 {
|
||||||
|
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||||
|
v >>= 7
|
||||||
|
offset++
|
||||||
|
}
|
||||||
|
dAtA[offset] = uint8(v)
|
||||||
|
return offset + 1
|
||||||
|
}
|
||||||
|
func (m *BytesMessage) Size() (n int) {
|
||||||
|
var l int
|
||||||
|
_ = l
|
||||||
|
l = len(m.Data)
|
||||||
|
if l > 0 {
|
||||||
|
n += 1 + l + sovFilesync(uint64(l))
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func sovFilesync(x uint64) (n int) {
|
||||||
|
for {
|
||||||
|
n++
|
||||||
|
x >>= 7
|
||||||
|
if x == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
func sozFilesync(x uint64) (n int) {
|
||||||
|
return sovFilesync(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||||
|
}
|
||||||
|
func (this *BytesMessage) String() string {
|
||||||
|
if this == nil {
|
||||||
|
return "nil"
|
||||||
|
}
|
||||||
|
s := strings.Join([]string{`&BytesMessage{`,
|
||||||
|
`Data:` + fmt.Sprintf("%v", this.Data) + `,`,
|
||||||
|
`}`,
|
||||||
|
}, "")
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
func valueToStringFilesync(v interface{}) string {
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if rv.IsNil() {
|
||||||
|
return "nil"
|
||||||
|
}
|
||||||
|
pv := reflect.Indirect(rv).Interface()
|
||||||
|
return fmt.Sprintf("*%v", pv)
|
||||||
|
}
|
||||||
|
func (m *BytesMessage) Unmarshal(dAtA []byte) error {
|
||||||
|
l := len(dAtA)
|
||||||
|
iNdEx := 0
|
||||||
|
for iNdEx < l {
|
||||||
|
preIndex := iNdEx
|
||||||
|
var wire uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowFilesync
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
wire |= (uint64(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fieldNum := int32(wire >> 3)
|
||||||
|
wireType := int(wire & 0x7)
|
||||||
|
if wireType == 4 {
|
||||||
|
return fmt.Errorf("proto: BytesMessage: wiretype end group for non-group")
|
||||||
|
}
|
||||||
|
if fieldNum <= 0 {
|
||||||
|
return fmt.Errorf("proto: BytesMessage: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||||
|
}
|
||||||
|
switch fieldNum {
|
||||||
|
case 1:
|
||||||
|
if wireType != 2 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType)
|
||||||
|
}
|
||||||
|
var byteLen int
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowFilesync
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
byteLen |= (int(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if byteLen < 0 {
|
||||||
|
return ErrInvalidLengthFilesync
|
||||||
|
}
|
||||||
|
postIndex := iNdEx + byteLen
|
||||||
|
if postIndex > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...)
|
||||||
|
if m.Data == nil {
|
||||||
|
m.Data = []byte{}
|
||||||
|
}
|
||||||
|
iNdEx = postIndex
|
||||||
|
default:
|
||||||
|
iNdEx = preIndex
|
||||||
|
skippy, err := skipFilesync(dAtA[iNdEx:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if skippy < 0 {
|
||||||
|
return ErrInvalidLengthFilesync
|
||||||
|
}
|
||||||
|
if (iNdEx + skippy) > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
iNdEx += skippy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if iNdEx > l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func skipFilesync(dAtA []byte) (n int, err error) {
|
||||||
|
l := len(dAtA)
|
||||||
|
iNdEx := 0
|
||||||
|
for iNdEx < l {
|
||||||
|
var wire uint64
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return 0, ErrIntOverflowFilesync
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
wire |= (uint64(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wireType := int(wire & 0x7)
|
||||||
|
switch wireType {
|
||||||
|
case 0:
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return 0, ErrIntOverflowFilesync
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
iNdEx++
|
||||||
|
if dAtA[iNdEx-1] < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return iNdEx, nil
|
||||||
|
case 1:
|
||||||
|
iNdEx += 8
|
||||||
|
return iNdEx, nil
|
||||||
|
case 2:
|
||||||
|
var length int
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return 0, ErrIntOverflowFilesync
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
length |= (int(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iNdEx += length
|
||||||
|
if length < 0 {
|
||||||
|
return 0, ErrInvalidLengthFilesync
|
||||||
|
}
|
||||||
|
return iNdEx, nil
|
||||||
|
case 3:
|
||||||
|
for {
|
||||||
|
var innerWire uint64
|
||||||
|
var start int = iNdEx
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return 0, ErrIntOverflowFilesync
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
innerWire |= (uint64(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
innerWireType := int(innerWire & 0x7)
|
||||||
|
if innerWireType == 4 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
next, err := skipFilesync(dAtA[start:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
iNdEx = start + next
|
||||||
|
}
|
||||||
|
return iNdEx, nil
|
||||||
|
case 4:
|
||||||
|
return iNdEx, nil
|
||||||
|
case 5:
|
||||||
|
iNdEx += 4
|
||||||
|
return iNdEx, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidLengthFilesync = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||||
|
ErrIntOverflowFilesync = fmt.Errorf("proto: integer overflow")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() { proto.RegisterFile("filesync.proto", fileDescriptorFilesync) }
|
||||||
|
|
||||||
|
var fileDescriptorFilesync = []byte{
|
||||||
|
// 198 bytes of a gzipped FileDescriptorProto
|
||||||
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x4b, 0xcb, 0xcc, 0x49,
|
||||||
|
0x2d, 0xae, 0xcc, 0x4b, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0xc8, 0xcd, 0x4f, 0xaa,
|
||||||
|
0xd4, 0x83, 0x0b, 0x96, 0x19, 0x2a, 0x29, 0x71, 0xf1, 0x38, 0x55, 0x96, 0xa4, 0x16, 0xfb, 0xa6,
|
||||||
|
0x16, 0x17, 0x27, 0xa6, 0xa7, 0x0a, 0x09, 0x71, 0xb1, 0xa4, 0x24, 0x96, 0x24, 0x4a, 0x30, 0x2a,
|
||||||
|
0x30, 0x6a, 0xf0, 0x04, 0x81, 0xd9, 0x46, 0xab, 0x19, 0xb9, 0x38, 0xdc, 0x32, 0x73, 0x52, 0x83,
|
||||||
|
0x2b, 0xf3, 0x92, 0x85, 0xfc, 0xb8, 0x38, 0x5c, 0x32, 0xd3, 0xd2, 0x9c, 0xf3, 0x0b, 0x2a, 0x85,
|
||||||
|
0xe4, 0xf4, 0xd0, 0xcd, 0xd3, 0x43, 0x36, 0x4c, 0x8a, 0x80, 0xbc, 0x06, 0xa3, 0x01, 0xa3, 0x90,
|
||||||
|
0x3f, 0x17, 0x67, 0x48, 0x62, 0x51, 0x70, 0x49, 0x51, 0x6a, 0x62, 0x2e, 0x35, 0x0c, 0x74, 0x32,
|
||||||
|
0xbb, 0xf0, 0x50, 0x8e, 0xe1, 0xc6, 0x43, 0x39, 0x86, 0x0f, 0x0f, 0xe5, 0x18, 0x1b, 0x1e, 0xc9,
|
||||||
|
0x31, 0xae, 0x78, 0x24, 0xc7, 0x78, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e,
|
||||||
|
0xc9, 0x31, 0xbe, 0x78, 0x24, 0xc7, 0xf0, 0xe1, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x51,
|
||||||
|
0x1c, 0x30, 0xb3, 0x92, 0xd8, 0xc0, 0x41, 0x64, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x5f, 0x0c,
|
||||||
|
0x8d, 0xc5, 0x34, 0x01, 0x00, 0x00,
|
||||||
|
}
|
15
vendor/github.com/docker/docker/client/session/filesync/filesync.proto
generated
vendored
Normal file
15
vendor/github.com/docker/docker/client/session/filesync/filesync.proto
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package moby.filesync.v1;
|
||||||
|
|
||||||
|
option go_package = "filesync";
|
||||||
|
|
||||||
|
service FileSync{
|
||||||
|
rpc DiffCopy(stream BytesMessage) returns (stream BytesMessage);
|
||||||
|
rpc TarStream(stream BytesMessage) returns (stream BytesMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesMessage contains a chunk of byte data
|
||||||
|
message BytesMessage{
|
||||||
|
bytes data = 1;
|
||||||
|
}
|
3
vendor/github.com/docker/docker/client/session/filesync/generate.go
generated
vendored
Normal file
3
vendor/github.com/docker/docker/client/session/filesync/generate.go
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
package filesync
|
||||||
|
|
||||||
|
//go:generate protoc --gogoslick_out=plugins=grpc:. filesync.proto
|
83
vendor/github.com/docker/docker/client/session/filesync/tarstream.go
generated
vendored
Normal file
83
vendor/github.com/docker/docker/client/session/filesync/tarstream.go
generated
vendored
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package filesync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
|
"github.com/docker/docker/pkg/chrootarchive"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sendTarStream(stream grpc.Stream, dir string, excludes []string, progress progressCb) error {
|
||||||
|
a, err := archive.TarWithOptions(dir, &archive.TarOptions{
|
||||||
|
ExcludePatterns: excludes,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
size := 0
|
||||||
|
buf := make([]byte, 1<<15)
|
||||||
|
t := new(BytesMessage)
|
||||||
|
for {
|
||||||
|
n, err := a.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.Data = buf[:n]
|
||||||
|
|
||||||
|
if err := stream.SendMsg(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
size += n
|
||||||
|
if progress != nil {
|
||||||
|
progress(size, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if progress != nil {
|
||||||
|
progress(size, true)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func recvTarStream(ds grpc.Stream, dest string, cs CacheUpdater) error {
|
||||||
|
|
||||||
|
pr, pw := io.Pipe()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
t = new(BytesMessage)
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
if err = ds.RecvMsg(t); err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, err = pw.Write(t.Data)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = pw.CloseWithError(err); err != nil {
|
||||||
|
logrus.Errorf("failed to close tar transfer pipe")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
decompressedStream, err := archive.DecompressStream(pr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to decompress stream")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := chrootarchive.Untar(decompressedStream, dest, nil); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to untar context")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/health/grpc_health_v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func serve(ctx context.Context, grpcServer *grpc.Server, conn net.Conn) {
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
conn.Close()
|
||||||
|
}()
|
||||||
|
logrus.Debugf("serving grpc connection")
|
||||||
|
(&http2.Server{}).ServeConn(conn, &http2.ServeConnOpts{Handler: grpcServer})
|
||||||
|
}
|
||||||
|
|
||||||
|
func grpcClientConn(ctx context.Context, conn net.Conn) (context.Context, *grpc.ClientConn, error) {
|
||||||
|
dialOpt := grpc.WithDialer(func(addr string, d time.Duration) (net.Conn, error) {
|
||||||
|
return conn, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
cc, err := grpc.DialContext(ctx, "", dialOpt, grpc.WithInsecure())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.Wrap(err, "failed to create grpc client")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
go monitorHealth(ctx, cc, cancel)
|
||||||
|
|
||||||
|
return ctx, cc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func monitorHealth(ctx context.Context, cc *grpc.ClientConn, cancelConn func()) {
|
||||||
|
defer cancelConn()
|
||||||
|
defer cc.Close()
|
||||||
|
|
||||||
|
ticker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
healthClient := grpc_health_v1.NewHealthClient(cc)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
<-ticker.C
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
_, err := healthClient.Check(ctx, &grpc_health_v1.HealthCheckRequest{})
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,187 @@
|
||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Caller can invoke requests on the session
|
||||||
|
type Caller interface {
|
||||||
|
Context() context.Context
|
||||||
|
Supports(method string) bool
|
||||||
|
Conn() *grpc.ClientConn
|
||||||
|
Name() string
|
||||||
|
SharedKey() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
Session
|
||||||
|
cc *grpc.ClientConn
|
||||||
|
supported map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manager is a controller for accessing currently active sessions
|
||||||
|
type Manager struct {
|
||||||
|
sessions map[string]*client
|
||||||
|
mu sync.Mutex
|
||||||
|
updateCondition *sync.Cond
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager returns a new Manager
|
||||||
|
func NewManager() (*Manager, error) {
|
||||||
|
sm := &Manager{
|
||||||
|
sessions: make(map[string]*client),
|
||||||
|
}
|
||||||
|
sm.updateCondition = sync.NewCond(&sm.mu)
|
||||||
|
return sm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleHTTPRequest handles an incoming HTTP request
|
||||||
|
func (sm *Manager) HandleHTTPRequest(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
hijacker, ok := w.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("handler does not support hijack")
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid := r.Header.Get(headerSessionUUID)
|
||||||
|
name := r.Header.Get(headerSessionName)
|
||||||
|
sharedKey := r.Header.Get(headerSessionSharedKey)
|
||||||
|
|
||||||
|
proto := r.Header.Get("Upgrade")
|
||||||
|
|
||||||
|
sm.mu.Lock()
|
||||||
|
if _, ok := sm.sessions[uuid]; ok {
|
||||||
|
sm.mu.Unlock()
|
||||||
|
return errors.Errorf("session %s already exists", uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if proto == "" {
|
||||||
|
sm.mu.Unlock()
|
||||||
|
return errors.New("no upgrade proto in request")
|
||||||
|
}
|
||||||
|
|
||||||
|
if proto != "h2c" {
|
||||||
|
sm.mu.Unlock()
|
||||||
|
return errors.Errorf("protocol %s not supported", proto)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, _, err := hijacker.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
sm.mu.Unlock()
|
||||||
|
return errors.Wrap(err, "failed to hijack connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &http.Response{
|
||||||
|
StatusCode: http.StatusSwitchingProtocols,
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
Header: http.Header{},
|
||||||
|
}
|
||||||
|
resp.Header.Set("Connection", "Upgrade")
|
||||||
|
resp.Header.Set("Upgrade", proto)
|
||||||
|
|
||||||
|
// set raw mode
|
||||||
|
conn.Write([]byte{})
|
||||||
|
resp.Write(conn)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ctx, cc, err := grpcClientConn(ctx, conn)
|
||||||
|
if err != nil {
|
||||||
|
sm.mu.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &client{
|
||||||
|
Session: Session{
|
||||||
|
uuid: uuid,
|
||||||
|
name: name,
|
||||||
|
sharedKey: sharedKey,
|
||||||
|
ctx: ctx,
|
||||||
|
cancelCtx: cancel,
|
||||||
|
done: make(chan struct{}),
|
||||||
|
},
|
||||||
|
cc: cc,
|
||||||
|
supported: make(map[string]struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range r.Header[headerSessionMethod] {
|
||||||
|
c.supported[strings.ToLower(m)] = struct{}{}
|
||||||
|
}
|
||||||
|
sm.sessions[uuid] = c
|
||||||
|
sm.updateCondition.Broadcast()
|
||||||
|
sm.mu.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
sm.mu.Lock()
|
||||||
|
delete(sm.sessions, uuid)
|
||||||
|
sm.mu.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-c.ctx.Done()
|
||||||
|
conn.Close()
|
||||||
|
close(c.done)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a session by UUID
|
||||||
|
func (sm *Manager) Get(ctx context.Context, uuid string) (Caller, error) {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
sm.updateCondition.Broadcast()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var c *client
|
||||||
|
|
||||||
|
sm.mu.Lock()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
sm.mu.Unlock()
|
||||||
|
return nil, errors.Wrapf(ctx.Err(), "no active session for %s", uuid)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
c, ok = sm.sessions[uuid]
|
||||||
|
if !ok || c.closed() {
|
||||||
|
sm.updateCondition.Wait()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sm.mu.Unlock()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Context() context.Context {
|
||||||
|
return c.context()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Name() string {
|
||||||
|
return c.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) SharedKey() string {
|
||||||
|
return c.sharedKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Supports(url string) bool {
|
||||||
|
_, ok := c.supported[strings.ToLower(url)]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
func (c *client) Conn() *grpc.ClientConn {
|
||||||
|
return c.cc
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/health"
|
||||||
|
"google.golang.org/grpc/health/grpc_health_v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
headerSessionUUID = "X-Docker-Expose-Session-Uuid"
|
||||||
|
headerSessionName = "X-Docker-Expose-Session-Name"
|
||||||
|
headerSessionSharedKey = "X-Docker-Expose-Session-Sharedkey"
|
||||||
|
headerSessionMethod = "X-Docker-Expose-Session-Grpc-Method"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dialer returns a connection that can be used by the session
|
||||||
|
type Dialer func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error)
|
||||||
|
|
||||||
|
// Attachable defines a feature that can be expsed on a session
|
||||||
|
type Attachable interface {
|
||||||
|
Register(*grpc.Server)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session is a long running connection between client and a daemon
|
||||||
|
type Session struct {
|
||||||
|
uuid string
|
||||||
|
name string
|
||||||
|
sharedKey string
|
||||||
|
ctx context.Context
|
||||||
|
cancelCtx func()
|
||||||
|
done chan struct{}
|
||||||
|
grpcServer *grpc.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSession returns a new long running session
|
||||||
|
func NewSession(name, sharedKey string) (*Session, error) {
|
||||||
|
uuid := stringid.GenerateRandomID()
|
||||||
|
s := &Session{
|
||||||
|
uuid: uuid,
|
||||||
|
name: name,
|
||||||
|
sharedKey: sharedKey,
|
||||||
|
grpcServer: grpc.NewServer(),
|
||||||
|
}
|
||||||
|
|
||||||
|
grpc_health_v1.RegisterHealthServer(s.grpcServer, health.NewServer())
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow enable a given service to be reachable through the grpc session
|
||||||
|
func (s *Session) Allow(a Attachable) {
|
||||||
|
a.Register(s.grpcServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UUID returns unique identifier for the session
|
||||||
|
func (s *Session) UUID() string {
|
||||||
|
return s.uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run activates the session
|
||||||
|
func (s *Session) Run(ctx context.Context, dialer Dialer) error {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
s.cancelCtx = cancel
|
||||||
|
s.done = make(chan struct{})
|
||||||
|
|
||||||
|
defer cancel()
|
||||||
|
defer close(s.done)
|
||||||
|
|
||||||
|
meta := make(map[string][]string)
|
||||||
|
meta[headerSessionUUID] = []string{s.uuid}
|
||||||
|
meta[headerSessionName] = []string{s.name}
|
||||||
|
meta[headerSessionSharedKey] = []string{s.sharedKey}
|
||||||
|
|
||||||
|
for name, svc := range s.grpcServer.GetServiceInfo() {
|
||||||
|
for _, method := range svc.Methods {
|
||||||
|
meta[headerSessionMethod] = append(meta[headerSessionMethod], MethodURL(name, method.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn, err := dialer(ctx, "h2c", meta)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to dial gRPC")
|
||||||
|
}
|
||||||
|
serve(ctx, s.grpcServer, conn)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the session
|
||||||
|
func (s *Session) Close() error {
|
||||||
|
if s.cancelCtx != nil && s.done != nil {
|
||||||
|
s.cancelCtx()
|
||||||
|
<-s.done
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) context() context.Context {
|
||||||
|
return s.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) closed() bool {
|
||||||
|
select {
|
||||||
|
case <-s.context().Done():
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MethodURL returns a gRPC method URL for service and method name
|
||||||
|
func MethodURL(s, m string) string {
|
||||||
|
return "/" + s + "/" + m
|
||||||
|
}
|
|
@ -55,3 +55,27 @@ func (o *UlimitOpt) GetList() []*units.Ulimit {
|
||||||
func (o *UlimitOpt) Type() string {
|
func (o *UlimitOpt) Type() string {
|
||||||
return "ulimit"
|
return "ulimit"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NamedUlimitOpt defines a named map of Ulimits
|
||||||
|
type NamedUlimitOpt struct {
|
||||||
|
name string
|
||||||
|
UlimitOpt
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ NamedOption = &NamedUlimitOpt{}
|
||||||
|
|
||||||
|
// NewNamedUlimitOpt creates a new NamedUlimitOpt
|
||||||
|
func NewNamedUlimitOpt(name string, ref *map[string]*units.Ulimit) *NamedUlimitOpt {
|
||||||
|
if ref == nil {
|
||||||
|
ref = &map[string]*units.Ulimit{}
|
||||||
|
}
|
||||||
|
return &NamedUlimitOpt{
|
||||||
|
name: name,
|
||||||
|
UlimitOpt: *NewUlimitOpt(ref),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the option name
|
||||||
|
func (o *NamedUlimitOpt) Name() string {
|
||||||
|
return o.name
|
||||||
|
}
|
||||||
|
|
|
@ -305,15 +305,7 @@ func (compression *Compression) Extension() string {
|
||||||
|
|
||||||
// FileInfoHeader creates a populated Header from fi.
|
// FileInfoHeader creates a populated Header from fi.
|
||||||
// Compared to archive pkg this function fills in more information.
|
// Compared to archive pkg this function fills in more information.
|
||||||
func FileInfoHeader(path, name string, fi os.FileInfo) (*tar.Header, error) {
|
func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, error) {
|
||||||
var link string
|
|
||||||
if fi.Mode()&os.ModeSymlink != 0 {
|
|
||||||
var err error
|
|
||||||
link, err = os.Readlink(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hdr, err := tar.FileInfoHeader(fi, link)
|
hdr, err := tar.FileInfoHeader(fi, link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -327,12 +319,18 @@ func FileInfoHeader(path, name string, fi os.FileInfo) (*tar.Header, error) {
|
||||||
if err := setHeaderForSpecialDevice(hdr, name, fi.Sys()); err != nil {
|
if err := setHeaderForSpecialDevice(hdr, name, fi.Sys()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return hdr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem
|
||||||
|
// to a tar header
|
||||||
|
func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error {
|
||||||
capability, _ := system.Lgetxattr(path, "security.capability")
|
capability, _ := system.Lgetxattr(path, "security.capability")
|
||||||
if capability != nil {
|
if capability != nil {
|
||||||
hdr.Xattrs = make(map[string]string)
|
hdr.Xattrs = make(map[string]string)
|
||||||
hdr.Xattrs["security.capability"] = string(capability)
|
hdr.Xattrs["security.capability"] = string(capability)
|
||||||
}
|
}
|
||||||
return hdr, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type tarWhiteoutConverter interface {
|
type tarWhiteoutConverter interface {
|
||||||
|
@ -386,10 +384,22 @@ func (ta *tarAppender) addTarFile(path, name string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hdr, err := FileInfoHeader(path, name, fi)
|
var link string
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
var err error
|
||||||
|
link, err = os.Readlink(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr, err := FileInfoHeader(name, fi, link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := ReadSecurityXattrToTarHeader(path, hdr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// if it's not a directory and has more than 1 link,
|
// if it's not a directory and has more than 1 link,
|
||||||
// it's hard linked, so set the type flag accordingly
|
// it's hard linked, so set the type flag accordingly
|
||||||
|
@ -1035,7 +1045,7 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
|
||||||
dst = filepath.Join(dst, filepath.Base(src))
|
dst = filepath.Join(dst, filepath.Base(src))
|
||||||
}
|
}
|
||||||
// Create the holding directory if necessary
|
// Create the holding directory if necessary
|
||||||
if err := system.MkdirAll(filepath.Dir(dst), 0700); err != nil {
|
if err := system.MkdirAll(filepath.Dir(dst), 0700, ""); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,16 +45,13 @@ func chmodTarEntry(perm os.FileMode) os.FileMode {
|
||||||
func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) (err error) {
|
func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) (err error) {
|
||||||
s, ok := stat.(*syscall.Stat_t)
|
s, ok := stat.(*syscall.Stat_t)
|
||||||
|
|
||||||
if !ok {
|
if ok {
|
||||||
err = errors.New("cannot convert stat value to syscall.Stat_t")
|
// Currently go does not fill in the major/minors
|
||||||
return
|
if s.Mode&syscall.S_IFBLK != 0 ||
|
||||||
}
|
s.Mode&syscall.S_IFCHR != 0 {
|
||||||
|
hdr.Devmajor = int64(major(uint64(s.Rdev)))
|
||||||
// Currently go does not fill in the major/minors
|
hdr.Devminor = int64(minor(uint64(s.Rdev)))
|
||||||
if s.Mode&syscall.S_IFBLK != 0 ||
|
}
|
||||||
s.Mode&syscall.S_IFCHR != 0 {
|
|
||||||
hdr.Devmajor = int64(major(uint64(s.Rdev)))
|
|
||||||
hdr.Devminor = int64(minor(uint64(s.Rdev)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -63,13 +60,10 @@ func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) (
|
||||||
func getInodeFromStat(stat interface{}) (inode uint64, err error) {
|
func getInodeFromStat(stat interface{}) (inode uint64, err error) {
|
||||||
s, ok := stat.(*syscall.Stat_t)
|
s, ok := stat.(*syscall.Stat_t)
|
||||||
|
|
||||||
if !ok {
|
if ok {
|
||||||
err = errors.New("cannot convert stat value to syscall.Stat_t")
|
inode = uint64(s.Ino)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inode = uint64(s.Ino)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64,
|
||||||
parentPath := filepath.Join(dest, parent)
|
parentPath := filepath.Join(dest, parent)
|
||||||
|
|
||||||
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
|
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
|
||||||
err = system.MkdirAll(parentPath, 0600)
|
err = system.MkdirAll(parentPath, 0600, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
package chrootarchive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
|
"github.com/docker/docker/pkg/idtools"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewArchiver returns a new Archiver which uses chrootarchive.Untar
|
||||||
|
func NewArchiver(idMappings *idtools.IDMappings) *archive.Archiver {
|
||||||
|
if idMappings == nil {
|
||||||
|
idMappings = &idtools.IDMappings{}
|
||||||
|
}
|
||||||
|
return &archive.Archiver{Untar: Untar, IDMappings: idMappings}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
|
||||||
|
// and unpacks it into the directory at `dest`.
|
||||||
|
// The archive may be compressed with one of the following algorithms:
|
||||||
|
// identity (uncompressed), gzip, bzip2, xz.
|
||||||
|
func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
|
||||||
|
return untarHandler(tarArchive, dest, options, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UntarUncompressed reads a stream of bytes from `archive`, parses it as a tar archive,
|
||||||
|
// and unpacks it into the directory at `dest`.
|
||||||
|
// The archive must be an uncompressed stream.
|
||||||
|
func UntarUncompressed(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
|
||||||
|
return untarHandler(tarArchive, dest, options, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler for teasing out the automatic decompression
|
||||||
|
func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions, decompress bool) error {
|
||||||
|
if tarArchive == nil {
|
||||||
|
return fmt.Errorf("Empty archive")
|
||||||
|
}
|
||||||
|
if options == nil {
|
||||||
|
options = &archive.TarOptions{}
|
||||||
|
}
|
||||||
|
if options.ExcludePatterns == nil {
|
||||||
|
options.ExcludePatterns = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
idMappings := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps)
|
||||||
|
rootIDs := idMappings.RootPair()
|
||||||
|
|
||||||
|
dest = filepath.Clean(dest)
|
||||||
|
if _, err := os.Stat(dest); os.IsNotExist(err) {
|
||||||
|
if err := idtools.MkdirAllAndChownNew(dest, 0755, rootIDs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r := ioutil.NopCloser(tarArchive)
|
||||||
|
if decompress {
|
||||||
|
decompressedArchive, err := archive.DecompressStream(tarArchive)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer decompressedArchive.Close()
|
||||||
|
r = decompressedArchive
|
||||||
|
}
|
||||||
|
|
||||||
|
return invokeUnpack(r, dest, options)
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package chrootarchive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
|
"github.com/docker/docker/pkg/reexec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// untar is the entry-point for docker-untar on re-exec. This is not used on
|
||||||
|
// Windows as it does not support chroot, hence no point sandboxing through
|
||||||
|
// chroot and rexec.
|
||||||
|
func untar() {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var options *archive.TarOptions
|
||||||
|
|
||||||
|
//read the options from the pipe "ExtraFiles"
|
||||||
|
if err := json.NewDecoder(os.NewFile(3, "options")).Decode(&options); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := chroot(flag.Arg(0)); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := archive.Unpack(os.Stdin, "/", options); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
// fully consume stdin in case it is zero padded
|
||||||
|
if _, err := flush(os.Stdin); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.TarOptions) error {
|
||||||
|
|
||||||
|
// We can't pass a potentially large exclude list directly via cmd line
|
||||||
|
// because we easily overrun the kernel's max argument/environment size
|
||||||
|
// when the full image list is passed (e.g. when this is used by
|
||||||
|
// `docker load`). We will marshall the options via a pipe to the
|
||||||
|
// child
|
||||||
|
r, w, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Untar pipe failure: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := reexec.Command("docker-untar", dest)
|
||||||
|
cmd.Stdin = decompressedArchive
|
||||||
|
|
||||||
|
cmd.ExtraFiles = append(cmd.ExtraFiles, r)
|
||||||
|
output := bytes.NewBuffer(nil)
|
||||||
|
cmd.Stdout = output
|
||||||
|
cmd.Stderr = output
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return fmt.Errorf("Untar error on re-exec cmd: %v", err)
|
||||||
|
}
|
||||||
|
//write the options to the pipe for the untar exec to read
|
||||||
|
if err := json.NewEncoder(w).Encode(options); err != nil {
|
||||||
|
return fmt.Errorf("Untar json encode to pipe failed: %v", err)
|
||||||
|
}
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
// when `xz -d -c -q | docker-untar ...` failed on docker-untar side,
|
||||||
|
// we need to exhaust `xz`'s output, otherwise the `xz` side will be
|
||||||
|
// pending on write pipe forever
|
||||||
|
io.Copy(ioutil.Discard, decompressedArchive)
|
||||||
|
|
||||||
|
return fmt.Errorf("Error processing tar file(%v): %s", err, output)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
22
vendor/github.com/docker/docker/pkg/chrootarchive/archive_windows.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker/pkg/chrootarchive/archive_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package chrootarchive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
|
"github.com/docker/docker/pkg/longpath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// chroot is not supported by Windows
|
||||||
|
func chroot(path string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeUnpack(decompressedArchive io.ReadCloser,
|
||||||
|
dest string,
|
||||||
|
options *archive.TarOptions) error {
|
||||||
|
// Windows is different to Linux here because Windows does not support
|
||||||
|
// chroot. Hence there is no point sandboxing a chrooted process to
|
||||||
|
// do the unpack. We call inline instead within the daemon process.
|
||||||
|
return archive.Unpack(decompressedArchive, longpath.AddPrefix(dest), options)
|
||||||
|
}
|
108
vendor/github.com/docker/docker/pkg/chrootarchive/chroot_linux.go
generated
vendored
Normal file
108
vendor/github.com/docker/docker/pkg/chrootarchive/chroot_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
package chrootarchive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/mount"
|
||||||
|
rsystem "github.com/opencontainers/runc/libcontainer/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
// chroot on linux uses pivot_root instead of chroot
|
||||||
|
// pivot_root takes a new root and an old root.
|
||||||
|
// Old root must be a sub-dir of new root, it is where the current rootfs will reside after the call to pivot_root.
|
||||||
|
// New root is where the new rootfs is set to.
|
||||||
|
// Old root is removed after the call to pivot_root so it is no longer available under the new root.
|
||||||
|
// This is similar to how libcontainer sets up a container's rootfs
|
||||||
|
func chroot(path string) (err error) {
|
||||||
|
// if the engine is running in a user namespace we need to use actual chroot
|
||||||
|
if rsystem.RunningInUserNS() {
|
||||||
|
return realChroot(path)
|
||||||
|
}
|
||||||
|
if err := syscall.Unshare(syscall.CLONE_NEWNS); err != nil {
|
||||||
|
return fmt.Errorf("Error creating mount namespace before pivot: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make everything in new ns private
|
||||||
|
if err := mount.MakeRPrivate("/"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if mounted, _ := mount.Mounted(path); !mounted {
|
||||||
|
if err := mount.Mount(path, path, "bind", "rbind,rw"); err != nil {
|
||||||
|
return realChroot(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup oldRoot for pivot_root
|
||||||
|
pivotDir, err := ioutil.TempDir(path, ".pivot_root")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error setting up pivot dir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mounted bool
|
||||||
|
defer func() {
|
||||||
|
if mounted {
|
||||||
|
// make sure pivotDir is not mounted before we try to remove it
|
||||||
|
if errCleanup := syscall.Unmount(pivotDir, syscall.MNT_DETACH); errCleanup != nil {
|
||||||
|
if err == nil {
|
||||||
|
err = errCleanup
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errCleanup := os.Remove(pivotDir)
|
||||||
|
// pivotDir doesn't exist if pivot_root failed and chroot+chdir was successful
|
||||||
|
// because we already cleaned it up on failed pivot_root
|
||||||
|
if errCleanup != nil && !os.IsNotExist(errCleanup) {
|
||||||
|
errCleanup = fmt.Errorf("Error cleaning up after pivot: %v", errCleanup)
|
||||||
|
if err == nil {
|
||||||
|
err = errCleanup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := syscall.PivotRoot(path, pivotDir); err != nil {
|
||||||
|
// If pivot fails, fall back to the normal chroot after cleaning up temp dir
|
||||||
|
if err := os.Remove(pivotDir); err != nil {
|
||||||
|
return fmt.Errorf("Error cleaning up after failed pivot: %v", err)
|
||||||
|
}
|
||||||
|
return realChroot(path)
|
||||||
|
}
|
||||||
|
mounted = true
|
||||||
|
|
||||||
|
// This is the new path for where the old root (prior to the pivot) has been moved to
|
||||||
|
// This dir contains the rootfs of the caller, which we need to remove so it is not visible during extraction
|
||||||
|
pivotDir = filepath.Join("/", filepath.Base(pivotDir))
|
||||||
|
|
||||||
|
if err := syscall.Chdir("/"); err != nil {
|
||||||
|
return fmt.Errorf("Error changing to new root: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the pivotDir (where the old root lives) private so it can be unmounted without propagating to the host
|
||||||
|
if err := syscall.Mount("", pivotDir, "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil {
|
||||||
|
return fmt.Errorf("Error making old root private after pivot: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now unmount the old root so it's no longer visible from the new root
|
||||||
|
if err := syscall.Unmount(pivotDir, syscall.MNT_DETACH); err != nil {
|
||||||
|
return fmt.Errorf("Error while unmounting old root after pivot: %v", err)
|
||||||
|
}
|
||||||
|
mounted = false
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func realChroot(path string) error {
|
||||||
|
if err := syscall.Chroot(path); err != nil {
|
||||||
|
return fmt.Errorf("Error after fallback to chroot: %v", err)
|
||||||
|
}
|
||||||
|
if err := syscall.Chdir("/"); err != nil {
|
||||||
|
return fmt.Errorf("Error changing to new root after chroot: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// +build !windows,!linux
|
||||||
|
|
||||||
|
package chrootarchive
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
func chroot(path string) error {
|
||||||
|
if err := syscall.Chroot(path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return syscall.Chdir("/")
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package chrootarchive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ApplyLayer parses a diff in the standard layer format from `layer`,
|
||||||
|
// and applies it to the directory `dest`. The stream `layer` can only be
|
||||||
|
// uncompressed.
|
||||||
|
// Returns the size in bytes of the contents of the layer.
|
||||||
|
func ApplyLayer(dest string, layer io.Reader) (size int64, err error) {
|
||||||
|
return applyLayerHandler(dest, layer, &archive.TarOptions{}, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyUncompressedLayer parses a diff in the standard layer format from
|
||||||
|
// `layer`, and applies it to the directory `dest`. The stream `layer`
|
||||||
|
// can only be uncompressed.
|
||||||
|
// Returns the size in bytes of the contents of the layer.
|
||||||
|
func ApplyUncompressedLayer(dest string, layer io.Reader, options *archive.TarOptions) (int64, error) {
|
||||||
|
return applyLayerHandler(dest, layer, options, false)
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
//+build !windows
|
||||||
|
|
||||||
|
package chrootarchive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
|
"github.com/docker/docker/pkg/reexec"
|
||||||
|
"github.com/docker/docker/pkg/system"
|
||||||
|
rsystem "github.com/opencontainers/runc/libcontainer/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
type applyLayerResponse struct {
|
||||||
|
LayerSize int64 `json:"layerSize"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyLayer is the entry-point for docker-applylayer on re-exec. This is not
|
||||||
|
// used on Windows as it does not support chroot, hence no point sandboxing
|
||||||
|
// through chroot and rexec.
|
||||||
|
func applyLayer() {
|
||||||
|
|
||||||
|
var (
|
||||||
|
tmpDir string
|
||||||
|
err error
|
||||||
|
options *archive.TarOptions
|
||||||
|
)
|
||||||
|
runtime.LockOSThread()
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
inUserns := rsystem.RunningInUserNS()
|
||||||
|
if err := chroot(flag.Arg(0)); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to be able to set any perms
|
||||||
|
oldmask, err := system.Umask(0)
|
||||||
|
defer system.Umask(oldmask)
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(os.Getenv("OPT")), &options); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if inUserns {
|
||||||
|
options.InUserNS = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpDir, err = ioutil.TempDir("/", "temp-docker-extract"); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Setenv("TMPDIR", tmpDir)
|
||||||
|
size, err := archive.UnpackLayer("/", os.Stdin, options)
|
||||||
|
os.RemoveAll(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(os.Stdout)
|
||||||
|
if err := encoder.Encode(applyLayerResponse{size}); err != nil {
|
||||||
|
fatal(fmt.Errorf("unable to encode layerSize JSON: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := flush(os.Stdin); err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyLayerHandler parses a diff in the standard layer format from `layer`, and
|
||||||
|
// applies it to the directory `dest`. Returns the size in bytes of the
|
||||||
|
// contents of the layer.
|
||||||
|
func applyLayerHandler(dest string, layer io.Reader, options *archive.TarOptions, decompress bool) (size int64, err error) {
|
||||||
|
dest = filepath.Clean(dest)
|
||||||
|
if decompress {
|
||||||
|
decompressed, err := archive.DecompressStream(layer)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer decompressed.Close()
|
||||||
|
|
||||||
|
layer = decompressed
|
||||||
|
}
|
||||||
|
if options == nil {
|
||||||
|
options = &archive.TarOptions{}
|
||||||
|
if rsystem.RunningInUserNS() {
|
||||||
|
options.InUserNS = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.ExcludePatterns == nil {
|
||||||
|
options.ExcludePatterns = []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(options)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("ApplyLayer json encode: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := reexec.Command("docker-applyLayer", dest)
|
||||||
|
cmd.Stdin = layer
|
||||||
|
cmd.Env = append(cmd.Env, fmt.Sprintf("OPT=%s", data))
|
||||||
|
|
||||||
|
outBuf, errBuf := new(bytes.Buffer), new(bytes.Buffer)
|
||||||
|
cmd.Stdout, cmd.Stderr = outBuf, errBuf
|
||||||
|
|
||||||
|
if err = cmd.Run(); err != nil {
|
||||||
|
return 0, fmt.Errorf("ApplyLayer %s stdout: %s stderr: %s", err, outBuf, errBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stdout should be a valid JSON struct representing an applyLayerResponse.
|
||||||
|
response := applyLayerResponse{}
|
||||||
|
decoder := json.NewDecoder(outBuf)
|
||||||
|
if err = decoder.Decode(&response); err != nil {
|
||||||
|
return 0, fmt.Errorf("unable to decode ApplyLayer JSON response: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.LayerSize, nil
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package chrootarchive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
|
"github.com/docker/docker/pkg/longpath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// applyLayerHandler parses a diff in the standard layer format from `layer`, and
|
||||||
|
// applies it to the directory `dest`. Returns the size in bytes of the
|
||||||
|
// contents of the layer.
|
||||||
|
func applyLayerHandler(dest string, layer io.Reader, options *archive.TarOptions, decompress bool) (size int64, err error) {
|
||||||
|
dest = filepath.Clean(dest)
|
||||||
|
|
||||||
|
// Ensure it is a Windows-style volume path
|
||||||
|
dest = longpath.AddPrefix(dest)
|
||||||
|
|
||||||
|
if decompress {
|
||||||
|
decompressed, err := archive.DecompressStream(layer)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer decompressed.Close()
|
||||||
|
|
||||||
|
layer = decompressed
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpDir, err := ioutil.TempDir(os.Getenv("temp"), "temp-docker-extract")
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("ApplyLayer failed to create temp-docker-extract under %s. %s", dest, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := archive.UnpackLayer(dest, layer, nil)
|
||||||
|
os.RemoveAll(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("ApplyLayer %s failed UnpackLayer to %s: %s", layer, dest, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package chrootarchive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/reexec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
reexec.Register("docker-applyLayer", applyLayer)
|
||||||
|
reexec.Register("docker-untar", untar)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatal(err error) {
|
||||||
|
fmt.Fprint(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// flush consumes all the bytes from the reader discarding
|
||||||
|
// any errors
|
||||||
|
func flush(r io.Reader) (bytes int64, err error) {
|
||||||
|
return io.Copy(ioutil.Discard, r)
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
package chrootarchive
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
}
|
|
@ -49,7 +49,7 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown
|
||||||
paths = append(paths, dirPath)
|
paths = append(paths, dirPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := system.MkdirAll(path, mode); err != nil && !os.IsExist(err) {
|
if err := system.MkdirAll(path, mode, ""); err != nil && !os.IsExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
// Platforms such as Windows do not support the UID/GID concept. So make this
|
// Platforms such as Windows do not support the UID/GID concept. So make this
|
||||||
// just a wrapper around system.MkdirAll.
|
// just a wrapper around system.MkdirAll.
|
||||||
func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error {
|
func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error {
|
||||||
if err := system.MkdirAll(path, mode); err != nil && !os.IsExist(err) {
|
if err := system.MkdirAll(path, mode, ""); err != nil && !os.IsExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
package random
|
|
||||||
|
|
||||||
import (
|
|
||||||
cryptorand "crypto/rand"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
"math/rand"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Rand is a global *rand.Rand instance, which initialized with NewSource() source.
|
|
||||||
var Rand = rand.New(NewSource())
|
|
||||||
|
|
||||||
// Reader is a global, shared instance of a pseudorandom bytes generator.
|
|
||||||
// It doesn't consume entropy.
|
|
||||||
var Reader io.Reader = &reader{rnd: Rand}
|
|
||||||
|
|
||||||
// copypaste from standard math/rand
|
|
||||||
type lockedSource struct {
|
|
||||||
lk sync.Mutex
|
|
||||||
src rand.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *lockedSource) Int63() (n int64) {
|
|
||||||
r.lk.Lock()
|
|
||||||
n = r.src.Int63()
|
|
||||||
r.lk.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *lockedSource) Seed(seed int64) {
|
|
||||||
r.lk.Lock()
|
|
||||||
r.src.Seed(seed)
|
|
||||||
r.lk.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSource returns math/rand.Source safe for concurrent use and initialized
|
|
||||||
// with current unix-nano timestamp
|
|
||||||
func NewSource() rand.Source {
|
|
||||||
var seed int64
|
|
||||||
if cryptoseed, err := cryptorand.Int(cryptorand.Reader, big.NewInt(math.MaxInt64)); err != nil {
|
|
||||||
// This should not happen, but worst-case fallback to time-based seed.
|
|
||||||
seed = time.Now().UnixNano()
|
|
||||||
} else {
|
|
||||||
seed = cryptoseed.Int64()
|
|
||||||
}
|
|
||||||
return &lockedSource{
|
|
||||||
src: rand.NewSource(seed),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type reader struct {
|
|
||||||
rnd *rand.Rand
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *reader) Read(b []byte) (int, error) {
|
|
||||||
i := 0
|
|
||||||
for {
|
|
||||||
val := r.rnd.Int63()
|
|
||||||
for val > 0 {
|
|
||||||
b[i] = byte(val)
|
|
||||||
i++
|
|
||||||
if i == len(b) {
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
val >>= 8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# reexec
|
||||||
|
|
||||||
|
The `reexec` package facilitates the busybox style reexec of the docker binary that we require because
|
||||||
|
of the forking limitations of using Go. Handlers can be registered with a name and the argv 0 of
|
||||||
|
the exec of the binary will be used to find and execute custom init paths.
|
|
@ -0,0 +1,28 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package reexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Self returns the path to the current process's binary.
|
||||||
|
// Returns "/proc/self/exe".
|
||||||
|
func Self() string {
|
||||||
|
return "/proc/self/exe"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns *exec.Cmd which has Path as current binary. Also it setting
|
||||||
|
// SysProcAttr.Pdeathsig to SIGTERM.
|
||||||
|
// This will use the in-memory version (/proc/self/exe) of the current binary,
|
||||||
|
// it is thus safe to delete or replace the on-disk binary (os.Args[0]).
|
||||||
|
func Command(args ...string) *exec.Cmd {
|
||||||
|
return &exec.Cmd{
|
||||||
|
Path: Self(),
|
||||||
|
Args: args,
|
||||||
|
SysProcAttr: &syscall.SysProcAttr{
|
||||||
|
Pdeathsig: syscall.SIGTERM,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// +build freebsd solaris darwin
|
||||||
|
|
||||||
|
package reexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Self returns the path to the current process's binary.
|
||||||
|
// Uses os.Args[0].
|
||||||
|
func Self() string {
|
||||||
|
return naiveSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns *exec.Cmd which has Path as current binary.
|
||||||
|
// For example if current binary is "docker" at "/usr/bin/", then cmd.Path will
|
||||||
|
// be set to "/usr/bin/docker".
|
||||||
|
func Command(args ...string) *exec.Cmd {
|
||||||
|
return &exec.Cmd{
|
||||||
|
Path: Self(),
|
||||||
|
Args: args,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// +build !linux,!windows,!freebsd,!solaris,!darwin
|
||||||
|
|
||||||
|
package reexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Command is unsupported on operating systems apart from Linux, Windows, Solaris and Darwin.
|
||||||
|
func Command(args ...string) *exec.Cmd {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package reexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Self returns the path to the current process's binary.
|
||||||
|
// Uses os.Args[0].
|
||||||
|
func Self() string {
|
||||||
|
return naiveSelf()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns *exec.Cmd which has Path as current binary.
|
||||||
|
// For example if current binary is "docker.exe" at "C:\", then cmd.Path will
|
||||||
|
// be set to "C:\docker.exe".
|
||||||
|
func Command(args ...string) *exec.Cmd {
|
||||||
|
return &exec.Cmd{
|
||||||
|
Path: Self(),
|
||||||
|
Args: args,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package reexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var registeredInitializers = make(map[string]func())
|
||||||
|
|
||||||
|
// Register adds an initialization func under the specified name
|
||||||
|
func Register(name string, initializer func()) {
|
||||||
|
if _, exists := registeredInitializers[name]; exists {
|
||||||
|
panic(fmt.Sprintf("reexec func already registered under name %q", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
registeredInitializers[name] = initializer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init is called as the first part of the exec process and returns true if an
|
||||||
|
// initialization function was called.
|
||||||
|
func Init() bool {
|
||||||
|
initializer, exists := registeredInitializers[os.Args[0]]
|
||||||
|
if exists {
|
||||||
|
initializer()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func naiveSelf() string {
|
||||||
|
name := os.Args[0]
|
||||||
|
if filepath.Base(name) == name {
|
||||||
|
if lp, err := exec.LookPath(name); err == nil {
|
||||||
|
return lp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// handle conversion of relative paths to absolute
|
||||||
|
if absName, err := filepath.Abs(name); err == nil {
|
||||||
|
return absName
|
||||||
|
}
|
||||||
|
// if we couldn't get absolute name, return original
|
||||||
|
// (NOTE: Go only errors on Abs() if os.Getwd fails)
|
||||||
|
return name
|
||||||
|
}
|
|
@ -2,15 +2,17 @@
|
||||||
package stringid
|
package stringid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
cryptorand "crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"math/rand"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"github.com/docker/docker/pkg/random"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const shortLen = 12
|
const shortLen = 12
|
||||||
|
@ -39,12 +41,8 @@ func TruncateID(id string) string {
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateID(crypto bool) string {
|
func generateID(r io.Reader) string {
|
||||||
b := make([]byte, 32)
|
b := make([]byte, 32)
|
||||||
r := random.Reader
|
|
||||||
if crypto {
|
|
||||||
r = rand.Reader
|
|
||||||
}
|
|
||||||
for {
|
for {
|
||||||
if _, err := io.ReadFull(r, b); err != nil {
|
if _, err := io.ReadFull(r, b); err != nil {
|
||||||
panic(err) // This shouldn't happen
|
panic(err) // This shouldn't happen
|
||||||
|
@ -62,14 +60,14 @@ func generateID(crypto bool) string {
|
||||||
|
|
||||||
// GenerateRandomID returns a unique id.
|
// GenerateRandomID returns a unique id.
|
||||||
func GenerateRandomID() string {
|
func GenerateRandomID() string {
|
||||||
return generateID(true)
|
return generateID(cryptorand.Reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateNonCryptoID generates unique id without using cryptographically
|
// GenerateNonCryptoID generates unique id without using cryptographically
|
||||||
// secure sources of random.
|
// secure sources of random.
|
||||||
// It helps you to save entropy.
|
// It helps you to save entropy.
|
||||||
func GenerateNonCryptoID() string {
|
func GenerateNonCryptoID() string {
|
||||||
return generateID(false)
|
return generateID(readerFunc(rand.Read))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateID checks whether an ID string is a valid image ID.
|
// ValidateID checks whether an ID string is a valid image ID.
|
||||||
|
@ -79,3 +77,23 @@ func ValidateID(id string) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// safely set the seed globally so we generate random ids. Tries to use a
|
||||||
|
// crypto seed before falling back to time.
|
||||||
|
var seed int64
|
||||||
|
if cryptoseed, err := cryptorand.Int(cryptorand.Reader, big.NewInt(math.MaxInt64)); err != nil {
|
||||||
|
// This should not happen, but worst-case fallback to time-based seed.
|
||||||
|
seed = time.Now().UnixNano()
|
||||||
|
} else {
|
||||||
|
seed = cryptoseed.Int64()
|
||||||
|
}
|
||||||
|
|
||||||
|
rand.Seed(seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
type readerFunc func(p []byte) (int, error)
|
||||||
|
|
||||||
|
func (fn readerFunc) Read(p []byte) (int, error) {
|
||||||
|
return fn(p)
|
||||||
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/random"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenerateRandomAlphaOnlyString generates an alphabetical random string with length n.
|
// GenerateRandomAlphaOnlyString generates an alphabetical random string with length n.
|
||||||
|
@ -15,7 +13,7 @@ func GenerateRandomAlphaOnlyString(n int) string {
|
||||||
letters := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
letters := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||||
b := make([]byte, n)
|
b := make([]byte, n)
|
||||||
for i := range b {
|
for i := range b {
|
||||||
b[i] = letters[random.Rand.Intn(len(letters))]
|
b[i] = letters[rand.Intn(len(letters))]
|
||||||
}
|
}
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,26 +2,9 @@ package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
maxTime time.Time
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chtimes changes the access time and modified time of a file at the given path
|
// Chtimes changes the access time and modified time of a file at the given path
|
||||||
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)
|
unixMinTime := time.Unix(0, 0)
|
||||||
|
|
|
@ -8,15 +8,14 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MkdirAllWithACL is a wrapper for MkdirAll that creates a directory
|
// MkdirAllWithACL is a wrapper for MkdirAll on unix systems.
|
||||||
// ACL'd for Builtin Administrators and Local System.
|
func MkdirAllWithACL(path string, perm os.FileMode, sddl string) error {
|
||||||
func MkdirAllWithACL(path string, perm os.FileMode) error {
|
return MkdirAll(path, perm, sddl)
|
||||||
return MkdirAll(path, perm)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MkdirAll creates a directory named path along with any necessary parents,
|
// MkdirAll creates a directory named path along with any necessary parents,
|
||||||
// with permission specified by attribute perm for all dir created.
|
// with permission specified by attribute perm for all dir created.
|
||||||
func MkdirAll(path string, perm os.FileMode) error {
|
func MkdirAll(path string, perm os.FileMode, sddl string) error {
|
||||||
return os.MkdirAll(path, perm)
|
return os.MkdirAll(path, perm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,21 +16,28 @@ import (
|
||||||
winio "github.com/Microsoft/go-winio"
|
winio "github.com/Microsoft/go-winio"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SddlAdministratorsLocalSystem is local administrators plus NT AUTHORITY\System
|
||||||
|
SddlAdministratorsLocalSystem = "D:P(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)"
|
||||||
|
// SddlNtvmAdministratorsLocalSystem is NT VIRTUAL MACHINE\Virtual Machines plus local administrators plus NT AUTHORITY\System
|
||||||
|
SddlNtvmAdministratorsLocalSystem = "D:P(A;OICI;GA;;;S-1-5-83-0)(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)"
|
||||||
|
)
|
||||||
|
|
||||||
// MkdirAllWithACL is a wrapper for MkdirAll that creates a directory
|
// MkdirAllWithACL is a wrapper for MkdirAll that creates a directory
|
||||||
// ACL'd for Builtin Administrators and Local System.
|
// with an appropriate SDDL defined ACL.
|
||||||
func MkdirAllWithACL(path string, perm os.FileMode) error {
|
func MkdirAllWithACL(path string, perm os.FileMode, sddl string) error {
|
||||||
return mkdirall(path, true)
|
return mkdirall(path, true, sddl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MkdirAll implementation that is volume path aware for Windows.
|
// MkdirAll implementation that is volume path aware for Windows.
|
||||||
func MkdirAll(path string, _ os.FileMode) error {
|
func MkdirAll(path string, _ os.FileMode, sddl string) error {
|
||||||
return mkdirall(path, false)
|
return mkdirall(path, false, sddl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, adminAndLocalSystem bool) error {
|
func mkdirall(path string, applyACL bool, sddl string) error {
|
||||||
if re := regexp.MustCompile(`^\\\\\?\\Volume{[a-z0-9-]+}$`); re.MatchString(path) {
|
if re := regexp.MustCompile(`^\\\\\?\\Volume{[a-z0-9-]+}$`); re.MatchString(path) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -64,15 +71,15 @@ func mkdirall(path string, adminAndLocalSystem bool) error {
|
||||||
|
|
||||||
if j > 1 {
|
if j > 1 {
|
||||||
// Create parent
|
// Create parent
|
||||||
err = mkdirall(path[0:j-1], false)
|
err = mkdirall(path[0:j-1], false, sddl)
|
||||||
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 os.Mkdir or mkdirWithACL and use its result.
|
||||||
if adminAndLocalSystem {
|
if applyACL {
|
||||||
err = mkdirWithACL(path)
|
err = mkdirWithACL(path, sddl)
|
||||||
} else {
|
} else {
|
||||||
err = os.Mkdir(path, 0)
|
err = os.Mkdir(path, 0)
|
||||||
}
|
}
|
||||||
|
@ -96,9 +103,9 @@ func mkdirall(path string, adminAndLocalSystem bool) 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) error {
|
func mkdirWithACL(name string, sddl string) error {
|
||||||
sa := syscall.SecurityAttributes{Length: 0}
|
sa := syscall.SecurityAttributes{Length: 0}
|
||||||
sddl := "D:P(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)"
|
|
||||||
sd, err := winio.SddlToSecurityDescriptor(sddl)
|
sd, err := winio.SddlToSecurityDescriptor(sddl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &os.PathError{Op: "mkdir", Path: name, Err: err}
|
return &os.PathError{Op: "mkdir", Path: name, Err: err}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// LCOWSupported determines if Linux Containers on Windows are supported.
|
||||||
|
// Note: This feature is in development (06/17) and enabled through an
|
||||||
|
// environment variable. At a future time, it will be enabled based
|
||||||
|
// on build number. @jhowardmsft
|
||||||
|
var lcowSupported = false
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// LCOW initialization
|
||||||
|
if os.Getenv("LCOW_SUPPORTED") != "" {
|
||||||
|
lcowSupported = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
// LCOWSupported returns true if Linux containers on Windows are supported.
|
||||||
|
func LCOWSupported() bool {
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
// LCOWSupported returns true if Linux containers on Windows are supported.
|
||||||
|
func LCOWSupported() bool {
|
||||||
|
return lcowSupported
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import "runtime"
|
||||||
|
|
||||||
|
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 .
|
||||||
|
func DefaultPathEnv(platform string) string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
if platform != runtime.GOOS && LCOWSupported() {
|
||||||
|
return defaultUnixPathEnv
|
||||||
|
}
|
||||||
|
// Deliberately empty on Windows containers on Windows as the default path will be set by
|
||||||
|
// the container. Docker has no context of what the default path should be.
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return defaultUnixPathEnv
|
||||||
|
|
||||||
|
}
|
|
@ -2,11 +2,6 @@
|
||||||
|
|
||||||
package system
|
package system
|
||||||
|
|
||||||
// DefaultPathEnv is unix style list of directories to search for
|
|
||||||
// executables. Each directory is separated from the next by a colon
|
|
||||||
// ':' character .
|
|
||||||
const DefaultPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
||||||
|
|
||||||
// CheckSystemDriveAndRemoveDriveLetter verifies that a path, if it includes a drive letter,
|
// CheckSystemDriveAndRemoveDriveLetter verifies that a path, if it includes a drive letter,
|
||||||
// is the system drive. This is a no-op on Linux.
|
// is the system drive. This is a no-op on Linux.
|
||||||
func CheckSystemDriveAndRemoveDriveLetter(path string) (string, error) {
|
func CheckSystemDriveAndRemoveDriveLetter(path string) (string, error) {
|
||||||
|
|
|
@ -8,10 +8,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultPathEnv is deliberately empty on Windows as the default path will be set by
|
|
||||||
// the container. Docker has no context of what the default path should be.
|
|
||||||
const DefaultPathEnv = ""
|
|
||||||
|
|
||||||
// CheckSystemDriveAndRemoveDriveLetter verifies and manipulates a Windows path.
|
// CheckSystemDriveAndRemoveDriveLetter verifies and manipulates a Windows path.
|
||||||
// This is used, for example, when validating a user provided path in docker cp.
|
// 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
|
// If a drive letter is supplied, it must be the system drive. The drive letter
|
||||||
|
|
|
@ -34,3 +34,23 @@ func (f *TempFile) Name() string {
|
||||||
func (f *TempFile) Remove() {
|
func (f *TempFile) Remove() {
|
||||||
os.Remove(f.Name())
|
os.Remove(f.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TempDir is a temporary directory that can be used with unit tests. TempDir
|
||||||
|
// reduces the boilerplate setup required in each test case by handling
|
||||||
|
// setup errors.
|
||||||
|
type TempDir struct {
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTempDir returns a new temp file with contents
|
||||||
|
func NewTempDir(t require.TestingT, prefix string) *TempDir {
|
||||||
|
path, err := ioutil.TempDir("", prefix+"-")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return &TempDir{Path: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes the file
|
||||||
|
func (f *TempDir) Remove() {
|
||||||
|
os.Remove(f.Path)
|
||||||
|
}
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
// +build go1.6,!go1.7
|
|
||||||
|
|
||||||
package tlsconfig
|
|
||||||
|
|
||||||
import "crypto/tls"
|
|
||||||
|
|
||||||
// Clone returns a clone of tls.Config. This function is provided for
|
|
||||||
// compatibility for go1.6 that doesn't include this method in stdlib.
|
|
||||||
func Clone(c *tls.Config) *tls.Config {
|
|
||||||
return &tls.Config{
|
|
||||||
Rand: c.Rand,
|
|
||||||
Time: c.Time,
|
|
||||||
Certificates: c.Certificates,
|
|
||||||
NameToCertificate: c.NameToCertificate,
|
|
||||||
GetCertificate: c.GetCertificate,
|
|
||||||
RootCAs: c.RootCAs,
|
|
||||||
NextProtos: c.NextProtos,
|
|
||||||
ServerName: c.ServerName,
|
|
||||||
ClientAuth: c.ClientAuth,
|
|
||||||
ClientCAs: c.ClientCAs,
|
|
||||||
InsecureSkipVerify: c.InsecureSkipVerify,
|
|
||||||
CipherSuites: c.CipherSuites,
|
|
||||||
PreferServerCipherSuites: c.PreferServerCipherSuites,
|
|
||||||
SessionTicketsDisabled: c.SessionTicketsDisabled,
|
|
||||||
SessionTicketKey: c.SessionTicketKey,
|
|
||||||
ClientSessionCache: c.ClientSessionCache,
|
|
||||||
MinVersion: c.MinVersion,
|
|
||||||
MaxVersion: c.MaxVersion,
|
|
||||||
CurvePreferences: c.CurvePreferences,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
# the following lines are in sorted order, FYI
|
# the following lines are in sorted order, FYI
|
||||||
github.com/Azure/go-ansiterm 388960b655244e76e24c75f48631564eaefade62
|
github.com/Azure/go-ansiterm 388960b655244e76e24c75f48631564eaefade62
|
||||||
github.com/Microsoft/hcsshim v0.5.17
|
github.com/Microsoft/hcsshim v0.5.23
|
||||||
github.com/Microsoft/go-winio v0.4.2
|
github.com/Microsoft/go-winio v0.4.2
|
||||||
github.com/Sirupsen/logrus v0.11.0
|
github.com/Sirupsen/logrus v0.11.0
|
||||||
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
|
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
|
||||||
|
@ -8,6 +8,7 @@ github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||||
github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git
|
github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git
|
||||||
github.com/gorilla/context v1.1
|
github.com/gorilla/context v1.1
|
||||||
github.com/gorilla/mux v1.1
|
github.com/gorilla/mux v1.1
|
||||||
|
github.com/jhowardmsft/opengcs v0.0.3
|
||||||
github.com/kr/pty 5cf931ef8f
|
github.com/kr/pty 5cf931ef8f
|
||||||
github.com/mattn/go-shellwords v1.0.3
|
github.com/mattn/go-shellwords v1.0.3
|
||||||
github.com/tchap/go-patricia v2.2.6
|
github.com/tchap/go-patricia v2.2.6
|
||||||
|
@ -58,7 +59,6 @@ github.com/mistifyio/go-zfs 22c9b32c84eb0d0c6f4043b6e90fc94073de92fa
|
||||||
github.com/pborman/uuid v1.0
|
github.com/pborman/uuid v1.0
|
||||||
|
|
||||||
google.golang.org/grpc v1.3.0
|
google.golang.org/grpc v1.3.0
|
||||||
github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f
|
|
||||||
|
|
||||||
# When updating, also update RUNC_COMMIT in hack/dockerfile/binaries-commits accordingly
|
# When updating, also update RUNC_COMMIT in hack/dockerfile/binaries-commits accordingly
|
||||||
github.com/opencontainers/runc 2d41c047c83e09a6d61d464906feb2a2f3c52aa4 https://github.com/docker/runc
|
github.com/opencontainers/runc 2d41c047c83e09a6d61d464906feb2a2f3c52aa4 https://github.com/docker/runc
|
||||||
|
@ -102,6 +102,8 @@ google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
|
||||||
# containerd
|
# containerd
|
||||||
github.com/containerd/containerd 3addd840653146c90a254301d6c3a663c7fd6429
|
github.com/containerd/containerd 3addd840653146c90a254301d6c3a663c7fd6429
|
||||||
github.com/tonistiigi/fifo 1405643975692217d6720f8b54aeee1bf2cd5cf4
|
github.com/tonistiigi/fifo 1405643975692217d6720f8b54aeee1bf2cd5cf4
|
||||||
|
github.com/stevvooe/continuity cd7a8e21e2b6f84799f5dd4b65faf49c8d3ee02d
|
||||||
|
github.com/tonistiigi/fsutil 0ac4c11b053b9c5c7c47558f81f96c7100ce50fb
|
||||||
|
|
||||||
# cluster
|
# cluster
|
||||||
github.com/docker/swarmkit a4bf0135f63fb60f0e76ae81579cde87f580db6e
|
github.com/docker/swarmkit a4bf0135f63fb60f0e76ae81579cde87f580db6e
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# continuity
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/containerd/continuity?status.svg)](https://godoc.org/github.com/containerd/continuity)
|
||||||
|
[![Build Status](https://travis-ci.org/containerd/continuity.svg?branch=master)](https://travis-ci.org/containerd/continuity)
|
||||||
|
|
||||||
|
A transport-agnostic, filesystem metadata manifest system
|
||||||
|
|
||||||
|
This project is a staging area for experiments in providing transport agnostic
|
||||||
|
metadata storage.
|
||||||
|
|
||||||
|
Please see https://github.com/opencontainers/specs/issues/11 for more details.
|
||||||
|
|
||||||
|
## Building Proto Package
|
||||||
|
|
||||||
|
If you change the proto file you will need to rebuild the generated Go with `go generate`.
|
||||||
|
|
||||||
|
```
|
||||||
|
go generate ./proto
|
||||||
|
```
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !gccgo
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·use(SB),NOSPLIT,$0
|
||||||
|
RET
|
|
@ -0,0 +1,18 @@
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AtSymlinkNoFollow defined from AT_SYMLINK_NOFOLLOW in <sys/fcntl.h>
|
||||||
|
AtSymlinkNofollow = 0x20
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
|
||||||
|
// SYS_FCHMODAT defined from golang.org/sys/unix
|
||||||
|
SYS_FCHMODAT = 467
|
||||||
|
)
|
||||||
|
|
||||||
|
// These functions will be generated by generate.sh
|
||||||
|
// $ GOOS=darwin GOARCH=386 ./generate.sh chmod
|
||||||
|
// $ GOOS=darwin GOARCH=amd64 ./generate.sh chmod
|
||||||
|
|
||||||
|
//sys Fchmodat(dirfd int, path string, mode uint32, flags int) (err error)
|
|
@ -0,0 +1,25 @@
|
||||||
|
// mksyscall.pl -l32 chmod_darwin.go
|
||||||
|
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||||
|
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = syscall.BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _, e1 := syscall.Syscall6(SYS_FCHMODAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(flags), 0, 0)
|
||||||
|
use(unsafe.Pointer(_p0))
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
// mksyscall.pl chmod_darwin.go
|
||||||
|
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||||
|
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = syscall.BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _, e1 := syscall.Syscall6(SYS_FCHMODAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(flags), 0, 0)
|
||||||
|
use(unsafe.Pointer(_p0))
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AtSymlinkNoFollow defined from AT_SYMLINK_NOFOLLOW in <sys/fcntl.h>
|
||||||
|
AtSymlinkNofollow = 0x200
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
|
||||||
|
// SYS_FCHMODAT defined from golang.org/sys/unix
|
||||||
|
SYS_FCHMODAT = 490
|
||||||
|
)
|
||||||
|
|
||||||
|
// These functions will be generated by generate.sh
|
||||||
|
// $ GOOS=freebsd GOARCH=amd64 ./generate.sh chmod
|
||||||
|
|
||||||
|
//sys Fchmodat(dirfd int, path string, mode uint32, flags int) (err error)
|
|
@ -0,0 +1,25 @@
|
||||||
|
// mksyscall.pl chmod_freebsd.go
|
||||||
|
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||||
|
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func Fchmodat(dirfd int, path string, mode uint32, flags int) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = syscall.BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _, e1 := syscall.Syscall6(SYS_FCHMODAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(flags), 0, 0)
|
||||||
|
use(unsafe.Pointer(_p0))
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AtSymlinkNoFollow defined from AT_SYMLINK_NOFOLLOW in /usr/include/linux/fcntl.h
|
||||||
|
AtSymlinkNofollow = 0x100
|
||||||
|
)
|
||||||
|
|
||||||
|
func Fchmodat(dirfd int, path string, mode uint32, flags int) error {
|
||||||
|
return syscall.Fchmodat(dirfd, path, mode, flags)
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
// These functions will be generated by generate.sh
|
||||||
|
// $ GOOS=linux GOARCH=386 ./generate.sh copy
|
||||||
|
// $ GOOS=linux GOARCH=amd64 ./generate.sh copy
|
||||||
|
// $ GOOS=linux GOARCH=arm ./generate.sh copy
|
||||||
|
// $ GOOS=linux GOARCH=arm64 ./generate.sh copy
|
||||||
|
|
||||||
|
//sys CopyFileRange(fdin uintptr, offin *int64, fdout uintptr, offout *int64, len int, flags int) (n int, err error)
|
|
@ -0,0 +1,20 @@
|
||||||
|
// mksyscall.pl -l32 copy_linux.go
|
||||||
|
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||||
|
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func CopyFileRange(fdin uintptr, offin *int64, fdout uintptr, offout *int64, len int, flags int) (n int, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall6(SYS_COPY_FILE_RANGE, uintptr(fdin), uintptr(unsafe.Pointer(offin)), uintptr(fdout), uintptr(unsafe.Pointer(offout)), uintptr(len), uintptr(flags))
|
||||||
|
n = int(r0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// mksyscall.pl copy_linux.go
|
||||||
|
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||||
|
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func CopyFileRange(fdin uintptr, offin *int64, fdout uintptr, offout *int64, len int, flags int) (n int, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall6(SYS_COPY_FILE_RANGE, uintptr(fdin), uintptr(unsafe.Pointer(offin)), uintptr(fdout), uintptr(unsafe.Pointer(offout)), uintptr(len), uintptr(flags))
|
||||||
|
n = int(r0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// mksyscall.pl -l32 copy_linux.go
|
||||||
|
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||||
|
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func CopyFileRange(fdin uintptr, offin *int64, fdout uintptr, offout *int64, len int, flags int) (n int, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall6(SYS_COPY_FILE_RANGE, uintptr(fdin), uintptr(unsafe.Pointer(offin)), uintptr(fdout), uintptr(unsafe.Pointer(offout)), uintptr(len), uintptr(flags))
|
||||||
|
n = int(r0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// mksyscall.pl copy_linux.go
|
||||||
|
// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||||
|
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||||
|
|
||||||
|
func CopyFileRange(fdin uintptr, offin *int64, fdout uintptr, offout *int64, len int, flags int) (n int, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall6(SYS_COPY_FILE_RANGE, uintptr(fdin), uintptr(unsafe.Pointer(offin)), uintptr(fdout), uintptr(unsafe.Pointer(offout)), uintptr(len), uintptr(flags))
|
||||||
|
n = int(r0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ENODATA = syscall.ENODATA
|
|
@ -0,0 +1,9 @@
|
||||||
|
// +build darwin freebsd
|
||||||
|
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ENODATA = syscall.ENOATTR
|
|
@ -0,0 +1,37 @@
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _zero uintptr
|
||||||
|
|
||||||
|
// use is a no-op, but the compiler cannot see that it is.
|
||||||
|
// Calling use(p) ensures that p is kept live until that point.
|
||||||
|
//go:noescape
|
||||||
|
func use(p unsafe.Pointer)
|
||||||
|
|
||||||
|
// Do the interface allocations only once for common
|
||||||
|
// Errno values.
|
||||||
|
var (
|
||||||
|
errEAGAIN error = syscall.EAGAIN
|
||||||
|
errEINVAL error = syscall.EINVAL
|
||||||
|
errENOENT error = syscall.ENOENT
|
||||||
|
)
|
||||||
|
|
||||||
|
// errnoErr returns common boxed Errno values, to prevent
|
||||||
|
// allocations at runtime.
|
||||||
|
func errnoErr(e syscall.Errno) error {
|
||||||
|
switch e {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case syscall.EAGAIN:
|
||||||
|
return errEAGAIN
|
||||||
|
case syscall.EINVAL:
|
||||||
|
return errEINVAL
|
||||||
|
case syscall.ENOENT:
|
||||||
|
return errENOENT
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SYS_COPYFILERANGE defined in Kernel 4.5+
|
||||||
|
// Number defined in /usr/include/asm/unistd_32.h
|
||||||
|
SYS_COPY_FILE_RANGE = 377
|
||||||
|
)
|
|
@ -0,0 +1,7 @@
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SYS_COPYFILERANGE defined in Kernel 4.5+
|
||||||
|
// Number defined in /usr/include/asm/unistd_64.h
|
||||||
|
SYS_COPY_FILE_RANGE = 326
|
||||||
|
)
|
|
@ -0,0 +1,7 @@
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SYS_COPY_FILE_RANGE defined in Kernel 4.5+
|
||||||
|
// Number defined in /usr/include/arm-linux-gnueabihf/asm/unistd.h
|
||||||
|
SYS_COPY_FILE_RANGE = 391
|
||||||
|
)
|
|
@ -0,0 +1,7 @@
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SYS_COPY_FILE_RANGE defined in Kernel 4.5+
|
||||||
|
// Number defined in /usr/include/asm-generic/unistd.h
|
||||||
|
SYS_COPY_FILE_RANGE = 285
|
||||||
|
)
|
|
@ -0,0 +1,67 @@
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultXattrBufferSize = 5
|
||||||
|
|
||||||
|
var ErrNotSupported = fmt.Errorf("not supported")
|
||||||
|
|
||||||
|
type listxattrFunc func(path string, dest []byte) (int, error)
|
||||||
|
|
||||||
|
func listxattrAll(path string, listFunc listxattrFunc) ([]string, error) {
|
||||||
|
var p []byte // nil on first execution
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err := listFunc(path, p) // first call gets buffer size.
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if n > len(p) {
|
||||||
|
p = make([]byte, n)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
p = p[:n]
|
||||||
|
|
||||||
|
ps := bytes.Split(bytes.TrimSuffix(p, []byte{0}), []byte{0})
|
||||||
|
var entries []string
|
||||||
|
for _, p := range ps {
|
||||||
|
s := string(p)
|
||||||
|
if s != "" {
|
||||||
|
entries = append(entries, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type getxattrFunc func(string, string, []byte) (int, error)
|
||||||
|
|
||||||
|
func getxattrAll(path, attr string, getFunc getxattrFunc) ([]byte, error) {
|
||||||
|
p := make([]byte, defaultXattrBufferSize)
|
||||||
|
for {
|
||||||
|
n, err := getFunc(path, attr, p)
|
||||||
|
if err != nil {
|
||||||
|
if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERANGE {
|
||||||
|
p = make([]byte, len(p)*2) // this can't be ideal.
|
||||||
|
continue // try again!
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// realloc to correct size and repeat
|
||||||
|
if n > len(p) {
|
||||||
|
p = make([]byte, n)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return p[:n], nil
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue