mirror of https://github.com/docker/cli.git
Merge pull request #101 from jlhawn/update_container_wait
Update `run` and `start` to use container wait API
This commit is contained in:
commit
b3e7e1ff74
|
@ -109,7 +109,18 @@ func runAttach(dockerCli *command.DockerCli, opts *attachOptions) error {
|
||||||
logrus.Debugf("Error monitoring TTY size: %s", err)
|
logrus.Debugf("Error monitoring TTY size: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := holdHijackedConnection(ctx, dockerCli, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp); err != nil {
|
|
||||||
|
streamer := hijackedIOStreamer{
|
||||||
|
streams: dockerCli,
|
||||||
|
inputStream: in,
|
||||||
|
outputStream: dockerCli.Out(),
|
||||||
|
errorStream: dockerCli.Err(),
|
||||||
|
resp: resp,
|
||||||
|
tty: c.Config.Tty,
|
||||||
|
detachKeys: options.DetachKeys,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := streamer.stream(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -146,7 +146,17 @@ func runExec(dockerCli *command.DockerCli, options *execOptions, container strin
|
||||||
}
|
}
|
||||||
defer resp.Close()
|
defer resp.Close()
|
||||||
errCh = promise.Go(func() error {
|
errCh = promise.Go(func() error {
|
||||||
return holdHijackedConnection(ctx, dockerCli, execConfig.Tty, in, out, stderr, resp)
|
streamer := hijackedIOStreamer{
|
||||||
|
streams: dockerCli,
|
||||||
|
inputStream: in,
|
||||||
|
outputStream: out,
|
||||||
|
errorStream: stderr,
|
||||||
|
resp: resp,
|
||||||
|
tty: execConfig.Tty,
|
||||||
|
detachKeys: execConfig.DetachKeys,
|
||||||
|
}
|
||||||
|
|
||||||
|
return streamer.stream(ctx)
|
||||||
})
|
})
|
||||||
|
|
||||||
if execConfig.Tty && dockerCli.In().IsTerminal() {
|
if execConfig.Tty && dockerCli.In().IsTerminal() {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -8,92 +9,173 @@ import (
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
|
"github.com/docker/docker/pkg/term"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// holdHijackedConnection handles copying input to and output from streams to the
|
// The default escape key sequence: ctrl-p, ctrl-q
|
||||||
// connection
|
// TODO: This could be moved to `pkg/term`.
|
||||||
// nolint: gocyclo
|
var defaultEscapeKeys = []byte{16, 17}
|
||||||
func holdHijackedConnection(ctx context.Context, streams command.Streams, tty bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
|
|
||||||
var (
|
// A hijackedIOStreamer handles copying input to and output from streams to the
|
||||||
err error
|
// connection.
|
||||||
restoreOnce sync.Once
|
type hijackedIOStreamer struct {
|
||||||
)
|
streams command.Streams
|
||||||
if inputStream != nil && tty {
|
inputStream io.ReadCloser
|
||||||
if err := setRawTerminal(streams); err != nil {
|
outputStream io.Writer
|
||||||
return err
|
errorStream io.Writer
|
||||||
}
|
|
||||||
defer func() {
|
resp types.HijackedResponse
|
||||||
restoreOnce.Do(func() {
|
|
||||||
restoreTerminal(streams, inputStream)
|
tty bool
|
||||||
})
|
detachKeys string
|
||||||
}()
|
}
|
||||||
|
|
||||||
|
// stream handles setting up the IO and then begins streaming stdin/stdout
|
||||||
|
// to/from the hijacked connection, blocking until it is either done reading
|
||||||
|
// output, the user inputs the detach key sequence when in TTY mode, or when
|
||||||
|
// the given context is cancelled.
|
||||||
|
func (h *hijackedIOStreamer) stream(ctx context.Context) error {
|
||||||
|
restoreInput, err := h.setupInput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to setup input stream: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveStdout := make(chan error, 1)
|
defer restoreInput()
|
||||||
if outputStream != nil || errorStream != nil {
|
|
||||||
go func() {
|
|
||||||
// When TTY is ON, use regular copy
|
|
||||||
if tty && outputStream != nil {
|
|
||||||
_, err = io.Copy(outputStream, resp.Reader)
|
|
||||||
// we should restore the terminal as soon as possible once connection end
|
|
||||||
// so any following print messages will be in normal type.
|
|
||||||
if inputStream != nil {
|
|
||||||
restoreOnce.Do(func() {
|
|
||||||
restoreTerminal(streams, inputStream)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = stdcopy.StdCopy(outputStream, errorStream, resp.Reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.Debug("[hijack] End of stdout")
|
outputDone := h.beginOutputStream(restoreInput)
|
||||||
receiveStdout <- err
|
inputDone, detached := h.beginInputStream(restoreInput)
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
stdinDone := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
if inputStream != nil {
|
|
||||||
io.Copy(resp.Conn, inputStream)
|
|
||||||
// we should restore the terminal as soon as possible once connection end
|
|
||||||
// so any following print messages will be in normal type.
|
|
||||||
if tty {
|
|
||||||
restoreOnce.Do(func() {
|
|
||||||
restoreTerminal(streams, inputStream)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
logrus.Debug("[hijack] End of stdin")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := resp.CloseWrite(); err != nil {
|
|
||||||
logrus.Debugf("Couldn't send EOF: %s", err)
|
|
||||||
}
|
|
||||||
close(stdinDone)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case err := <-receiveStdout:
|
case err := <-outputDone:
|
||||||
if err != nil {
|
return err
|
||||||
logrus.Debugf("Error receiveStdout: %s", err)
|
case <-inputDone:
|
||||||
return err
|
// Input stream has closed.
|
||||||
}
|
if h.outputStream != nil || h.errorStream != nil {
|
||||||
case <-stdinDone:
|
// Wait for output to complete streaming.
|
||||||
if outputStream != nil || errorStream != nil {
|
|
||||||
select {
|
select {
|
||||||
case err := <-receiveStdout:
|
case err := <-outputDone:
|
||||||
if err != nil {
|
return err
|
||||||
logrus.Debugf("Error receiveStdout: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
case err := <-detached:
|
||||||
|
// Got a detach key sequence.
|
||||||
|
return err
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
|
||||||
|
if h.inputStream == nil || !h.tty {
|
||||||
|
// No need to setup input TTY.
|
||||||
|
// The restore func is a nop.
|
||||||
|
return func() {}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
if err := setRawTerminal(h.streams); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to set IO streams as raw terminal: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use sync.Once so we may call restore multiple times but ensure we
|
||||||
|
// only restore the terminal once.
|
||||||
|
var restoreOnce sync.Once
|
||||||
|
restore = func() {
|
||||||
|
restoreOnce.Do(func() {
|
||||||
|
restoreTerminal(h.streams, h.inputStream)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the input to detect detach escape sequence.
|
||||||
|
// Use default escape keys if an invalid sequence is given.
|
||||||
|
escapeKeys := defaultEscapeKeys
|
||||||
|
if h.detachKeys != "" {
|
||||||
|
customEscapeKeys, err := term.ToBytes(h.detachKeys)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warnf("invalid detach escape keys, using default: %s", err)
|
||||||
|
} else {
|
||||||
|
escapeKeys = customEscapeKeys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h.inputStream = ioutils.NewReadCloserWrapper(term.NewEscapeProxy(h.inputStream, escapeKeys), h.inputStream.Close)
|
||||||
|
|
||||||
|
return restore, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hijackedIOStreamer) beginOutputStream(restoreInput func()) <-chan error {
|
||||||
|
if h.outputStream == nil && h.errorStream == nil {
|
||||||
|
// Ther is no need to copy output.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
outputDone := make(chan error)
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// When TTY is ON, use regular copy
|
||||||
|
if h.outputStream != nil && h.tty {
|
||||||
|
_, err = io.Copy(h.outputStream, h.resp.Reader)
|
||||||
|
// We should restore the terminal as soon as possible
|
||||||
|
// once the connection ends so any following print
|
||||||
|
// messages will be in normal type.
|
||||||
|
restoreInput()
|
||||||
|
} else {
|
||||||
|
_, err = stdcopy.StdCopy(h.outputStream, h.errorStream, h.resp.Reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debug("[hijack] End of stdout")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Error receiveStdout: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outputDone <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
return outputDone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *hijackedIOStreamer) beginInputStream(restoreInput func()) (doneC <-chan struct{}, detachedC <-chan error) {
|
||||||
|
inputDone := make(chan struct{})
|
||||||
|
detached := make(chan error)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if h.inputStream != nil {
|
||||||
|
_, err := io.Copy(h.resp.Conn, h.inputStream)
|
||||||
|
// We should restore the terminal as soon as possible
|
||||||
|
// once the connection ends so any following print
|
||||||
|
// messages will be in normal type.
|
||||||
|
restoreInput()
|
||||||
|
|
||||||
|
logrus.Debug("[hijack] End of stdin")
|
||||||
|
|
||||||
|
if _, ok := err.(term.EscapeError); ok {
|
||||||
|
detached <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// This error will also occur on the receive
|
||||||
|
// side (from stdout) where it will be
|
||||||
|
// propogated back to the caller.
|
||||||
|
logrus.Debugf("Error sendStdin: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.resp.CloseWrite(); err != nil {
|
||||||
|
logrus.Debugf("Couldn't send EOF: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
close(inputDone)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return inputDone, detached
|
||||||
}
|
}
|
||||||
|
|
||||||
func setRawTerminal(streams command.Streams) error {
|
func setRawTerminal(streams command.Streams) error {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/pkg/promise"
|
"github.com/docker/docker/pkg/promise"
|
||||||
"github.com/docker/docker/pkg/signal"
|
"github.com/docker/docker/pkg/signal"
|
||||||
|
"github.com/docker/docker/pkg/term"
|
||||||
"github.com/docker/libnetwork/resolvconf/dns"
|
"github.com/docker/libnetwork/resolvconf/dns"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -175,8 +176,8 @@ func runContainer(dockerCli *command.DockerCli, opts *runOptions, copts *contain
|
||||||
|
|
||||||
//start the container
|
//start the container
|
||||||
if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
|
if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
|
||||||
// If we have holdHijackedConnection, we should notify
|
// If we have hijackedIOStreamer, we should notify
|
||||||
// holdHijackedConnection we are going to exit and wait
|
// hijackedIOStreamer we are going to exit and wait
|
||||||
// to avoid the terminal are not restored.
|
// to avoid the terminal are not restored.
|
||||||
if attach {
|
if attach {
|
||||||
cancelFun()
|
cancelFun()
|
||||||
|
@ -199,6 +200,11 @@ func runContainer(dockerCli *command.DockerCli, opts *runOptions, copts *contain
|
||||||
|
|
||||||
if errCh != nil {
|
if errCh != nil {
|
||||||
if err := <-errCh; err != nil {
|
if err := <-errCh; err != nil {
|
||||||
|
if _, ok := err.(term.EscapeError); ok {
|
||||||
|
// The user entered the detach escape sequence.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
logrus.Debugf("Error hijack: %s", err)
|
logrus.Debugf("Error hijack: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -261,7 +267,17 @@ func attachContainer(
|
||||||
}
|
}
|
||||||
|
|
||||||
*errCh = promise.Go(func() error {
|
*errCh = promise.Go(func() error {
|
||||||
if errHijack := holdHijackedConnection(ctx, dockerCli, config.Tty, in, out, cerr, resp); errHijack != nil {
|
streamer := hijackedIOStreamer{
|
||||||
|
streams: dockerCli,
|
||||||
|
inputStream: in,
|
||||||
|
outputStream: out,
|
||||||
|
errorStream: cerr,
|
||||||
|
resp: resp,
|
||||||
|
tty: config.Tty,
|
||||||
|
detachKeys: options.DetachKeys,
|
||||||
|
}
|
||||||
|
|
||||||
|
if errHijack := streamer.stream(ctx); errHijack != nil {
|
||||||
return errHijack
|
return errHijack
|
||||||
}
|
}
|
||||||
return errAttach
|
return errAttach
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/pkg/promise"
|
"github.com/docker/docker/pkg/promise"
|
||||||
"github.com/docker/docker/pkg/signal"
|
"github.com/docker/docker/pkg/signal"
|
||||||
|
"github.com/docker/docker/pkg/term"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -103,7 +104,17 @@ func runStart(dockerCli *command.DockerCli, opts *startOptions) error {
|
||||||
}
|
}
|
||||||
defer resp.Close()
|
defer resp.Close()
|
||||||
cErr := promise.Go(func() error {
|
cErr := promise.Go(func() error {
|
||||||
errHijack := holdHijackedConnection(ctx, dockerCli, c.Config.Tty, in, dockerCli.Out(), dockerCli.Err(), resp)
|
streamer := hijackedIOStreamer{
|
||||||
|
streams: dockerCli,
|
||||||
|
inputStream: in,
|
||||||
|
outputStream: dockerCli.Out(),
|
||||||
|
errorStream: dockerCli.Err(),
|
||||||
|
resp: resp,
|
||||||
|
tty: c.Config.Tty,
|
||||||
|
detachKeys: options.DetachKeys,
|
||||||
|
}
|
||||||
|
|
||||||
|
errHijack := streamer.stream(ctx)
|
||||||
if errHijack == nil {
|
if errHijack == nil {
|
||||||
return errAttach
|
return errAttach
|
||||||
}
|
}
|
||||||
|
@ -136,6 +147,10 @@ func runStart(dockerCli *command.DockerCli, opts *startOptions) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if attchErr := <-cErr; attchErr != nil {
|
if attchErr := <-cErr; attchErr != nil {
|
||||||
|
if _, ok := err.(term.EscapeError); ok {
|
||||||
|
// The user entered the detach escape sequence.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return attchErr
|
return attchErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/events"
|
"github.com/docker/docker/api/types/events"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
|
@ -13,12 +14,41 @@ import (
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
func waitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli, containerID string, waitRemove bool) chan int {
|
func waitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli, containerID string, waitRemove bool) <-chan int {
|
||||||
if len(containerID) == 0 {
|
if len(containerID) == 0 {
|
||||||
// containerID can never be empty
|
// containerID can never be empty
|
||||||
panic("Internal Error: waitExitOrRemoved needs a containerID as parameter")
|
panic("Internal Error: waitExitOrRemoved needs a containerID as parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Older versions used the Events API, and even older versions did not
|
||||||
|
// support server-side removal. This legacyWaitExitOrRemoved method
|
||||||
|
// preserves that old behavior and any issues it may have.
|
||||||
|
if versions.LessThan(dockerCli.Client().ClientVersion(), "1.30") {
|
||||||
|
return legacyWaitExitOrRemoved(ctx, dockerCli, containerID, waitRemove)
|
||||||
|
}
|
||||||
|
|
||||||
|
condition := container.WaitConditionNextExit
|
||||||
|
if waitRemove {
|
||||||
|
condition = container.WaitConditionRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
resultC, errC := dockerCli.Client().ContainerWait(ctx, containerID, condition)
|
||||||
|
|
||||||
|
statusC := make(chan int)
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case result := <-resultC:
|
||||||
|
statusC <- int(result.StatusCode)
|
||||||
|
case err := <-errC:
|
||||||
|
logrus.Errorf("error waiting for container: %v", err)
|
||||||
|
statusC <- 125
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return statusC
|
||||||
|
}
|
||||||
|
|
||||||
|
func legacyWaitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli, containerID string, waitRemove bool) <-chan int {
|
||||||
var removeErr error
|
var removeErr error
|
||||||
statusChan := make(chan int)
|
statusChan := make(chan int)
|
||||||
exitCode := 125
|
exitCode := 125
|
||||||
|
|
|
@ -28,6 +28,7 @@ func NewWaitCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
return runWait(dockerCli, &opts)
|
return runWait(dockerCli, &opts)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,12 +37,14 @@ func runWait(dockerCli *command.DockerCli, opts *waitOptions) error {
|
||||||
|
|
||||||
var errs []string
|
var errs []string
|
||||||
for _, container := range opts.containers {
|
for _, container := range opts.containers {
|
||||||
status, err := dockerCli.Client().ContainerWait(ctx, container)
|
resultC, errC := dockerCli.Client().ContainerWait(ctx, container, "")
|
||||||
if err != nil {
|
|
||||||
|
select {
|
||||||
|
case result := <-resultC:
|
||||||
|
fmt.Fprintf(dockerCli.Out(), "%d\n", result.StatusCode)
|
||||||
|
case err := <-errC:
|
||||||
errs = append(errs, err.Error())
|
errs = append(errs, err.Error())
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
fmt.Fprintf(dockerCli.Out(), "%d\n", status)
|
|
||||||
}
|
}
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return errors.New(strings.Join(errs, "\n"))
|
return errors.New(strings.Join(errs, "\n"))
|
||||||
|
|
|
@ -6,7 +6,7 @@ github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c
|
||||||
github.com/coreos/etcd 824277cb3a577a0e8c829ca9ec557b973fe06d20
|
github.com/coreos/etcd 824277cb3a577a0e8c829ca9ec557b973fe06d20
|
||||||
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 7fd8a9382c0fd3f23002288e357b5612b869a974
|
github.com/docker/docker 77c9728847358a3ed3581d828fb0753017e1afd3
|
||||||
github.com/docker/docker-credential-helpers v0.5.0
|
github.com/docker/docker-credential-helpers v0.5.0
|
||||||
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06
|
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06
|
||||||
github.com/docker/go-connections e15c02316c12de00874640cd76311849de2aeed5
|
github.com/docker/go-connections e15c02316c12de00874640cd76311849de2aeed5
|
||||||
|
@ -26,6 +26,7 @@ github.com/mattn/go-shellwords v1.0.3
|
||||||
github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f
|
github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f
|
||||||
github.com/mitchellh/mapstructure f3009df150dadf309fdee4a54ed65c124afad715
|
github.com/mitchellh/mapstructure f3009df150dadf309fdee4a54ed65c124afad715
|
||||||
github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb
|
github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb
|
||||||
|
github.com/opencontainers/image-spec f03dbe35d449c54915d235f1a3cf8f585a24babe
|
||||||
github.com/opencontainers/runc 9c2d8d184e5da67c95d601382adf14862e4f2228 https://github.com/docker/runc.git
|
github.com/opencontainers/runc 9c2d8d184e5da67c95d601382adf14862e4f2228 https://github.com/docker/runc.git
|
||||||
github.com/opencontainers/selinux ba1aefe8057f1d0cfb8e88d0ec1dc85925ef987d
|
github.com/opencontainers/selinux ba1aefe8057f1d0cfb8e88d0ec1dc85925ef987d
|
||||||
github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
|
github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
package manifest
|
|
|
@ -1,155 +0,0 @@
|
||||||
package manifestlist
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/distribution"
|
|
||||||
"github.com/docker/distribution/manifest"
|
|
||||||
"github.com/opencontainers/go-digest"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MediaTypeManifestList specifies the mediaType for manifest lists.
|
|
||||||
const MediaTypeManifestList = "application/vnd.docker.distribution.manifest.list.v2+json"
|
|
||||||
|
|
||||||
// SchemaVersion provides a pre-initialized version structure for this
|
|
||||||
// packages version of the manifest.
|
|
||||||
var SchemaVersion = manifest.Versioned{
|
|
||||||
SchemaVersion: 2,
|
|
||||||
MediaType: MediaTypeManifestList,
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
manifestListFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
|
|
||||||
m := new(DeserializedManifestList)
|
|
||||||
err := m.UnmarshalJSON(b)
|
|
||||||
if err != nil {
|
|
||||||
return nil, distribution.Descriptor{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
dgst := digest.FromBytes(b)
|
|
||||||
return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: MediaTypeManifestList}, err
|
|
||||||
}
|
|
||||||
err := distribution.RegisterManifestSchema(MediaTypeManifestList, manifestListFunc)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("Unable to register manifest: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PlatformSpec specifies a platform where a particular image manifest is
|
|
||||||
// applicable.
|
|
||||||
type PlatformSpec struct {
|
|
||||||
// Architecture field specifies the CPU architecture, for example
|
|
||||||
// `amd64` or `ppc64`.
|
|
||||||
Architecture string `json:"architecture"`
|
|
||||||
|
|
||||||
// OS specifies the operating system, for example `linux` or `windows`.
|
|
||||||
OS string `json:"os"`
|
|
||||||
|
|
||||||
// OSVersion is an optional field specifying the operating system
|
|
||||||
// version, for example `10.0.10586`.
|
|
||||||
OSVersion string `json:"os.version,omitempty"`
|
|
||||||
|
|
||||||
// OSFeatures is an optional field specifying an array of strings,
|
|
||||||
// each listing a required OS feature (for example on Windows `win32k`).
|
|
||||||
OSFeatures []string `json:"os.features,omitempty"`
|
|
||||||
|
|
||||||
// Variant is an optional field specifying a variant of the CPU, for
|
|
||||||
// example `ppc64le` to specify a little-endian version of a PowerPC CPU.
|
|
||||||
Variant string `json:"variant,omitempty"`
|
|
||||||
|
|
||||||
// Features is an optional field specifying an array of strings, each
|
|
||||||
// listing a required CPU feature (for example `sse4` or `aes`).
|
|
||||||
Features []string `json:"features,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// A ManifestDescriptor references a platform-specific manifest.
|
|
||||||
type ManifestDescriptor struct {
|
|
||||||
distribution.Descriptor
|
|
||||||
|
|
||||||
// Platform specifies which platform the manifest pointed to by the
|
|
||||||
// descriptor runs on.
|
|
||||||
Platform PlatformSpec `json:"platform"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ManifestList references manifests for various platforms.
|
|
||||||
type ManifestList struct {
|
|
||||||
manifest.Versioned
|
|
||||||
|
|
||||||
// Config references the image configuration as a blob.
|
|
||||||
Manifests []ManifestDescriptor `json:"manifests"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// References returnes the distribution descriptors for the referenced image
|
|
||||||
// manifests.
|
|
||||||
func (m ManifestList) References() []distribution.Descriptor {
|
|
||||||
dependencies := make([]distribution.Descriptor, len(m.Manifests))
|
|
||||||
for i := range m.Manifests {
|
|
||||||
dependencies[i] = m.Manifests[i].Descriptor
|
|
||||||
}
|
|
||||||
|
|
||||||
return dependencies
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeserializedManifestList wraps ManifestList with a copy of the original
|
|
||||||
// JSON.
|
|
||||||
type DeserializedManifestList struct {
|
|
||||||
ManifestList
|
|
||||||
|
|
||||||
// canonical is the canonical byte representation of the Manifest.
|
|
||||||
canonical []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromDescriptors takes a slice of descriptors, and returns a
|
|
||||||
// DeserializedManifestList which contains the resulting manifest list
|
|
||||||
// and its JSON representation.
|
|
||||||
func FromDescriptors(descriptors []ManifestDescriptor) (*DeserializedManifestList, error) {
|
|
||||||
m := ManifestList{
|
|
||||||
Versioned: SchemaVersion,
|
|
||||||
}
|
|
||||||
|
|
||||||
m.Manifests = make([]ManifestDescriptor, len(descriptors), len(descriptors))
|
|
||||||
copy(m.Manifests, descriptors)
|
|
||||||
|
|
||||||
deserialized := DeserializedManifestList{
|
|
||||||
ManifestList: m,
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
deserialized.canonical, err = json.MarshalIndent(&m, "", " ")
|
|
||||||
return &deserialized, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON populates a new ManifestList struct from JSON data.
|
|
||||||
func (m *DeserializedManifestList) UnmarshalJSON(b []byte) error {
|
|
||||||
m.canonical = make([]byte, len(b), len(b))
|
|
||||||
// store manifest list in canonical
|
|
||||||
copy(m.canonical, b)
|
|
||||||
|
|
||||||
// Unmarshal canonical JSON into ManifestList object
|
|
||||||
var manifestList ManifestList
|
|
||||||
if err := json.Unmarshal(m.canonical, &manifestList); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
m.ManifestList = manifestList
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON returns the contents of canonical. If canonical is empty,
|
|
||||||
// marshals the inner contents.
|
|
||||||
func (m *DeserializedManifestList) MarshalJSON() ([]byte, error) {
|
|
||||||
if len(m.canonical) > 0 {
|
|
||||||
return m.canonical, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("JSON representation not initialized in DeserializedManifestList")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Payload returns the raw content of the manifest list. The contents can be
|
|
||||||
// used to calculate the content identifier.
|
|
||||||
func (m DeserializedManifestList) Payload() (string, []byte, error) {
|
|
||||||
return m.MediaType, m.canonical, nil
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
package manifest
|
|
||||||
|
|
||||||
// Versioned provides a struct with the manifest schemaVersion and mediaType.
|
|
||||||
// Incoming content with unknown schema version can be decoded against this
|
|
||||||
// struct to check the version.
|
|
||||||
type Versioned struct {
|
|
||||||
// SchemaVersion is the image manifest schema that this image follows
|
|
||||||
SchemaVersion int `json:"schemaVersion"`
|
|
||||||
|
|
||||||
// MediaType is the media type of this schema.
|
|
||||||
MediaType string `json:"mediaType,omitempty"`
|
|
||||||
}
|
|
|
@ -276,6 +276,12 @@ type ServiceCreateOptions struct {
|
||||||
//
|
//
|
||||||
// This field follows the format of the X-Registry-Auth header.
|
// This field follows the format of the X-Registry-Auth header.
|
||||||
EncodedRegistryAuth string
|
EncodedRegistryAuth string
|
||||||
|
|
||||||
|
// QueryRegistry indicates whether the service update requires
|
||||||
|
// contacting a registry. A registry may be contacted to retrieve
|
||||||
|
// the image digest and manifest, which in turn can be used to update
|
||||||
|
// platform or other information about the service.
|
||||||
|
QueryRegistry bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceCreateResponse contains the information returned to a client
|
// ServiceCreateResponse contains the information returned to a client
|
||||||
|
@ -315,6 +321,12 @@ type ServiceUpdateOptions struct {
|
||||||
// The valid values are "previous" and "none". An empty value is the
|
// The valid values are "previous" and "none". An empty value is the
|
||||||
// same as "none".
|
// same as "none".
|
||||||
Rollback string
|
Rollback string
|
||||||
|
|
||||||
|
// QueryRegistry indicates whether the service update requires
|
||||||
|
// contacting a registry. A registry may be contacted to retrieve
|
||||||
|
// the image digest and manifest, which in turn can be used to update
|
||||||
|
// platform or other information about the service.
|
||||||
|
QueryRegistry bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceListOptions holds parameters to list services with.
|
// ServiceListOptions holds parameters to list services with.
|
||||||
|
|
22
vendor/github.com/docker/docker/api/types/container/waitcondition.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker/api/types/container/waitcondition.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package container
|
||||||
|
|
||||||
|
// WaitCondition is a type used to specify a container state for which
|
||||||
|
// to wait.
|
||||||
|
type WaitCondition string
|
||||||
|
|
||||||
|
// Possible WaitCondition Values.
|
||||||
|
//
|
||||||
|
// WaitConditionNotRunning (default) is used to wait for any of the non-running
|
||||||
|
// states: "created", "exited", "dead", "removing", or "removed".
|
||||||
|
//
|
||||||
|
// WaitConditionNextExit is used to wait for the next time the state changes
|
||||||
|
// to a non-running state. If the state is currently "created" or "exited",
|
||||||
|
// this would cause Wait() to block until either the container runs and exits
|
||||||
|
// or is removed.
|
||||||
|
//
|
||||||
|
// WaitConditionRemoved is used to wait for the container to be removed.
|
||||||
|
const (
|
||||||
|
WaitConditionNotRunning WaitCondition = "not-running"
|
||||||
|
WaitConditionNextExit WaitCondition = "next-exit"
|
||||||
|
WaitConditionRemoved WaitCondition = "removed"
|
||||||
|
)
|
|
@ -4,15 +4,16 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/docker/distribution"
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/docker/distribution/manifest/manifestlist"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServiceConfig stores daemon registry services configuration.
|
// ServiceConfig stores daemon registry services configuration.
|
||||||
type ServiceConfig struct {
|
type ServiceConfig struct {
|
||||||
InsecureRegistryCIDRs []*NetIPNet `json:"InsecureRegistryCIDRs"`
|
AllowNondistributableArtifactsCIDRs []*NetIPNet
|
||||||
IndexConfigs map[string]*IndexInfo `json:"IndexConfigs"`
|
AllowNondistributableArtifactsHostnames []string
|
||||||
Mirrors []string
|
InsecureRegistryCIDRs []*NetIPNet `json:"InsecureRegistryCIDRs"`
|
||||||
|
IndexConfigs map[string]*IndexInfo `json:"IndexConfigs"`
|
||||||
|
Mirrors []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetIPNet is the net.IPNet type, which can be marshalled and
|
// NetIPNet is the net.IPNet type, which can be marshalled and
|
||||||
|
@ -111,8 +112,8 @@ type SearchResults struct {
|
||||||
type DistributionInspect struct {
|
type DistributionInspect struct {
|
||||||
// Descriptor contains information about the manifest, including
|
// Descriptor contains information about the manifest, including
|
||||||
// the content addressable digest
|
// the content addressable digest
|
||||||
Descriptor distribution.Descriptor
|
Descriptor v1.Descriptor
|
||||||
// Platforms contains the list of platforms supported by the image,
|
// Platforms contains the list of platforms supported by the image,
|
||||||
// obtained by parsing the manifest
|
// obtained by parsing the manifest
|
||||||
Platforms []manifestlist.PlatformSpec
|
Platforms []v1.Platform
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,11 @@ type ResourceRequirements struct {
|
||||||
type Placement struct {
|
type Placement struct {
|
||||||
Constraints []string `json:",omitempty"`
|
Constraints []string `json:",omitempty"`
|
||||||
Preferences []PlacementPreference `json:",omitempty"`
|
Preferences []PlacementPreference `json:",omitempty"`
|
||||||
|
|
||||||
|
// Platforms stores all the platforms that the image can run on.
|
||||||
|
// This field is used in the platform filter for scheduling. If empty,
|
||||||
|
// then the platform filter is off, meaning there are no scheduling restrictions.
|
||||||
|
Platforms []Platform `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlacementPreference provides a way to make the scheduler aware of factors
|
// PlacementPreference provides a way to make the scheduler aware of factors
|
||||||
|
|
|
@ -2,25 +2,83 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/versions"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContainerWait pauses execution until a container exits.
|
// ContainerWait waits until the specified continer is in a certain state
|
||||||
// It returns the API status code as response of its readiness.
|
// indicated by the given condition, either "not-running" (default),
|
||||||
func (cli *Client) ContainerWait(ctx context.Context, containerID string) (int64, error) {
|
// "next-exit", or "removed".
|
||||||
resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", nil, nil, nil)
|
//
|
||||||
|
// If this client's API version is beforer 1.30, condition is ignored and
|
||||||
|
// ContainerWait will return immediately with the two channels, as the server
|
||||||
|
// will wait as if the condition were "not-running".
|
||||||
|
//
|
||||||
|
// If this client's API version is at least 1.30, ContainerWait blocks until
|
||||||
|
// the request has been acknowledged by the server (with a response header),
|
||||||
|
// then returns two channels on which the caller can wait for the exit status
|
||||||
|
// of the container or an error if there was a problem either beginning the
|
||||||
|
// wait request or in getting the response. This allows the caller to
|
||||||
|
// sychronize ContainerWait with other calls, such as specifying a
|
||||||
|
// "next-exit" condition before issuing a ContainerStart request.
|
||||||
|
func (cli *Client) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.ContainerWaitOKBody, <-chan error) {
|
||||||
|
if versions.LessThan(cli.ClientVersion(), "1.30") {
|
||||||
|
return cli.legacyContainerWait(ctx, containerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
resultC := make(chan container.ContainerWaitOKBody)
|
||||||
|
errC := make(chan error)
|
||||||
|
|
||||||
|
query := url.Values{}
|
||||||
|
query.Set("condition", string(condition))
|
||||||
|
|
||||||
|
resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", query, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, err
|
defer ensureReaderClosed(resp)
|
||||||
}
|
errC <- err
|
||||||
defer ensureReaderClosed(resp)
|
return resultC, errC
|
||||||
|
|
||||||
var res container.ContainerWaitOKBody
|
|
||||||
if err := json.NewDecoder(resp.body).Decode(&res); err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.StatusCode, nil
|
go func() {
|
||||||
|
defer ensureReaderClosed(resp)
|
||||||
|
var res container.ContainerWaitOKBody
|
||||||
|
if err := json.NewDecoder(resp.body).Decode(&res); err != nil {
|
||||||
|
errC <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resultC <- res
|
||||||
|
}()
|
||||||
|
|
||||||
|
return resultC, errC
|
||||||
|
}
|
||||||
|
|
||||||
|
// legacyContainerWait returns immediately and doesn't have an option to wait
|
||||||
|
// until the container is removed.
|
||||||
|
func (cli *Client) legacyContainerWait(ctx context.Context, containerID string) (<-chan container.ContainerWaitOKBody, <-chan error) {
|
||||||
|
resultC := make(chan container.ContainerWaitOKBody)
|
||||||
|
errC := make(chan error)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
errC <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer ensureReaderClosed(resp)
|
||||||
|
|
||||||
|
var res container.ContainerWaitOKBody
|
||||||
|
if err := json.NewDecoder(resp.body).Decode(&res); err != nil {
|
||||||
|
errC <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resultC <- res
|
||||||
|
}()
|
||||||
|
|
||||||
|
return resultC, errC
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
registrytypes "github.com/docker/docker/api/types/registry"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DistributionInspect returns the image digest with full Manifest
|
||||||
|
func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegistryAuth string) (registrytypes.DistributionInspect, error) {
|
||||||
|
var headers map[string][]string
|
||||||
|
|
||||||
|
if encodedRegistryAuth != "" {
|
||||||
|
headers = map[string][]string{
|
||||||
|
"X-Registry-Auth": {encodedRegistryAuth},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contact the registry to retrieve digest and platform information
|
||||||
|
var distributionInspect registrytypes.DistributionInspect
|
||||||
|
resp, err := cli.get(ctx, "/distribution/"+image+"/json", url.Values{}, headers)
|
||||||
|
if err != nil {
|
||||||
|
return distributionInspect, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.NewDecoder(resp.body).Decode(&distributionInspect)
|
||||||
|
ensureReaderClosed(resp)
|
||||||
|
return distributionInspect, err
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import (
|
||||||
type CommonAPIClient interface {
|
type CommonAPIClient interface {
|
||||||
ConfigAPIClient
|
ConfigAPIClient
|
||||||
ContainerAPIClient
|
ContainerAPIClient
|
||||||
|
DistributionAPIClient
|
||||||
ImageAPIClient
|
ImageAPIClient
|
||||||
NodeAPIClient
|
NodeAPIClient
|
||||||
NetworkAPIClient
|
NetworkAPIClient
|
||||||
|
@ -63,12 +64,17 @@ type ContainerAPIClient interface {
|
||||||
ContainerTop(ctx context.Context, container string, arguments []string) (container.ContainerTopOKBody, error)
|
ContainerTop(ctx context.Context, container string, arguments []string) (container.ContainerTopOKBody, error)
|
||||||
ContainerUnpause(ctx context.Context, container string) error
|
ContainerUnpause(ctx context.Context, container string) error
|
||||||
ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.ContainerUpdateOKBody, error)
|
ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.ContainerUpdateOKBody, error)
|
||||||
ContainerWait(ctx context.Context, container string) (int64, error)
|
ContainerWait(ctx context.Context, container string, condition container.WaitCondition) (<-chan container.ContainerWaitOKBody, <-chan error)
|
||||||
CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error)
|
CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error)
|
||||||
CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error
|
CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error
|
||||||
ContainersPrune(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error)
|
ContainersPrune(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DistributionAPIClient defines API client methods for the registry
|
||||||
|
type DistributionAPIClient interface {
|
||||||
|
DistributionInspect(ctx context.Context, image, encodedRegistryAuth string) (registry.DistributionInspect, error)
|
||||||
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
|
|
|
@ -2,22 +2,39 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServiceCreate creates a new Service.
|
// ServiceCreate creates a new Service.
|
||||||
func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error) {
|
func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error) {
|
||||||
var headers map[string][]string
|
var distErr error
|
||||||
|
|
||||||
if options.EncodedRegistryAuth != "" {
|
headers := map[string][]string{
|
||||||
headers = map[string][]string{
|
"version": {cli.version},
|
||||||
"X-Registry-Auth": {options.EncodedRegistryAuth},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if options.EncodedRegistryAuth != "" {
|
||||||
|
headers["X-Registry-Auth"] = []string{options.EncodedRegistryAuth}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contact the registry to retrieve digest and platform information
|
||||||
|
if options.QueryRegistry {
|
||||||
|
distributionInspect, err := cli.DistributionInspect(ctx, service.TaskTemplate.ContainerSpec.Image, options.EncodedRegistryAuth)
|
||||||
|
distErr = err
|
||||||
|
if err == nil {
|
||||||
|
// now pin by digest if the image doesn't already contain a digest
|
||||||
|
img := imageWithDigestString(service.TaskTemplate.ContainerSpec.Image, distributionInspect.Descriptor.Digest)
|
||||||
|
if img != "" {
|
||||||
|
service.TaskTemplate.ContainerSpec.Image = img
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
var response types.ServiceCreateResponse
|
var response types.ServiceCreateResponse
|
||||||
resp, err := cli.post(ctx, "/services/create", nil, service, headers)
|
resp, err := cli.post(ctx, "/services/create", nil, service, headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -25,6 +42,38 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.NewDecoder(resp.body).Decode(&response)
|
err = json.NewDecoder(resp.body).Decode(&response)
|
||||||
|
|
||||||
|
if distErr != nil {
|
||||||
|
response.Warnings = append(response.Warnings, digestWarning(service.TaskTemplate.ContainerSpec.Image))
|
||||||
|
}
|
||||||
|
|
||||||
ensureReaderClosed(resp)
|
ensureReaderClosed(resp)
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// imageWithDigestString takes an image string and a digest, and updates
|
||||||
|
// the image string if it didn't originally contain a digest. It assumes
|
||||||
|
// that the image string is not an image ID
|
||||||
|
func imageWithDigestString(image string, dgst digest.Digest) string {
|
||||||
|
isCanonical := false
|
||||||
|
ref, err := reference.ParseAnyReference(image)
|
||||||
|
if err == nil {
|
||||||
|
_, isCanonical = ref.(reference.Canonical)
|
||||||
|
|
||||||
|
if !isCanonical {
|
||||||
|
namedRef, _ := ref.(reference.Named)
|
||||||
|
img, err := reference.WithDigest(namedRef, dgst)
|
||||||
|
if err == nil {
|
||||||
|
return img.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// digestWarning constructs a formatted warning string using the
|
||||||
|
// image name that could not be pinned by digest. The formatting
|
||||||
|
// is hardcoded, but could me made smarter in the future
|
||||||
|
func digestWarning(image string) string {
|
||||||
|
return fmt.Sprintf("image %s could not be accessed on a registry to record\nits digest. Each node will access %s independently,\npossibly leading to different nodes running different\nversions of the image.\n", image, image)
|
||||||
|
}
|
||||||
|
|
|
@ -13,14 +13,16 @@ import (
|
||||||
// ServiceUpdate updates a Service.
|
// ServiceUpdate updates a Service.
|
||||||
func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) {
|
func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) {
|
||||||
var (
|
var (
|
||||||
headers map[string][]string
|
|
||||||
query = url.Values{}
|
query = url.Values{}
|
||||||
|
distErr error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
headers := map[string][]string{
|
||||||
|
"version": {cli.version},
|
||||||
|
}
|
||||||
|
|
||||||
if options.EncodedRegistryAuth != "" {
|
if options.EncodedRegistryAuth != "" {
|
||||||
headers = map[string][]string{
|
headers["X-Registry-Auth"] = []string{options.EncodedRegistryAuth}
|
||||||
"X-Registry-Auth": {options.EncodedRegistryAuth},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.RegistryAuthFrom != "" {
|
if options.RegistryAuthFrom != "" {
|
||||||
|
@ -33,6 +35,20 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
|
||||||
|
|
||||||
query.Set("version", strconv.FormatUint(version.Index, 10))
|
query.Set("version", strconv.FormatUint(version.Index, 10))
|
||||||
|
|
||||||
|
// Contact the registry to retrieve digest and platform information
|
||||||
|
// This happens only when the image has changed
|
||||||
|
if options.QueryRegistry {
|
||||||
|
distributionInspect, err := cli.DistributionInspect(ctx, service.TaskTemplate.ContainerSpec.Image, options.EncodedRegistryAuth)
|
||||||
|
distErr = err
|
||||||
|
if err == nil {
|
||||||
|
// now pin by digest if the image doesn't already contain a digest
|
||||||
|
img := imageWithDigestString(service.TaskTemplate.ContainerSpec.Image, distributionInspect.Descriptor.Digest)
|
||||||
|
if img != "" {
|
||||||
|
service.TaskTemplate.ContainerSpec.Image = img
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var response types.ServiceUpdateResponse
|
var response types.ServiceUpdateResponse
|
||||||
resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers)
|
resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -40,6 +56,11 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.NewDecoder(resp.body).Decode(&response)
|
err = json.NewDecoder(resp.body).Decode(&response)
|
||||||
|
|
||||||
|
if distErr != nil {
|
||||||
|
response.Warnings = append(response.Warnings, digestWarning(service.TaskTemplate.ContainerSpec.Image))
|
||||||
|
}
|
||||||
|
|
||||||
ensureReaderClosed(resp)
|
ensureReaderClosed(resp)
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
)
|
)
|
||||||
|
|
||||||
var headerRegexp = regexp.MustCompile(`\ADocker/.+\s\((.+)\)\z`)
|
var headerRegexp = regexp.MustCompile(`\ADocker/.+\s\((.+)\)\z`)
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
## About
|
||||||
|
|
||||||
|
This directory contains a collection of scripts used to build and manage this
|
||||||
|
repository. If there are any issues regarding the intention of a particular
|
||||||
|
script (or even part of a certain script), please reach out to us.
|
||||||
|
It may help us either refine our current scripts, or add on new ones
|
||||||
|
that are appropriate for a given use case.
|
||||||
|
|
||||||
|
## DinD (dind.sh)
|
||||||
|
|
||||||
|
DinD is a wrapper script which allows Docker to be run inside a Docker
|
||||||
|
container. DinD requires the container to
|
||||||
|
be run with privileged mode enabled.
|
||||||
|
|
||||||
|
## Generate Authors (generate-authors.sh)
|
||||||
|
|
||||||
|
Generates AUTHORS; a file with all the names and corresponding emails of
|
||||||
|
individual contributors. AUTHORS can be found in the home directory of
|
||||||
|
this repository.
|
||||||
|
|
||||||
|
## Install (install.sh)
|
||||||
|
|
||||||
|
Executable install script for installing Docker. If updates to this are
|
||||||
|
desired, please use hack/release.sh during a normal release. The following
|
||||||
|
one-liner may be used for script hotfixes:
|
||||||
|
|
||||||
|
- `aws s3 cp --acl public-read hack/install.sh s3://get.docker.com/index`
|
||||||
|
|
||||||
|
## Make
|
||||||
|
|
||||||
|
There are two make files, each with different extensions. Neither are supposed
|
||||||
|
to be called directly; only invoke `make`. Both scripts run inside a Docker
|
||||||
|
container.
|
||||||
|
|
||||||
|
### make.ps1
|
||||||
|
|
||||||
|
- The Windows native build script that uses PowerShell semantics; it is limited
|
||||||
|
unlike `hack\make.sh` since it does not provide support for the full set of
|
||||||
|
operations provided by the Linux counterpart, `make.sh`. However, `make.ps1`
|
||||||
|
does provide support for local Windows development and Windows to Windows CI.
|
||||||
|
More information is found within `make.ps1` by the author, @jhowardmsft
|
||||||
|
|
||||||
|
### make.sh
|
||||||
|
|
||||||
|
- Referenced via `make test` when running tests on a local machine,
|
||||||
|
or directly referenced when running tests inside a Docker development container.
|
||||||
|
- When running on a local machine, `make test` to run all tests found in
|
||||||
|
`test`, `test-unit`, `test-integration-cli`, and `test-docker-py` on
|
||||||
|
your local machine. The default timeout is set in `make.sh` to 60 minutes
|
||||||
|
(`${TIMEOUT:=60m}`), since it currently takes up to an hour to run
|
||||||
|
all of the tests.
|
||||||
|
- When running inside a Docker development container, `hack/make.sh` does
|
||||||
|
not have a single target that runs all the tests. You need to provide a
|
||||||
|
single command line with multiple targets that performs the same thing.
|
||||||
|
An example referenced from [Run targets inside a development container](https://docs.docker.com/opensource/project/test-and-docs/#run-targets-inside-a-development-container): `root@5f8630b873fe:/go/src/github.com/moby/moby# hack/make.sh dynbinary binary cross test-unit test-integration-cli test-docker-py`
|
||||||
|
- For more information related to testing outside the scope of this README,
|
||||||
|
refer to
|
||||||
|
[Run tests and test documentation](https://docs.docker.com/opensource/project/test-and-docs/)
|
||||||
|
|
||||||
|
## Release (release.sh)
|
||||||
|
|
||||||
|
Releases any bundles built by `make` on a public AWS S3 bucket.
|
||||||
|
For information regarding configuration, please view `release.sh`.
|
||||||
|
|
||||||
|
## Vendor (vendor.sh)
|
||||||
|
|
||||||
|
A shell script that is a wrapper around Vndr. For information on how to use
|
||||||
|
this, please refer to [vndr's README](https://github.com/LK4D4/vndr/blob/master/README.md)
|
|
@ -38,6 +38,8 @@ Following environment variables are known to work in this step:
|
||||||
- `BUILDFLAGS`
|
- `BUILDFLAGS`
|
||||||
- `DOCKER_INCREMENTAL_BINARY`
|
- `DOCKER_INCREMENTAL_BINARY`
|
||||||
|
|
||||||
|
Note: during the transition into Moby Project, you might need to create a symbolic link `$GOPATH/src/github.com/docker/docker` to `$GOPATH/src/github.com/moby/moby`.
|
||||||
|
|
||||||
### Step 2: Execute tests
|
### Step 2: Execute tests
|
||||||
|
|
||||||
$ ./hack/integration-cli-on-swarm/integration-cli-on-swarm -replicas 40 -push-worker-image YOUR_REGISTRY.EXAMPLE.COM/integration-cli-worker:latest
|
$ ./hack/integration-cli-on-swarm/integration-cli-on-swarm -replicas 40 -push-worker-image YOUR_REGISTRY.EXAMPLE.COM/integration-cli-worker:latest
|
||||||
|
|
|
@ -1,173 +0,0 @@
|
||||||
package opts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/csv"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
mounttypes "github.com/docker/docker/api/types/mount"
|
|
||||||
"github.com/docker/go-units"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MountOpt is a Value type for parsing mounts
|
|
||||||
type MountOpt struct {
|
|
||||||
values []mounttypes.Mount
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a new mount value
|
|
||||||
func (m *MountOpt) Set(value string) error {
|
|
||||||
csvReader := csv.NewReader(strings.NewReader(value))
|
|
||||||
fields, err := csvReader.Read()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
mount := mounttypes.Mount{}
|
|
||||||
|
|
||||||
volumeOptions := func() *mounttypes.VolumeOptions {
|
|
||||||
if mount.VolumeOptions == nil {
|
|
||||||
mount.VolumeOptions = &mounttypes.VolumeOptions{
|
|
||||||
Labels: make(map[string]string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if mount.VolumeOptions.DriverConfig == nil {
|
|
||||||
mount.VolumeOptions.DriverConfig = &mounttypes.Driver{}
|
|
||||||
}
|
|
||||||
return mount.VolumeOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
bindOptions := func() *mounttypes.BindOptions {
|
|
||||||
if mount.BindOptions == nil {
|
|
||||||
mount.BindOptions = new(mounttypes.BindOptions)
|
|
||||||
}
|
|
||||||
return mount.BindOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpfsOptions := func() *mounttypes.TmpfsOptions {
|
|
||||||
if mount.TmpfsOptions == nil {
|
|
||||||
mount.TmpfsOptions = new(mounttypes.TmpfsOptions)
|
|
||||||
}
|
|
||||||
return mount.TmpfsOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
setValueOnMap := func(target map[string]string, value string) {
|
|
||||||
parts := strings.SplitN(value, "=", 2)
|
|
||||||
if len(parts) == 1 {
|
|
||||||
target[value] = ""
|
|
||||||
} else {
|
|
||||||
target[parts[0]] = parts[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mount.Type = mounttypes.TypeVolume // default to volume mounts
|
|
||||||
// Set writable as the default
|
|
||||||
for _, field := range fields {
|
|
||||||
parts := strings.SplitN(field, "=", 2)
|
|
||||||
key := strings.ToLower(parts[0])
|
|
||||||
|
|
||||||
if len(parts) == 1 {
|
|
||||||
switch key {
|
|
||||||
case "readonly", "ro":
|
|
||||||
mount.ReadOnly = true
|
|
||||||
continue
|
|
||||||
case "volume-nocopy":
|
|
||||||
volumeOptions().NoCopy = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
|
|
||||||
}
|
|
||||||
|
|
||||||
value := parts[1]
|
|
||||||
switch key {
|
|
||||||
case "type":
|
|
||||||
mount.Type = mounttypes.Type(strings.ToLower(value))
|
|
||||||
case "source", "src":
|
|
||||||
mount.Source = value
|
|
||||||
case "target", "dst", "destination":
|
|
||||||
mount.Target = value
|
|
||||||
case "readonly", "ro":
|
|
||||||
mount.ReadOnly, err = strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid value for %s: %s", key, value)
|
|
||||||
}
|
|
||||||
case "consistency":
|
|
||||||
mount.Consistency = mounttypes.Consistency(strings.ToLower(value))
|
|
||||||
case "bind-propagation":
|
|
||||||
bindOptions().Propagation = mounttypes.Propagation(strings.ToLower(value))
|
|
||||||
case "volume-nocopy":
|
|
||||||
volumeOptions().NoCopy, err = strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid value for volume-nocopy: %s", value)
|
|
||||||
}
|
|
||||||
case "volume-label":
|
|
||||||
setValueOnMap(volumeOptions().Labels, value)
|
|
||||||
case "volume-driver":
|
|
||||||
volumeOptions().DriverConfig.Name = value
|
|
||||||
case "volume-opt":
|
|
||||||
if volumeOptions().DriverConfig.Options == nil {
|
|
||||||
volumeOptions().DriverConfig.Options = make(map[string]string)
|
|
||||||
}
|
|
||||||
setValueOnMap(volumeOptions().DriverConfig.Options, value)
|
|
||||||
case "tmpfs-size":
|
|
||||||
sizeBytes, err := units.RAMInBytes(value)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid value for %s: %s", key, value)
|
|
||||||
}
|
|
||||||
tmpfsOptions().SizeBytes = sizeBytes
|
|
||||||
case "tmpfs-mode":
|
|
||||||
ui64, err := strconv.ParseUint(value, 8, 32)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid value for %s: %s", key, value)
|
|
||||||
}
|
|
||||||
tmpfsOptions().Mode = os.FileMode(ui64)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unexpected key '%s' in '%s'", key, field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if mount.Type == "" {
|
|
||||||
return fmt.Errorf("type is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if mount.Target == "" {
|
|
||||||
return fmt.Errorf("target is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if mount.VolumeOptions != nil && mount.Type != mounttypes.TypeVolume {
|
|
||||||
return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", mount.Type)
|
|
||||||
}
|
|
||||||
if mount.BindOptions != nil && mount.Type != mounttypes.TypeBind {
|
|
||||||
return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", mount.Type)
|
|
||||||
}
|
|
||||||
if mount.TmpfsOptions != nil && mount.Type != mounttypes.TypeTmpfs {
|
|
||||||
return fmt.Errorf("cannot mix 'tmpfs-*' options with mount type '%s'", mount.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.values = append(m.values, mount)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the type of this option
|
|
||||||
func (m *MountOpt) Type() string {
|
|
||||||
return "mount"
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string repr of this option
|
|
||||||
func (m *MountOpt) String() string {
|
|
||||||
mounts := []string{}
|
|
||||||
for _, mount := range m.values {
|
|
||||||
repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target)
|
|
||||||
mounts = append(mounts, repr)
|
|
||||||
}
|
|
||||||
return strings.Join(mounts, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the mounts
|
|
||||||
func (m *MountOpt) Value() []mounttypes.Mount {
|
|
||||||
return m.values
|
|
||||||
}
|
|
|
@ -1,162 +0,0 @@
|
||||||
package opts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/csv"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/swarm"
|
|
||||||
"github.com/docker/go-connections/nat"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
portOptTargetPort = "target"
|
|
||||||
portOptPublishedPort = "published"
|
|
||||||
portOptProtocol = "protocol"
|
|
||||||
portOptMode = "mode"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PortOpt represents a port config in swarm mode.
|
|
||||||
type PortOpt struct {
|
|
||||||
ports []swarm.PortConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a new port value
|
|
||||||
func (p *PortOpt) Set(value string) error {
|
|
||||||
longSyntax, err := regexp.MatchString(`\w+=\w+(,\w+=\w+)*`, value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if longSyntax {
|
|
||||||
csvReader := csv.NewReader(strings.NewReader(value))
|
|
||||||
fields, err := csvReader.Read()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pConfig := swarm.PortConfig{}
|
|
||||||
for _, field := range fields {
|
|
||||||
parts := strings.SplitN(field, "=", 2)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return fmt.Errorf("invalid field %s", field)
|
|
||||||
}
|
|
||||||
|
|
||||||
key := strings.ToLower(parts[0])
|
|
||||||
value := strings.ToLower(parts[1])
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case portOptProtocol:
|
|
||||||
if value != string(swarm.PortConfigProtocolTCP) && value != string(swarm.PortConfigProtocolUDP) {
|
|
||||||
return fmt.Errorf("invalid protocol value %s", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pConfig.Protocol = swarm.PortConfigProtocol(value)
|
|
||||||
case portOptMode:
|
|
||||||
if value != string(swarm.PortConfigPublishModeIngress) && value != string(swarm.PortConfigPublishModeHost) {
|
|
||||||
return fmt.Errorf("invalid publish mode value %s", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pConfig.PublishMode = swarm.PortConfigPublishMode(value)
|
|
||||||
case portOptTargetPort:
|
|
||||||
tPort, err := strconv.ParseUint(value, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pConfig.TargetPort = uint32(tPort)
|
|
||||||
case portOptPublishedPort:
|
|
||||||
pPort, err := strconv.ParseUint(value, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pConfig.PublishedPort = uint32(pPort)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid field key %s", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pConfig.TargetPort == 0 {
|
|
||||||
return fmt.Errorf("missing mandatory field %q", portOptTargetPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
if pConfig.PublishMode == "" {
|
|
||||||
pConfig.PublishMode = swarm.PortConfigPublishModeIngress
|
|
||||||
}
|
|
||||||
|
|
||||||
if pConfig.Protocol == "" {
|
|
||||||
pConfig.Protocol = swarm.PortConfigProtocolTCP
|
|
||||||
}
|
|
||||||
|
|
||||||
p.ports = append(p.ports, pConfig)
|
|
||||||
} else {
|
|
||||||
// short syntax
|
|
||||||
portConfigs := []swarm.PortConfig{}
|
|
||||||
ports, portBindingMap, err := nat.ParsePortSpecs([]string{value})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, portBindings := range portBindingMap {
|
|
||||||
for _, portBinding := range portBindings {
|
|
||||||
if portBinding.HostIP != "" {
|
|
||||||
return fmt.Errorf("HostIP is not supported.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for port := range ports {
|
|
||||||
portConfig, err := ConvertPortToPortConfig(port, portBindingMap)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
portConfigs = append(portConfigs, portConfig...)
|
|
||||||
}
|
|
||||||
p.ports = append(p.ports, portConfigs...)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the type of this option
|
|
||||||
func (p *PortOpt) Type() string {
|
|
||||||
return "port"
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string repr of this option
|
|
||||||
func (p *PortOpt) String() string {
|
|
||||||
ports := []string{}
|
|
||||||
for _, port := range p.ports {
|
|
||||||
repr := fmt.Sprintf("%v:%v/%s/%s", port.PublishedPort, port.TargetPort, port.Protocol, port.PublishMode)
|
|
||||||
ports = append(ports, repr)
|
|
||||||
}
|
|
||||||
return strings.Join(ports, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the ports
|
|
||||||
func (p *PortOpt) Value() []swarm.PortConfig {
|
|
||||||
return p.ports
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertPortToPortConfig converts ports to the swarm type
|
|
||||||
func ConvertPortToPortConfig(
|
|
||||||
port nat.Port,
|
|
||||||
portBindings map[nat.Port][]nat.PortBinding,
|
|
||||||
) ([]swarm.PortConfig, error) {
|
|
||||||
ports := []swarm.PortConfig{}
|
|
||||||
|
|
||||||
for _, binding := range portBindings[port] {
|
|
||||||
hostPort, err := strconv.ParseUint(binding.HostPort, 10, 16)
|
|
||||||
if err != nil && binding.HostPort != "" {
|
|
||||||
return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port.Port())
|
|
||||||
}
|
|
||||||
ports = append(ports, swarm.PortConfig{
|
|
||||||
//TODO Name: ?
|
|
||||||
Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
|
|
||||||
TargetPort: uint32(port.Int()),
|
|
||||||
PublishedPort: uint32(hostPort),
|
|
||||||
PublishMode: swarm.PortConfigPublishModeIngress,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return ports, nil
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
package opts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/csv"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
swarmtypes "github.com/docker/docker/api/types/swarm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SecretOpt is a Value type for parsing secrets
|
|
||||||
type SecretOpt struct {
|
|
||||||
values []*swarmtypes.SecretReference
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a new secret value
|
|
||||||
func (o *SecretOpt) Set(value string) error {
|
|
||||||
csvReader := csv.NewReader(strings.NewReader(value))
|
|
||||||
fields, err := csvReader.Read()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
options := &swarmtypes.SecretReference{
|
|
||||||
File: &swarmtypes.SecretReferenceFileTarget{
|
|
||||||
UID: "0",
|
|
||||||
GID: "0",
|
|
||||||
Mode: 0444,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// support a simple syntax of --secret foo
|
|
||||||
if len(fields) == 1 {
|
|
||||||
options.File.Name = fields[0]
|
|
||||||
options.SecretName = fields[0]
|
|
||||||
o.values = append(o.values, options)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, field := range fields {
|
|
||||||
parts := strings.SplitN(field, "=", 2)
|
|
||||||
key := strings.ToLower(parts[0])
|
|
||||||
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
|
|
||||||
}
|
|
||||||
|
|
||||||
value := parts[1]
|
|
||||||
switch key {
|
|
||||||
case "source", "src":
|
|
||||||
options.SecretName = value
|
|
||||||
case "target":
|
|
||||||
options.File.Name = value
|
|
||||||
case "uid":
|
|
||||||
options.File.UID = value
|
|
||||||
case "gid":
|
|
||||||
options.File.GID = value
|
|
||||||
case "mode":
|
|
||||||
m, err := strconv.ParseUint(value, 0, 32)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid mode specified: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
options.File.Mode = os.FileMode(m)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid field in secret request: %s", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.SecretName == "" {
|
|
||||||
return fmt.Errorf("source is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
o.values = append(o.values, options)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the type of this option
|
|
||||||
func (o *SecretOpt) Type() string {
|
|
||||||
return "secret"
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string repr of this option
|
|
||||||
func (o *SecretOpt) String() string {
|
|
||||||
secrets := []string{}
|
|
||||||
for _, secret := range o.values {
|
|
||||||
repr := fmt.Sprintf("%s -> %s", secret.SecretName, secret.File.Name)
|
|
||||||
secrets = append(secrets, repr)
|
|
||||||
}
|
|
||||||
return strings.Join(secrets, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the secret requests
|
|
||||||
func (o *SecretOpt) Value() []*swarmtypes.SecretReference {
|
|
||||||
return o.values
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
package opts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/blkiodev"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ValidatorWeightFctType defines a validator function that returns a validated struct and/or an error.
|
|
||||||
type ValidatorWeightFctType func(val string) (*blkiodev.WeightDevice, error)
|
|
||||||
|
|
||||||
// ValidateWeightDevice validates that the specified string has a valid device-weight format.
|
|
||||||
func ValidateWeightDevice(val string) (*blkiodev.WeightDevice, error) {
|
|
||||||
split := strings.SplitN(val, ":", 2)
|
|
||||||
if len(split) != 2 {
|
|
||||||
return nil, fmt.Errorf("bad format: %s", val)
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(split[0], "/dev/") {
|
|
||||||
return nil, fmt.Errorf("bad format for device path: %s", val)
|
|
||||||
}
|
|
||||||
weight, err := strconv.ParseUint(split[1], 10, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid weight for device: %s", val)
|
|
||||||
}
|
|
||||||
if weight > 0 && (weight < 10 || weight > 1000) {
|
|
||||||
return nil, fmt.Errorf("invalid weight for device: %s", val)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &blkiodev.WeightDevice{
|
|
||||||
Path: split[0],
|
|
||||||
Weight: uint16(weight),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WeightdeviceOpt defines a map of WeightDevices
|
|
||||||
type WeightdeviceOpt struct {
|
|
||||||
values []*blkiodev.WeightDevice
|
|
||||||
validator ValidatorWeightFctType
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWeightdeviceOpt creates a new WeightdeviceOpt
|
|
||||||
func NewWeightdeviceOpt(validator ValidatorWeightFctType) WeightdeviceOpt {
|
|
||||||
values := []*blkiodev.WeightDevice{}
|
|
||||||
return WeightdeviceOpt{
|
|
||||||
values: values,
|
|
||||||
validator: validator,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set validates a WeightDevice and sets its name as a key in WeightdeviceOpt
|
|
||||||
func (opt *WeightdeviceOpt) Set(val string) error {
|
|
||||||
var value *blkiodev.WeightDevice
|
|
||||||
if opt.validator != nil {
|
|
||||||
v, err := opt.validator(val)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
value = v
|
|
||||||
}
|
|
||||||
(opt.values) = append((opt.values), value)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns WeightdeviceOpt values as a string.
|
|
||||||
func (opt *WeightdeviceOpt) String() string {
|
|
||||||
var out []string
|
|
||||||
for _, v := range opt.values {
|
|
||||||
out = append(out, v.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%v", out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetList returns a slice of pointers to WeightDevices.
|
|
||||||
func (opt *WeightdeviceOpt) GetList() []*blkiodev.WeightDevice {
|
|
||||||
var weightdevice []*blkiodev.WeightDevice
|
|
||||||
for _, v := range opt.values {
|
|
||||||
weightdevice = append(weightdevice, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return weightdevice
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns the option type
|
|
||||||
func (opt *WeightdeviceOpt) Type() string {
|
|
||||||
return "list"
|
|
||||||
}
|
|
|
@ -42,11 +42,14 @@ func CanonicalTarNameForPath(p string) (string, error) {
|
||||||
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
||||||
// on the platform the archival is done.
|
// on the platform the archival is done.
|
||||||
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
||||||
perm &= 0755
|
//perm &= 0755 // this 0-ed out tar flags (like link, regular file, directory marker etc.)
|
||||||
|
permPart := perm & os.ModePerm
|
||||||
|
noPermPart := perm &^ os.ModePerm
|
||||||
// Add the x bit: make everything +x from windows
|
// Add the x bit: make everything +x from windows
|
||||||
perm |= 0111
|
permPart |= 0111
|
||||||
|
permPart &= 0755
|
||||||
|
|
||||||
return perm
|
return noPermPart | permPart
|
||||||
}
|
}
|
||||||
|
|
||||||
func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) (err error) {
|
func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) (err error) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/symlink"
|
"github.com/docker/docker/pkg/symlink"
|
||||||
"github.com/docker/docker/pkg/urlutil"
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Clone clones a repository into a newly created directory which
|
// Clone clones a repository into a newly created directory which
|
||||||
|
@ -30,21 +31,45 @@ func Clone(remoteURL string) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment := u.Fragment
|
if out, err := gitWithinDir(root, "init"); err != nil {
|
||||||
clone := cloneArgs(u, root)
|
return "", errors.Wrapf(err, "failed to init repo at %s: %s", root, out)
|
||||||
|
|
||||||
if output, err := git(clone...); err != nil {
|
|
||||||
return "", fmt.Errorf("Error trying to use git: %s (%s)", err, output)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return checkoutGit(fragment, root)
|
ref, subdir := getRefAndSubdir(u.Fragment)
|
||||||
|
fetch := fetchArgs(u, ref)
|
||||||
|
|
||||||
|
u.Fragment = ""
|
||||||
|
|
||||||
|
// Add origin remote for compatibility with previous implementation that
|
||||||
|
// used "git clone" and also to make sure local refs are created for branches
|
||||||
|
if out, err := gitWithinDir(root, "remote", "add", "origin", u.String()); err != nil {
|
||||||
|
return "", errors.Wrapf(err, "failed add origin repo at %s: %s", u.String(), out)
|
||||||
|
}
|
||||||
|
|
||||||
|
if output, err := gitWithinDir(root, fetch...); err != nil {
|
||||||
|
return "", errors.Wrapf(err, "error fetching: %s", output)
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkoutGit(root, ref, subdir)
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloneArgs(remoteURL *url.URL, root string) []string {
|
func getRefAndSubdir(fragment string) (ref string, subdir string) {
|
||||||
args := []string{"clone", "--recursive"}
|
refAndDir := strings.SplitN(fragment, ":", 2)
|
||||||
shallow := len(remoteURL.Fragment) == 0
|
ref = "master"
|
||||||
|
if len(refAndDir[0]) != 0 {
|
||||||
|
ref = refAndDir[0]
|
||||||
|
}
|
||||||
|
if len(refAndDir) > 1 && len(refAndDir[1]) != 0 {
|
||||||
|
subdir = refAndDir[1]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if shallow && strings.HasPrefix(remoteURL.Scheme, "http") {
|
func fetchArgs(remoteURL *url.URL, ref string) []string {
|
||||||
|
args := []string{"fetch", "--recurse-submodules=yes"}
|
||||||
|
shallow := true
|
||||||
|
|
||||||
|
if strings.HasPrefix(remoteURL.Scheme, "http") {
|
||||||
res, err := http.Head(fmt.Sprintf("%s/info/refs?service=git-upload-pack", remoteURL))
|
res, err := http.Head(fmt.Sprintf("%s/info/refs?service=git-upload-pack", remoteURL))
|
||||||
if err != nil || res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" {
|
if err != nil || res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" {
|
||||||
shallow = false
|
shallow = false
|
||||||
|
@ -55,26 +80,23 @@ func cloneArgs(remoteURL *url.URL, root string) []string {
|
||||||
args = append(args, "--depth", "1")
|
args = append(args, "--depth", "1")
|
||||||
}
|
}
|
||||||
|
|
||||||
if remoteURL.Fragment != "" {
|
return append(args, "origin", ref)
|
||||||
remoteURL.Fragment = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(args, remoteURL.String(), root)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkoutGit(fragment, root string) (string, error) {
|
func checkoutGit(root, ref, subdir string) (string, error) {
|
||||||
refAndDir := strings.SplitN(fragment, ":", 2)
|
// Try checking out by ref name first. This will work on branches and sets
|
||||||
|
// .git/HEAD to the current branch name
|
||||||
if len(refAndDir[0]) != 0 {
|
if output, err := gitWithinDir(root, "checkout", ref); err != nil {
|
||||||
if output, err := gitWithinDir(root, "checkout", refAndDir[0]); err != nil {
|
// If checking out by branch name fails check out the last fetched ref
|
||||||
return "", fmt.Errorf("Error trying to use git: %s (%s)", err, output)
|
if _, err2 := gitWithinDir(root, "checkout", "FETCH_HEAD"); err2 != nil {
|
||||||
|
return "", errors.Wrapf(err, "error checking out %s: %s", ref, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(refAndDir) > 1 && len(refAndDir[1]) != 0 {
|
if subdir != "" {
|
||||||
newCtx, err := symlink.FollowSymlinkInScope(filepath.Join(root, refAndDir[1]), root)
|
newCtx, err := symlink.FollowSymlinkInScope(filepath.Join(root, subdir), root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Error setting git context, %q not within git root: %s", refAndDir[1], err)
|
return "", errors.Wrapf(err, "error setting git context, %q not within git root", subdir)
|
||||||
}
|
}
|
||||||
|
|
||||||
fi, err := os.Stat(newCtx)
|
fi, err := os.Stat(newCtx)
|
||||||
|
@ -82,7 +104,7 @@ func checkoutGit(fragment, root string) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if !fi.IsDir() {
|
if !fi.IsDir() {
|
||||||
return "", fmt.Errorf("Error setting git context, not a directory: %s", newCtx)
|
return "", errors.Errorf("error setting git context, not a directory: %s", newCtx)
|
||||||
}
|
}
|
||||||
root = newCtx
|
root = newCtx
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EscapeError is special error which returned by a TTY proxy reader's Read()
|
||||||
|
// method in case its detach escape sequence is read.
|
||||||
|
type EscapeError struct{}
|
||||||
|
|
||||||
|
func (EscapeError) Error() string {
|
||||||
|
return "read escape sequence"
|
||||||
|
}
|
||||||
|
|
||||||
|
// escapeProxy is used only for attaches with a TTY. It is used to proxy
|
||||||
|
// stdin keypresses from the underlying reader and look for the passed in
|
||||||
|
// escape key sequence to signal a detach.
|
||||||
|
type escapeProxy struct {
|
||||||
|
escapeKeys []byte
|
||||||
|
escapeKeyPos int
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEscapeProxy returns a new TTY proxy reader which wraps the given reader
|
||||||
|
// and detects when the specified escape keys are read, in which case the Read
|
||||||
|
// method will return an error of type EscapeError.
|
||||||
|
func NewEscapeProxy(r io.Reader, escapeKeys []byte) io.Reader {
|
||||||
|
return &escapeProxy{
|
||||||
|
escapeKeys: escapeKeys,
|
||||||
|
r: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *escapeProxy) Read(buf []byte) (int, error) {
|
||||||
|
nr, err := r.r.Read(buf)
|
||||||
|
|
||||||
|
preserve := func() {
|
||||||
|
// this preserves the original key presses in the passed in buffer
|
||||||
|
nr += r.escapeKeyPos
|
||||||
|
preserve := make([]byte, 0, r.escapeKeyPos+len(buf))
|
||||||
|
preserve = append(preserve, r.escapeKeys[:r.escapeKeyPos]...)
|
||||||
|
preserve = append(preserve, buf...)
|
||||||
|
r.escapeKeyPos = 0
|
||||||
|
copy(buf[0:nr], preserve)
|
||||||
|
}
|
||||||
|
|
||||||
|
if nr != 1 || err != nil {
|
||||||
|
if r.escapeKeyPos > 0 {
|
||||||
|
preserve()
|
||||||
|
}
|
||||||
|
return nr, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[0] != r.escapeKeys[r.escapeKeyPos] {
|
||||||
|
if r.escapeKeyPos > 0 {
|
||||||
|
preserve()
|
||||||
|
}
|
||||||
|
return nr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.escapeKeyPos == len(r.escapeKeys)-1 {
|
||||||
|
return 0, EscapeError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks like we've got an escape key, but we need to match again on the next
|
||||||
|
// read.
|
||||||
|
// Store the current escape key we found so we can look for the next one on
|
||||||
|
// the next read.
|
||||||
|
// Since this is an escape key, make sure we don't let the caller read it
|
||||||
|
// If later on we find that this is not the escape sequence, we'll add the
|
||||||
|
// keys back
|
||||||
|
r.escapeKeyPos++
|
||||||
|
return nr - r.escapeKeyPos, nil
|
||||||
|
}
|
|
@ -18,8 +18,9 @@ import (
|
||||||
|
|
||||||
// ServiceOptions holds command line options.
|
// ServiceOptions holds command line options.
|
||||||
type ServiceOptions struct {
|
type ServiceOptions struct {
|
||||||
Mirrors []string `json:"registry-mirrors,omitempty"`
|
AllowNondistributableArtifacts []string `json:"allow-nondistributable-artifacts,omitempty"`
|
||||||
InsecureRegistries []string `json:"insecure-registries,omitempty"`
|
Mirrors []string `json:"registry-mirrors,omitempty"`
|
||||||
|
InsecureRegistries []string `json:"insecure-registries,omitempty"`
|
||||||
|
|
||||||
// V2Only controls access to legacy registries. If it is set to true via the
|
// V2Only controls access to legacy registries. If it is set to true via the
|
||||||
// command line flag the daemon will not attempt to contact v1 legacy registries
|
// command line flag the daemon will not attempt to contact v1 legacy registries
|
||||||
|
@ -74,9 +75,11 @@ var lookupIP = net.LookupIP
|
||||||
// InstallCliFlags adds command-line options to the top-level flag parser for
|
// InstallCliFlags adds command-line options to the top-level flag parser for
|
||||||
// the current process.
|
// the current process.
|
||||||
func (options *ServiceOptions) InstallCliFlags(flags *pflag.FlagSet) {
|
func (options *ServiceOptions) InstallCliFlags(flags *pflag.FlagSet) {
|
||||||
|
ana := opts.NewNamedListOptsRef("allow-nondistributable-artifacts", &options.AllowNondistributableArtifacts, ValidateIndexName)
|
||||||
mirrors := opts.NewNamedListOptsRef("registry-mirrors", &options.Mirrors, ValidateMirror)
|
mirrors := opts.NewNamedListOptsRef("registry-mirrors", &options.Mirrors, ValidateMirror)
|
||||||
insecureRegistries := opts.NewNamedListOptsRef("insecure-registries", &options.InsecureRegistries, ValidateIndexName)
|
insecureRegistries := opts.NewNamedListOptsRef("insecure-registries", &options.InsecureRegistries, ValidateIndexName)
|
||||||
|
|
||||||
|
flags.Var(ana, "allow-nondistributable-artifacts", "Allow push of nondistributable artifacts to registry")
|
||||||
flags.Var(mirrors, "registry-mirror", "Preferred Docker registry mirror")
|
flags.Var(mirrors, "registry-mirror", "Preferred Docker registry mirror")
|
||||||
flags.Var(insecureRegistries, "insecure-registry", "Enable insecure registry communication")
|
flags.Var(insecureRegistries, "insecure-registry", "Enable insecure registry communication")
|
||||||
|
|
||||||
|
@ -95,12 +98,50 @@ func newServiceConfig(options ServiceOptions) *serviceConfig {
|
||||||
V2Only: options.V2Only,
|
V2Only: options.V2Only,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.LoadAllowNondistributableArtifacts(options.AllowNondistributableArtifacts)
|
||||||
config.LoadMirrors(options.Mirrors)
|
config.LoadMirrors(options.Mirrors)
|
||||||
config.LoadInsecureRegistries(options.InsecureRegistries)
|
config.LoadInsecureRegistries(options.InsecureRegistries)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries into config.
|
||||||
|
func (config *serviceConfig) LoadAllowNondistributableArtifacts(registries []string) error {
|
||||||
|
cidrs := map[string]*registrytypes.NetIPNet{}
|
||||||
|
hostnames := map[string]bool{}
|
||||||
|
|
||||||
|
for _, r := range registries {
|
||||||
|
if _, err := ValidateIndexName(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if validateNoScheme(r) != nil {
|
||||||
|
return fmt.Errorf("allow-nondistributable-artifacts registry %s should not contain '://'", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ipnet, err := net.ParseCIDR(r); err == nil {
|
||||||
|
// Valid CIDR.
|
||||||
|
cidrs[ipnet.String()] = (*registrytypes.NetIPNet)(ipnet)
|
||||||
|
} else if err := validateHostPort(r); err == nil {
|
||||||
|
// Must be `host:port` if not CIDR.
|
||||||
|
hostnames[r] = true
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("allow-nondistributable-artifacts registry %s is not valid: %v", r, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.AllowNondistributableArtifactsCIDRs = make([]*(registrytypes.NetIPNet), 0)
|
||||||
|
for _, c := range cidrs {
|
||||||
|
config.AllowNondistributableArtifactsCIDRs = append(config.AllowNondistributableArtifactsCIDRs, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.AllowNondistributableArtifactsHostnames = make([]string, 0)
|
||||||
|
for h := range hostnames {
|
||||||
|
config.AllowNondistributableArtifactsHostnames = append(config.AllowNondistributableArtifactsHostnames, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// LoadMirrors loads mirrors to config, after removing duplicates.
|
// LoadMirrors loads mirrors to config, after removing duplicates.
|
||||||
// Returns an error if mirrors contains an invalid mirror.
|
// Returns an error if mirrors contains an invalid mirror.
|
||||||
func (config *serviceConfig) LoadMirrors(mirrors []string) error {
|
func (config *serviceConfig) LoadMirrors(mirrors []string) error {
|
||||||
|
@ -211,6 +252,25 @@ skip:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// allowNondistributableArtifacts returns true if the provided hostname is part of the list of regsitries
|
||||||
|
// that allow push of nondistributable artifacts.
|
||||||
|
//
|
||||||
|
// The list can contain elements with CIDR notation to specify a whole subnet. If the subnet contains an IP
|
||||||
|
// of the registry specified by hostname, true is returned.
|
||||||
|
//
|
||||||
|
// hostname should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
|
||||||
|
// or an IP address. If it is a domain name, then it will be resolved to IP addresses for matching. If
|
||||||
|
// resolution fails, CIDR matching is not performed.
|
||||||
|
func allowNondistributableArtifacts(config *serviceConfig, hostname string) bool {
|
||||||
|
for _, h := range config.AllowNondistributableArtifactsHostnames {
|
||||||
|
if h == hostname {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isCIDRMatch(config.AllowNondistributableArtifactsCIDRs, hostname)
|
||||||
|
}
|
||||||
|
|
||||||
// isSecureIndex returns false if the provided indexName is part of the list of insecure registries
|
// isSecureIndex returns false if the provided indexName is part of the list of insecure registries
|
||||||
// Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
|
// Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
|
||||||
//
|
//
|
||||||
|
@ -229,10 +289,17 @@ func isSecureIndex(config *serviceConfig, indexName string) bool {
|
||||||
return index.Secure
|
return index.Secure
|
||||||
}
|
}
|
||||||
|
|
||||||
host, _, err := net.SplitHostPort(indexName)
|
return !isCIDRMatch(config.InsecureRegistryCIDRs, indexName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isCIDRMatch returns true if URLHost matches an element of cidrs. URLHost is a URL.Host (`host:port` or `host`)
|
||||||
|
// where the `host` part can be either a domain name or an IP address. If it is a domain name, then it will be
|
||||||
|
// resolved to IP addresses for matching. If resolution fails, false is returned.
|
||||||
|
func isCIDRMatch(cidrs []*registrytypes.NetIPNet, URLHost string) bool {
|
||||||
|
host, _, err := net.SplitHostPort(URLHost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// assume indexName is of the form `host` without the port and go on.
|
// Assume URLHost is of the form `host` without the port and go on.
|
||||||
host = indexName
|
host = URLHost
|
||||||
}
|
}
|
||||||
|
|
||||||
addrs, err := lookupIP(host)
|
addrs, err := lookupIP(host)
|
||||||
|
@ -249,15 +316,15 @@ func isSecureIndex(config *serviceConfig, indexName string) bool {
|
||||||
|
|
||||||
// Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
|
// Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
for _, ipnet := range config.InsecureRegistryCIDRs {
|
for _, ipnet := range cidrs {
|
||||||
// check if the addr falls in the subnet
|
// check if the addr falls in the subnet
|
||||||
if (*net.IPNet)(ipnet).Contains(addr) {
|
if (*net.IPNet)(ipnet).Contains(addr) {
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateMirror validates an HTTP(S) registry mirror
|
// ValidateMirror validates an HTTP(S) registry mirror
|
||||||
|
|
|
@ -31,6 +31,7 @@ type Service interface {
|
||||||
Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error)
|
Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error)
|
||||||
ServiceConfig() *registrytypes.ServiceConfig
|
ServiceConfig() *registrytypes.ServiceConfig
|
||||||
TLSConfig(hostname string) (*tls.Config, error)
|
TLSConfig(hostname string) (*tls.Config, error)
|
||||||
|
LoadAllowNondistributableArtifacts([]string) error
|
||||||
LoadMirrors([]string) error
|
LoadMirrors([]string) error
|
||||||
LoadInsecureRegistries([]string) error
|
LoadInsecureRegistries([]string) error
|
||||||
}
|
}
|
||||||
|
@ -56,13 +57,17 @@ func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
servConfig := registrytypes.ServiceConfig{
|
servConfig := registrytypes.ServiceConfig{
|
||||||
InsecureRegistryCIDRs: make([]*(registrytypes.NetIPNet), 0),
|
AllowNondistributableArtifactsCIDRs: make([]*(registrytypes.NetIPNet), 0),
|
||||||
IndexConfigs: make(map[string]*(registrytypes.IndexInfo)),
|
AllowNondistributableArtifactsHostnames: make([]string, 0),
|
||||||
Mirrors: make([]string, 0),
|
InsecureRegistryCIDRs: make([]*(registrytypes.NetIPNet), 0),
|
||||||
|
IndexConfigs: make(map[string]*(registrytypes.IndexInfo)),
|
||||||
|
Mirrors: make([]string, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
// construct a new ServiceConfig which will not retrieve s.Config directly,
|
// construct a new ServiceConfig which will not retrieve s.Config directly,
|
||||||
// and look up items in s.config with mu locked
|
// and look up items in s.config with mu locked
|
||||||
|
servConfig.AllowNondistributableArtifactsCIDRs = append(servConfig.AllowNondistributableArtifactsCIDRs, s.config.ServiceConfig.AllowNondistributableArtifactsCIDRs...)
|
||||||
|
servConfig.AllowNondistributableArtifactsHostnames = append(servConfig.AllowNondistributableArtifactsHostnames, s.config.ServiceConfig.AllowNondistributableArtifactsHostnames...)
|
||||||
servConfig.InsecureRegistryCIDRs = append(servConfig.InsecureRegistryCIDRs, s.config.ServiceConfig.InsecureRegistryCIDRs...)
|
servConfig.InsecureRegistryCIDRs = append(servConfig.InsecureRegistryCIDRs, s.config.ServiceConfig.InsecureRegistryCIDRs...)
|
||||||
|
|
||||||
for key, value := range s.config.ServiceConfig.IndexConfigs {
|
for key, value := range s.config.ServiceConfig.IndexConfigs {
|
||||||
|
@ -74,6 +79,14 @@ func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
|
||||||
return &servConfig
|
return &servConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries for Service.
|
||||||
|
func (s *DefaultService) LoadAllowNondistributableArtifacts(registries []string) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
return s.config.LoadAllowNondistributableArtifacts(registries)
|
||||||
|
}
|
||||||
|
|
||||||
// LoadMirrors loads registry mirrors for Service
|
// LoadMirrors loads registry mirrors for Service
|
||||||
func (s *DefaultService) LoadMirrors(mirrors []string) error {
|
func (s *DefaultService) LoadMirrors(mirrors []string) error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
|
@ -235,12 +248,13 @@ func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInf
|
||||||
|
|
||||||
// APIEndpoint represents a remote API endpoint
|
// APIEndpoint represents a remote API endpoint
|
||||||
type APIEndpoint struct {
|
type APIEndpoint struct {
|
||||||
Mirror bool
|
Mirror bool
|
||||||
URL *url.URL
|
URL *url.URL
|
||||||
Version APIVersion
|
Version APIVersion
|
||||||
Official bool
|
AllowNondistributableArtifacts bool
|
||||||
TrimHostname bool
|
Official bool
|
||||||
TLSConfig *tls.Config
|
TrimHostname bool
|
||||||
|
TLSConfig *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
|
// ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
|
||||||
|
|
|
@ -44,6 +44,8 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
|
||||||
return endpoints, nil
|
return endpoints, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ana := allowNondistributableArtifacts(s.config, hostname)
|
||||||
|
|
||||||
tlsConfig, err = s.tlsConfig(hostname)
|
tlsConfig, err = s.tlsConfig(hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -55,9 +57,10 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
|
||||||
Scheme: "https",
|
Scheme: "https",
|
||||||
Host: hostname,
|
Host: hostname,
|
||||||
},
|
},
|
||||||
Version: APIVersion2,
|
Version: APIVersion2,
|
||||||
TrimHostname: true,
|
AllowNondistributableArtifacts: ana,
|
||||||
TLSConfig: tlsConfig,
|
TrimHostname: true,
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,8 +70,9 @@ func (s *DefaultService) lookupV2Endpoints(hostname string) (endpoints []APIEndp
|
||||||
Scheme: "http",
|
Scheme: "http",
|
||||||
Host: hostname,
|
Host: hostname,
|
||||||
},
|
},
|
||||||
Version: APIVersion2,
|
Version: APIVersion2,
|
||||||
TrimHostname: true,
|
AllowNondistributableArtifacts: ana,
|
||||||
|
TrimHostname: true,
|
||||||
// used to check if supposed to be secure via InsecureSkipVerify
|
// used to check if supposed to be secure via InsecureSkipVerify
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
})
|
})
|
||||||
|
|
|
@ -62,6 +62,7 @@ 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 b6b70e53451794e8333e9b602cc096b47a20bd0f
|
github.com/opencontainers/runc b6b70e53451794e8333e9b602cc096b47a20bd0f
|
||||||
github.com/opencontainers/runtime-spec v1.0.0-rc5 # specs
|
github.com/opencontainers/runtime-spec v1.0.0-rc5 # specs
|
||||||
|
github.com/opencontainers/image-spec f03dbe35d449c54915d235f1a3cf8f585a24babe
|
||||||
|
|
||||||
github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0
|
github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0
|
||||||
|
|
||||||
|
@ -105,7 +106,7 @@ github.com/docker/containerd 8ef7df579710405c4bb6e0812495671002ce08e0
|
||||||
github.com/tonistiigi/fifo 1405643975692217d6720f8b54aeee1bf2cd5cf4
|
github.com/tonistiigi/fifo 1405643975692217d6720f8b54aeee1bf2cd5cf4
|
||||||
|
|
||||||
# cluster
|
# cluster
|
||||||
github.com/docker/swarmkit f420c4b9e1535170fc229db97ee8ac32374020b1
|
github.com/docker/swarmkit ae29cf24355ef2106b63884d2f9b0a6406e5a144
|
||||||
github.com/gogo/protobuf 8d70fb3182befc465c4a1eac8ad4d38ff49778e2
|
github.com/gogo/protobuf 8d70fb3182befc465c4a1eac8ad4d38ff49778e2
|
||||||
github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a
|
github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a
|
||||||
github.com/google/certificate-transparency d90e65c3a07988180c5b1ece71791c0b6506826e
|
github.com/google/certificate-transparency d90e65c3a07988180c5b1ece71791c0b6506826e
|
||||||
|
@ -132,6 +133,6 @@ github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||||
github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github.com/ijc25/Gotty
|
github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github.com/ijc25/Gotty
|
||||||
|
|
||||||
# metrics
|
# metrics
|
||||||
github.com/docker/go-metrics 8fd5772bf1584597834c6f7961a530f06cbfbb87
|
github.com/docker/go-metrics d466d4f6fd960e01820085bd7e1a24426ee7ef18
|
||||||
|
|
||||||
github.com/opencontainers/selinux v1.0.0-rc1
|
github.com/opencontainers/selinux v1.0.0-rc1
|
||||||
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Copyright 2016 The Linux Foundation.
|
||||||
|
|
||||||
|
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,167 @@
|
||||||
|
# OCI Image Format Specification
|
||||||
|
<div>
|
||||||
|
<a href="https://travis-ci.org/opencontainers/image-spec">
|
||||||
|
<img src="https://travis-ci.org/opencontainers/image-spec.svg?branch=master"></img>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
The OCI Image Format project creates and maintains the software shipping container image format spec (OCI Image Format).
|
||||||
|
|
||||||
|
**[The specification can be found here](spec.md).**
|
||||||
|
|
||||||
|
This repository also provides [Go types](specs-go), [intra-blob validation tooling, and JSON Schema](schema).
|
||||||
|
The Go types and validation should be compatible with the current Go release; earlier Go releases are not supported.
|
||||||
|
|
||||||
|
Additional documentation about how this group operates:
|
||||||
|
|
||||||
|
- [Code of Conduct](https://github.com/opencontainers/tob/blob/d2f9d68c1332870e40693fe077d311e0742bc73d/code-of-conduct.md)
|
||||||
|
- [Roadmap](#roadmap)
|
||||||
|
- [Releases](RELEASES.md)
|
||||||
|
- [Project Documentation](project.md)
|
||||||
|
|
||||||
|
The _optional_ and _base_ layers of all OCI projects are tracked in the [OCI Scope Table](https://www.opencontainers.org/about/oci-scope-table).
|
||||||
|
|
||||||
|
## Running an OCI Image
|
||||||
|
|
||||||
|
The OCI Image Format partner project is the [OCI Runtime Spec project](https://github.com/opencontainers/runtime-spec).
|
||||||
|
The Runtime Specification outlines how to run a "[filesystem bundle](https://github.com/opencontainers/runtime-spec/blob/master/bundle.md)" that is unpacked on disk.
|
||||||
|
At a high-level an OCI implementation would download an OCI Image then unpack that image into an OCI Runtime filesystem bundle.
|
||||||
|
At this point the OCI Runtime Bundle would be run by an OCI Runtime.
|
||||||
|
|
||||||
|
This entire workflow supports the UX that users have come to expect from container engines like Docker and rkt: primarily, the ability to run an image with no additional arguments:
|
||||||
|
|
||||||
|
* docker run example.com/org/app:v1.0.0
|
||||||
|
* rkt run example.com/org/app,version=v1.0.0
|
||||||
|
|
||||||
|
To support this UX the OCI Image Format contains sufficient information to launch the application on the target platform (e.g. command, arguments, environment variables, etc).
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
**Q: Why doesn't this project mention distribution?**
|
||||||
|
|
||||||
|
A: Distribution, for example using HTTP as both Docker v2.2 and AppC do today, is currently out of scope on the [OCI Scope Table](https://www.opencontainers.org/about/oci-scope-table).
|
||||||
|
There has been [some discussion on the TOB mailing list](https://groups.google.com/a/opencontainers.org/d/msg/tob/A3JnmI-D-6Y/tLuptPDHAgAJ) to make distribution an optional layer, but this topic is a work in progress.
|
||||||
|
|
||||||
|
**Q: What happens to AppC or Docker Image Formats?**
|
||||||
|
|
||||||
|
A: Existing formats can continue to be a proving ground for technologies, as needed.
|
||||||
|
The OCI Image Format project strives to provide a dependable open specification that can be shared between different tools and be evolved for years or decades of compatibility; as the deb and rpm format have.
|
||||||
|
|
||||||
|
Find more [FAQ on the OCI site](https://www.opencontainers.org/faq).
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
The [GitHub milestones](https://github.com/opencontainers/image-spec/milestones) lay out the path to the OCI v1.0.0 release in late 2016.
|
||||||
|
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Development happens on GitHub for the spec.
|
||||||
|
Issues are used for bugs and actionable items and longer discussions can happen on the [mailing list](#mailing-list).
|
||||||
|
|
||||||
|
The specification and code is licensed under the Apache 2.0 license found in the `LICENSE` file of this repository.
|
||||||
|
|
||||||
|
## Discuss your design
|
||||||
|
|
||||||
|
The project welcomes submissions, but please let everyone know what you are working on.
|
||||||
|
|
||||||
|
Before undertaking a nontrivial change to this specification, send mail to the [mailing list](#mailing-list) to discuss what you plan to do.
|
||||||
|
This gives everyone a chance to validate the design, helps prevent duplication of effort, and ensures that the idea fits.
|
||||||
|
It also guarantees that the design is sound before code is written; a GitHub pull-request is not the place for high-level discussions.
|
||||||
|
|
||||||
|
Typos and grammatical errors can go straight to a pull-request.
|
||||||
|
When in doubt, start on the [mailing-list](#mailing-list).
|
||||||
|
|
||||||
|
## Weekly Call
|
||||||
|
|
||||||
|
The contributors and maintainers of all OCI projects have a weekly meeting Wednesdays at 2:00 PM (USA Pacific).
|
||||||
|
Everyone is welcome to participate via [UberConference web][UberConference] or audio-only: +1-415-968-0849 (no PIN needed).
|
||||||
|
An initial agenda will be posted to the [mailing list](#mailing-list) earlier in the week, and everyone is welcome to propose additional topics or suggest other agenda alterations there.
|
||||||
|
Minutes are posted to the [mailing list](#mailing-list) and minutes from past calls are archived [here][minutes].
|
||||||
|
|
||||||
|
## Mailing List
|
||||||
|
|
||||||
|
You can subscribe and join the mailing list on [Google Groups](https://groups.google.com/a/opencontainers.org/forum/#!forum/dev).
|
||||||
|
|
||||||
|
## IRC
|
||||||
|
|
||||||
|
OCI discussion happens on #opencontainers on Freenode ([logs][irc-logs]).
|
||||||
|
|
||||||
|
## Markdown style
|
||||||
|
|
||||||
|
To keep consistency throughout the Markdown files in the Open Container spec all files should be formatted one sentence per line.
|
||||||
|
This fixes two things: it makes diffing easier with git and it resolves fights about line wrapping length.
|
||||||
|
For example, this paragraph will span three lines in the Markdown source.
|
||||||
|
|
||||||
|
## Git commit
|
||||||
|
|
||||||
|
### Sign your work
|
||||||
|
|
||||||
|
The sign-off is a simple line at the end of the explanation for the patch, which certifies that you wrote it or otherwise have the right to pass it on as an open-source patch.
|
||||||
|
The rules are pretty simple: if you can certify the below (from [developercertificate.org](http://developercertificate.org/)):
|
||||||
|
|
||||||
|
```
|
||||||
|
Developer Certificate of Origin
|
||||||
|
Version 1.1
|
||||||
|
|
||||||
|
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||||
|
660 York Street, Suite 102,
|
||||||
|
San Francisco, CA 94110 USA
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies of this
|
||||||
|
license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
Developer's Certificate of Origin 1.1
|
||||||
|
|
||||||
|
By making a contribution to this project, I certify that:
|
||||||
|
|
||||||
|
(a) The contribution was created in whole or in part by me and I
|
||||||
|
have the right to submit it under the open source license
|
||||||
|
indicated in the file; or
|
||||||
|
|
||||||
|
(b) The contribution is based upon previous work that, to the best
|
||||||
|
of my knowledge, is covered under an appropriate open source
|
||||||
|
license and I have the right under that license to submit that
|
||||||
|
work with modifications, whether created in whole or in part
|
||||||
|
by me, under the same open source license (unless I am
|
||||||
|
permitted to submit under a different license), as indicated
|
||||||
|
in the file; or
|
||||||
|
|
||||||
|
(c) The contribution was provided directly to me by some other
|
||||||
|
person who certified (a), (b) or (c) and I have not modified
|
||||||
|
it.
|
||||||
|
|
||||||
|
(d) I understand and agree that this project and the contribution
|
||||||
|
are public and that a record of the contribution (including all
|
||||||
|
personal information I submit with it, including my sign-off) is
|
||||||
|
maintained indefinitely and may be redistributed consistent with
|
||||||
|
this project or the open source license(s) involved.
|
||||||
|
```
|
||||||
|
|
||||||
|
then you just add a line to every git commit message:
|
||||||
|
|
||||||
|
Signed-off-by: Joe Smith <joe@gmail.com>
|
||||||
|
|
||||||
|
using your real name (sorry, no pseudonyms or anonymous contributions.)
|
||||||
|
|
||||||
|
You can add the sign off when creating the git commit via `git commit -s`.
|
||||||
|
|
||||||
|
### Commit Style
|
||||||
|
|
||||||
|
Simple house-keeping for clean git history.
|
||||||
|
Read more on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/) or the Discussion section of [`git-commit(1)`](http://git-scm.com/docs/git-commit).
|
||||||
|
|
||||||
|
1. Separate the subject from body with a blank line
|
||||||
|
2. Limit the subject line to 50 characters
|
||||||
|
3. Capitalize the subject line
|
||||||
|
4. Do not end the subject line with a period
|
||||||
|
5. Use the imperative mood in the subject line
|
||||||
|
6. Wrap the body at 72 characters
|
||||||
|
7. Use the body to explain what and why vs. how
|
||||||
|
* If there was important/useful/essential conversation or information, copy or include a reference
|
||||||
|
8. When possible, one keyword to scope the change in the subject (i.e. "README: ...", "runtime: ...")
|
||||||
|
|
||||||
|
|
||||||
|
[UberConference]: https://www.uberconference.com/opencontainers
|
||||||
|
[irc-logs]: http://ircbot.wl.linuxfoundation.org/eavesdrop/%23opencontainers/
|
||||||
|
[minutes]: http://ircbot.wl.linuxfoundation.org/meetings/opencontainers/
|
103
vendor/github.com/opencontainers/image-spec/specs-go/v1/config.go
generated
vendored
Normal file
103
vendor/github.com/opencontainers/image-spec/specs-go/v1/config.go
generated
vendored
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright 2016 The Linux Foundation
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
digest "github.com/opencontainers/go-digest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ImageConfig defines the execution parameters which should be used as a base when running a container using an image.
|
||||||
|
type ImageConfig struct {
|
||||||
|
// User defines the username or UID which the process in the container should run as.
|
||||||
|
User string `json:"User,omitempty"`
|
||||||
|
|
||||||
|
// ExposedPorts a set of ports to expose from a container running this image.
|
||||||
|
ExposedPorts map[string]struct{} `json:"ExposedPorts,omitempty"`
|
||||||
|
|
||||||
|
// Env is a list of environment variables to be used in a container.
|
||||||
|
Env []string `json:"Env,omitempty"`
|
||||||
|
|
||||||
|
// Entrypoint defines a list of arguments to use as the command to execute when the container starts.
|
||||||
|
Entrypoint []string `json:"Entrypoint,omitempty"`
|
||||||
|
|
||||||
|
// Cmd defines the default arguments to the entrypoint of the container.
|
||||||
|
Cmd []string `json:"Cmd,omitempty"`
|
||||||
|
|
||||||
|
// Volumes is a set of directories which should be created as data volumes in a container running this image.
|
||||||
|
Volumes map[string]struct{} `json:"Volumes,omitempty"`
|
||||||
|
|
||||||
|
// WorkingDir sets the current working directory of the entrypoint process in the container.
|
||||||
|
WorkingDir string `json:"WorkingDir,omitempty"`
|
||||||
|
|
||||||
|
// Labels contains arbitrary metadata for the container.
|
||||||
|
Labels map[string]string `json:"Labels,omitempty"`
|
||||||
|
|
||||||
|
// StopSignal contains the system call signal that will be sent to the container to exit.
|
||||||
|
StopSignal string `json:"StopSignal,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootFS describes a layer content addresses
|
||||||
|
type RootFS struct {
|
||||||
|
// Type is the type of the rootfs.
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// DiffIDs is an array of layer content hashes (DiffIDs), in order from bottom-most to top-most.
|
||||||
|
DiffIDs []digest.Digest `json:"diff_ids"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// History describes the history of a layer.
|
||||||
|
type History struct {
|
||||||
|
// Created is the combined date and time at which the layer was created, formatted as defined by RFC 3339, section 5.6.
|
||||||
|
Created *time.Time `json:"created,omitempty"`
|
||||||
|
|
||||||
|
// CreatedBy is the command which created the layer.
|
||||||
|
CreatedBy string `json:"created_by,omitempty"`
|
||||||
|
|
||||||
|
// Author is the author of the build point.
|
||||||
|
Author string `json:"author,omitempty"`
|
||||||
|
|
||||||
|
// Comment is a custom message set when creating the layer.
|
||||||
|
Comment string `json:"comment,omitempty"`
|
||||||
|
|
||||||
|
// EmptyLayer is used to mark if the history item created a filesystem diff.
|
||||||
|
EmptyLayer bool `json:"empty_layer,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image is the JSON structure which describes some basic information about the image.
|
||||||
|
// This provides the `application/vnd.oci.image.config.v1+json` mediatype when marshalled to JSON.
|
||||||
|
type Image struct {
|
||||||
|
// Created is the combined date and time at which the image was created, formatted as defined by RFC 3339, section 5.6.
|
||||||
|
Created *time.Time `json:"created,omitempty"`
|
||||||
|
|
||||||
|
// Author defines the name and/or email address of the person or entity which created and is responsible for maintaining the image.
|
||||||
|
Author string `json:"author,omitempty"`
|
||||||
|
|
||||||
|
// Architecture is the CPU architecture which the binaries in this image are built to run on.
|
||||||
|
Architecture string `json:"architecture"`
|
||||||
|
|
||||||
|
// OS is the name of the operating system which the image is built to run on.
|
||||||
|
OS string `json:"os"`
|
||||||
|
|
||||||
|
// Config defines the execution parameters which should be used as a base when running a container using the image.
|
||||||
|
Config ImageConfig `json:"config,omitempty"`
|
||||||
|
|
||||||
|
// RootFS references the layer content addresses used by the image.
|
||||||
|
RootFS RootFS `json:"rootfs"`
|
||||||
|
|
||||||
|
// History describes the history of each layer.
|
||||||
|
History []History `json:"history,omitempty"`
|
||||||
|
}
|
68
vendor/github.com/opencontainers/image-spec/specs-go/v1/descriptor.go
generated
vendored
Normal file
68
vendor/github.com/opencontainers/image-spec/specs-go/v1/descriptor.go
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// Copyright 2016 The Linux Foundation
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import digest "github.com/opencontainers/go-digest"
|
||||||
|
|
||||||
|
// Descriptor describes the disposition of targeted content.
|
||||||
|
// This structure provides `application/vnd.oci.descriptor.v1+json` mediatype
|
||||||
|
// when marshalled to JSON.
|
||||||
|
type Descriptor struct {
|
||||||
|
// MediaType is the media type of the object this schema refers to.
|
||||||
|
MediaType string `json:"mediaType,omitempty"`
|
||||||
|
|
||||||
|
// Digest is the digest of the targeted content.
|
||||||
|
Digest digest.Digest `json:"digest"`
|
||||||
|
|
||||||
|
// Size specifies the size in bytes of the blob.
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
|
||||||
|
// URLs specifies a list of URLs from which this object MAY be downloaded
|
||||||
|
URLs []string `json:"urls,omitempty"`
|
||||||
|
|
||||||
|
// Annotations contains arbitrary metadata relating to the targeted content.
|
||||||
|
Annotations map[string]string `json:"annotations,omitempty"`
|
||||||
|
|
||||||
|
// Platform describes the platform which the image in the manifest runs on.
|
||||||
|
//
|
||||||
|
// This should only be used when referring to a manifest.
|
||||||
|
Platform *Platform `json:"platform,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Platform describes the platform which the image in the manifest runs on.
|
||||||
|
type Platform struct {
|
||||||
|
// Architecture field specifies the CPU architecture, for example
|
||||||
|
// `amd64` or `ppc64`.
|
||||||
|
Architecture string `json:"architecture"`
|
||||||
|
|
||||||
|
// OS specifies the operating system, for example `linux` or `windows`.
|
||||||
|
OS string `json:"os"`
|
||||||
|
|
||||||
|
// OSVersion is an optional field specifying the operating system
|
||||||
|
// version, for example `10.0.10586`.
|
||||||
|
OSVersion string `json:"os.version,omitempty"`
|
||||||
|
|
||||||
|
// OSFeatures is an optional field specifying an array of strings,
|
||||||
|
// each listing a required OS feature (for example on Windows `win32k`).
|
||||||
|
OSFeatures []string `json:"os.features,omitempty"`
|
||||||
|
|
||||||
|
// Variant is an optional field specifying a variant of the CPU, for
|
||||||
|
// example `ppc64le` to specify a little-endian version of a PowerPC CPU.
|
||||||
|
Variant string `json:"variant,omitempty"`
|
||||||
|
|
||||||
|
// Features is an optional field specifying an array of strings, each
|
||||||
|
// listing a required CPU feature (for example `sse4` or `aes`).
|
||||||
|
Features []string `json:"features,omitempty"`
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
// Copyright 2016 The Linux Foundation
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import "github.com/opencontainers/image-spec/specs-go"
|
||||||
|
|
||||||
|
// Index references manifests for various platforms.
|
||||||
|
// This structure provides `application/vnd.oci.image.index.v1+json` mediatype when marshalled to JSON.
|
||||||
|
type Index struct {
|
||||||
|
specs.Versioned
|
||||||
|
|
||||||
|
// Manifests references platform specific manifests.
|
||||||
|
Manifests []Descriptor `json:"manifests"`
|
||||||
|
|
||||||
|
// Annotations contains arbitrary metadata for the image index.
|
||||||
|
Annotations map[string]string `json:"annotations,omitempty"`
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright 2016 The Linux Foundation
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ImageLayoutFile is the file name of oci image layout file
|
||||||
|
ImageLayoutFile = "oci-layout"
|
||||||
|
// ImageLayoutVersion is the version of ImageLayout
|
||||||
|
ImageLayoutVersion = "1.0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ImageLayout is the structure in the "oci-layout" file, found in the root
|
||||||
|
// of an OCI Image-layout directory.
|
||||||
|
type ImageLayout struct {
|
||||||
|
Version string `json:"imageLayoutVersion"`
|
||||||
|
}
|
32
vendor/github.com/opencontainers/image-spec/specs-go/v1/manifest.go
generated
vendored
Normal file
32
vendor/github.com/opencontainers/image-spec/specs-go/v1/manifest.go
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2016 The Linux Foundation
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import "github.com/opencontainers/image-spec/specs-go"
|
||||||
|
|
||||||
|
// Manifest provides `application/vnd.oci.image.manifest.v1+json` mediatype structure when marshalled to JSON.
|
||||||
|
type Manifest struct {
|
||||||
|
specs.Versioned
|
||||||
|
|
||||||
|
// Config references a configuration object for a container, by digest.
|
||||||
|
// The referenced configuration object is a JSON blob that the runtime uses to set up the container.
|
||||||
|
Config Descriptor `json:"config"`
|
||||||
|
|
||||||
|
// Layers is an indexed list of layers referenced by the manifest.
|
||||||
|
Layers []Descriptor `json:"layers"`
|
||||||
|
|
||||||
|
// Annotations contains arbitrary metadata for the image manifest.
|
||||||
|
Annotations map[string]string `json:"annotations,omitempty"`
|
||||||
|
}
|
48
vendor/github.com/opencontainers/image-spec/specs-go/v1/mediatype.go
generated
vendored
Normal file
48
vendor/github.com/opencontainers/image-spec/specs-go/v1/mediatype.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// Copyright 2016 The Linux Foundation
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MediaTypeDescriptor specifies the media type for a content descriptor.
|
||||||
|
MediaTypeDescriptor = "application/vnd.oci.descriptor.v1+json"
|
||||||
|
|
||||||
|
// MediaTypeLayoutHeader specifies the media type for the oci-layout.
|
||||||
|
MediaTypeLayoutHeader = "application/vnd.oci.layout.header.v1+json"
|
||||||
|
|
||||||
|
// MediaTypeImageManifest specifies the media type for an image manifest.
|
||||||
|
MediaTypeImageManifest = "application/vnd.oci.image.manifest.v1+json"
|
||||||
|
|
||||||
|
// MediaTypeImageIndex specifies the media type for an image index.
|
||||||
|
MediaTypeImageIndex = "application/vnd.oci.image.index.v1+json"
|
||||||
|
|
||||||
|
// MediaTypeImageLayer is the media type used for layers referenced by the manifest.
|
||||||
|
MediaTypeImageLayer = "application/vnd.oci.image.layer.v1.tar"
|
||||||
|
|
||||||
|
// MediaTypeImageLayerGzip is the media type used for gzipped layers
|
||||||
|
// referenced by the manifest.
|
||||||
|
MediaTypeImageLayerGzip = "application/vnd.oci.image.layer.v1.tar+gzip"
|
||||||
|
|
||||||
|
// MediaTypeImageLayerNonDistributable is the media type for layers referenced by
|
||||||
|
// the manifest but with distribution restrictions.
|
||||||
|
MediaTypeImageLayerNonDistributable = "application/vnd.oci.image.layer.nondistributable.v1.tar"
|
||||||
|
|
||||||
|
// MediaTypeImageLayerNonDistributableGzip is the media type for
|
||||||
|
// gzipped layers referenced by the manifest but with distribution
|
||||||
|
// restrictions.
|
||||||
|
MediaTypeImageLayerNonDistributableGzip = "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip"
|
||||||
|
|
||||||
|
// MediaTypeImageConfig specifies the media type for the image configuration.
|
||||||
|
MediaTypeImageConfig = "application/vnd.oci.image.config.v1+json"
|
||||||
|
)
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2016 The Linux Foundation
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package specs
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// VersionMajor is for an API incompatible changes
|
||||||
|
VersionMajor = 1
|
||||||
|
// VersionMinor is for functionality in a backwards-compatible manner
|
||||||
|
VersionMinor = 0
|
||||||
|
// VersionPatch is for backwards-compatible bug fixes
|
||||||
|
VersionPatch = 0
|
||||||
|
|
||||||
|
// VersionDev indicates development branch. Releases will be empty string.
|
||||||
|
VersionDev = "-rc5-dev"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version is the specification version that the package types support.
|
||||||
|
var Version = fmt.Sprintf("%d.%d.%d%s", VersionMajor, VersionMinor, VersionPatch, VersionDev)
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright 2016 The Linux Foundation
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package specs
|
||||||
|
|
||||||
|
// Versioned provides a struct with the manifest schemaVersion and mediaType.
|
||||||
|
// Incoming content with unknown schema version can be decoded against this
|
||||||
|
// struct to check the version.
|
||||||
|
type Versioned struct {
|
||||||
|
// SchemaVersion is the image manifest schema that this image follows
|
||||||
|
SchemaVersion int `json:"schemaVersion"`
|
||||||
|
}
|
Loading…
Reference in New Issue