Merge pull request #25962 from mrjana/net
Add support for docker run in swarm mode overlay
This commit is contained in:
commit
719a640743
227 changed files with 4249 additions and 8414 deletions
|
@ -23,6 +23,7 @@ type createOptions struct {
|
|||
labels []string
|
||||
internal bool
|
||||
ipv6 bool
|
||||
attachable bool
|
||||
|
||||
ipamDriver string
|
||||
ipamSubnet []string
|
||||
|
@ -55,6 +56,7 @@ func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
flags.StringSliceVar(&opts.labels, "label", []string{}, "Set metadata on a network")
|
||||
flags.BoolVar(&opts.internal, "internal", false, "Restrict external access to the network")
|
||||
flags.BoolVar(&opts.ipv6, "ipv6", false, "Enable IPv6 networking")
|
||||
flags.BoolVar(&opts.attachable, "attachable", false, "Enable manual container attachment")
|
||||
|
||||
flags.StringVar(&opts.ipamDriver, "ipam-driver", "default", "IP Address Management Driver")
|
||||
flags.StringSliceVar(&opts.ipamSubnet, "subnet", []string{}, "Subnet in CIDR format that represents a network segment")
|
||||
|
@ -87,6 +89,7 @@ func runCreate(dockerCli *client.DockerCli, opts createOptions) error {
|
|||
CheckDuplicate: true,
|
||||
Internal: opts.internal,
|
||||
EnableIPv6: opts.ipv6,
|
||||
Attachable: opts.attachable,
|
||||
Labels: runconfigopts.ConvertKVStringsToMap(opts.labels),
|
||||
}
|
||||
|
||||
|
|
|
@ -451,6 +451,7 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
|
|||
Mounts: opts.mounts.Value(),
|
||||
StopGracePeriod: opts.stopGrace.Value(),
|
||||
},
|
||||
Networks: convertNetworks(opts.networks),
|
||||
Resources: opts.resources.ToResourceRequirements(),
|
||||
RestartPolicy: opts.restartPolicy.ToRestartPolicy(),
|
||||
Placement: &swarm.Placement{
|
||||
|
@ -458,13 +459,13 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
|
|||
},
|
||||
LogDriver: opts.logDriver.toLogDriver(),
|
||||
},
|
||||
Mode: swarm.ServiceMode{},
|
||||
Networks: convertNetworks(opts.networks),
|
||||
Mode: swarm.ServiceMode{},
|
||||
UpdateConfig: &swarm.UpdateConfig{
|
||||
Parallelism: opts.update.parallelism,
|
||||
Delay: opts.update.delay,
|
||||
FailureAction: opts.update.onFailure,
|
||||
},
|
||||
Networks: convertNetworks(opts.networks),
|
||||
EndpointSpec: opts.endpoint.ToEndpointSpec(),
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package network
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
@ -11,7 +10,6 @@ import (
|
|||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/errors"
|
||||
"github.com/docker/libnetwork"
|
||||
)
|
||||
|
||||
|
@ -116,17 +114,7 @@ func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseW
|
|||
return err
|
||||
}
|
||||
|
||||
nw, err := n.backend.FindNetwork(vars["id"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if nw.Info().Dynamic() {
|
||||
err := fmt.Errorf("operation not supported for swarm scoped networks")
|
||||
return errors.NewRequestForbiddenError(err)
|
||||
}
|
||||
|
||||
return n.backend.ConnectContainerToNetwork(connect.Container, nw.Name(), connect.EndpointConfig)
|
||||
return n.backend.ConnectContainerToNetwork(connect.Container, vars["id"], connect.EndpointConfig)
|
||||
}
|
||||
|
||||
func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
|
@ -143,13 +131,6 @@ func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.Respon
|
|||
return err
|
||||
}
|
||||
|
||||
nw, _ := n.backend.FindNetwork(vars["id"])
|
||||
|
||||
if nw != nil && nw.Info().Dynamic() {
|
||||
err := fmt.Errorf("operation not supported for swarm scoped networks")
|
||||
return errors.NewRequestForbiddenError(err)
|
||||
}
|
||||
|
||||
return n.backend.DisconnectContainerFromNetwork(disconnect.Container, vars["id"], disconnect.Force)
|
||||
}
|
||||
|
||||
|
|
|
@ -700,7 +700,9 @@ func (container *Container) BuildEndpointInfo(n libnetwork.Network, ep libnetwor
|
|||
}
|
||||
|
||||
if _, ok := networkSettings.Networks[n.Name()]; !ok {
|
||||
networkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
|
||||
networkSettings.Networks[n.Name()] = &network.EndpointSettings{
|
||||
EndpointSettings: &networktypes.EndpointSettings{},
|
||||
}
|
||||
}
|
||||
networkSettings.Networks[n.Name()].NetworkID = n.ID()
|
||||
networkSettings.Networks[n.Name()].EndpointID = ep.ID()
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/Sirupsen/logrus"
|
||||
apitypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
types "github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/daemon/cluster/convert"
|
||||
executorpkg "github.com/docker/docker/daemon/cluster/executor"
|
||||
|
@ -126,6 +127,18 @@ type Cluster struct {
|
|||
stop bool
|
||||
err error
|
||||
cancelDelay func()
|
||||
attachers map[string]*attacher
|
||||
}
|
||||
|
||||
// attacher manages the in-memory attachment state of a container
|
||||
// attachment to a global scope network managed by swarm manager. It
|
||||
// helps in identifying the attachment ID via the taskID and the
|
||||
// corresponding attachment configuration obtained from the manager.
|
||||
type attacher struct {
|
||||
taskID string
|
||||
config *network.NetworkingConfig
|
||||
attachWaitCh chan *network.NetworkingConfig
|
||||
detachWaitCh chan struct{}
|
||||
}
|
||||
|
||||
type node struct {
|
||||
|
@ -154,6 +167,7 @@ func New(config Config) (*Cluster, error) {
|
|||
config: config,
|
||||
configEvent: make(chan struct{}, 10),
|
||||
runtimeRoot: config.RuntimeRoot,
|
||||
attachers: make(map[string]*attacher),
|
||||
}
|
||||
|
||||
st, err := c.loadState()
|
||||
|
@ -1212,6 +1226,120 @@ func (c *Cluster) GetNetworks() ([]apitypes.NetworkResource, error) {
|
|||
return networks, nil
|
||||
}
|
||||
|
||||
func attacherKey(target, containerID string) string {
|
||||
return containerID + ":" + target
|
||||
}
|
||||
|
||||
// UpdateAttachment signals the attachment config to the attachment
|
||||
// waiter who is trying to start or attach the container to the
|
||||
// network.
|
||||
func (c *Cluster) UpdateAttachment(target, containerID string, config *network.NetworkingConfig) error {
|
||||
c.RLock()
|
||||
attacher, ok := c.attachers[attacherKey(target, containerID)]
|
||||
c.RUnlock()
|
||||
if !ok || attacher == nil {
|
||||
return fmt.Errorf("could not find attacher for container %s to network %s", containerID, target)
|
||||
}
|
||||
|
||||
attacher.attachWaitCh <- config
|
||||
close(attacher.attachWaitCh)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForDetachment waits for the container to stop or detach from
|
||||
// the network.
|
||||
func (c *Cluster) WaitForDetachment(ctx context.Context, networkName, networkID, taskID, containerID string) error {
|
||||
c.RLock()
|
||||
attacher, ok := c.attachers[attacherKey(networkName, containerID)]
|
||||
if !ok {
|
||||
attacher, ok = c.attachers[attacherKey(networkID, containerID)]
|
||||
}
|
||||
if c.node == nil || c.node.Agent() == nil {
|
||||
c.RUnlock()
|
||||
return fmt.Errorf("invalid cluster node while waiting for detachment")
|
||||
}
|
||||
|
||||
agent := c.node.Agent()
|
||||
c.RUnlock()
|
||||
|
||||
if ok && attacher != nil && attacher.detachWaitCh != nil {
|
||||
select {
|
||||
case <-attacher.detachWaitCh:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return agent.ResourceAllocator().DetachNetwork(ctx, taskID)
|
||||
}
|
||||
|
||||
// AttachNetwork generates an attachment request towards the manager.
|
||||
func (c *Cluster) AttachNetwork(target string, containerID string, addresses []string) (*network.NetworkingConfig, error) {
|
||||
aKey := attacherKey(target, containerID)
|
||||
c.Lock()
|
||||
if c.node == nil || c.node.Agent() == nil {
|
||||
c.Unlock()
|
||||
return nil, fmt.Errorf("invalid cluster node while attaching to network")
|
||||
}
|
||||
if attacher, ok := c.attachers[aKey]; ok {
|
||||
c.Unlock()
|
||||
return attacher.config, nil
|
||||
}
|
||||
|
||||
agent := c.node.Agent()
|
||||
attachWaitCh := make(chan *network.NetworkingConfig)
|
||||
detachWaitCh := make(chan struct{})
|
||||
c.attachers[aKey] = &attacher{
|
||||
attachWaitCh: attachWaitCh,
|
||||
detachWaitCh: detachWaitCh,
|
||||
}
|
||||
c.Unlock()
|
||||
|
||||
ctx, cancel := c.getRequestContext()
|
||||
defer cancel()
|
||||
|
||||
taskID, err := agent.ResourceAllocator().AttachNetwork(ctx, containerID, target, addresses)
|
||||
if err != nil {
|
||||
c.Lock()
|
||||
delete(c.attachers, aKey)
|
||||
c.Unlock()
|
||||
return nil, fmt.Errorf("Could not attach to network %s: %v", target, err)
|
||||
}
|
||||
|
||||
logrus.Debugf("Successfully attached to network %s with tid %s", target, taskID)
|
||||
|
||||
var config *network.NetworkingConfig
|
||||
select {
|
||||
case config = <-attachWaitCh:
|
||||
case <-ctx.Done():
|
||||
return nil, fmt.Errorf("attaching to network failed, make sure your network options are correct and check manager logs: %v", ctx.Err())
|
||||
}
|
||||
|
||||
c.Lock()
|
||||
c.attachers[aKey].taskID = taskID
|
||||
c.attachers[aKey].config = config
|
||||
c.Unlock()
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// DetachNetwork unblocks the waiters waiting on WaitForDetachment so
|
||||
// that a request to detach can be generated towards the manager.
|
||||
func (c *Cluster) DetachNetwork(target string, containerID string) error {
|
||||
aKey := attacherKey(target, containerID)
|
||||
|
||||
c.Lock()
|
||||
attacher, ok := c.attachers[aKey]
|
||||
delete(c.attachers, aKey)
|
||||
c.Unlock()
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("could not find network attachment for container %s to network %s", containerID, target)
|
||||
}
|
||||
|
||||
close(attacher.detachWaitCh)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateNetwork creates a new cluster managed network.
|
||||
func (c *Cluster) CreateNetwork(s apitypes.NetworkCreateRequest) (string, error) {
|
||||
c.RLock()
|
||||
|
@ -1262,7 +1390,14 @@ func (c *Cluster) RemoveNetwork(input string) error {
|
|||
}
|
||||
|
||||
func (c *Cluster) populateNetworkID(ctx context.Context, client swarmapi.ControlClient, s *types.ServiceSpec) error {
|
||||
for i, n := range s.Networks {
|
||||
// Always prefer NetworkAttachmentConfigs from TaskTemplate
|
||||
// but fallback to service spec for backward compatibility
|
||||
networks := s.TaskTemplate.Networks
|
||||
if len(networks) == 0 {
|
||||
networks = s.Networks
|
||||
}
|
||||
|
||||
for i, n := range networks {
|
||||
apiNetwork, err := getNetwork(ctx, client, n.Target)
|
||||
if err != nil {
|
||||
if ln, _ := c.config.Backend.FindNetwork(n.Target); ln != nil && !ln.Info().Dynamic() {
|
||||
|
@ -1271,7 +1406,7 @@ func (c *Cluster) populateNetworkID(ctx context.Context, client swarmapi.Control
|
|||
}
|
||||
return err
|
||||
}
|
||||
s.Networks[i].Target = apiNetwork.ID
|
||||
networks[i].Target = apiNetwork.ID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ func networkFromGRPC(n *swarmapi.Network) types.Network {
|
|||
Spec: types.NetworkSpec{
|
||||
IPv6Enabled: n.Spec.Ipv6Enabled,
|
||||
Internal: n.Spec.Internal,
|
||||
Attachable: n.Spec.Attachable,
|
||||
IPAMOptions: ipamFromGRPC(n.Spec.IPAM),
|
||||
},
|
||||
IPAMOptions: ipamFromGRPC(n.IPAM),
|
||||
|
@ -155,6 +156,7 @@ func BasicNetworkFromGRPC(n swarmapi.Network) basictypes.NetworkResource {
|
|||
EnableIPv6: spec.Ipv6Enabled,
|
||||
IPAM: ipam,
|
||||
Internal: spec.Internal,
|
||||
Attachable: spec.Attachable,
|
||||
Labels: n.Spec.Annotations.Labels,
|
||||
}
|
||||
|
||||
|
@ -179,6 +181,7 @@ func BasicNetworkCreateToGRPC(create basictypes.NetworkCreateRequest) swarmapi.N
|
|||
},
|
||||
Ipv6Enabled: create.EnableIPv6,
|
||||
Internal: create.Internal,
|
||||
Attachable: create.Attachable,
|
||||
}
|
||||
if create.IPAM != nil {
|
||||
ns.IPAM = &swarmapi.IPAMOptions{
|
||||
|
|
|
@ -15,10 +15,16 @@ func ServiceFromGRPC(s swarmapi.Service) types.Service {
|
|||
spec := s.Spec
|
||||
containerConfig := spec.Task.Runtime.(*swarmapi.TaskSpec_Container).Container
|
||||
|
||||
networks := make([]types.NetworkAttachmentConfig, 0, len(spec.Networks))
|
||||
serviceNetworks := make([]types.NetworkAttachmentConfig, 0, len(spec.Networks))
|
||||
for _, n := range spec.Networks {
|
||||
networks = append(networks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
|
||||
serviceNetworks = append(serviceNetworks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
|
||||
}
|
||||
|
||||
taskNetworks := make([]types.NetworkAttachmentConfig, 0, len(spec.Task.Networks))
|
||||
for _, n := range spec.Task.Networks {
|
||||
taskNetworks = append(taskNetworks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
|
||||
}
|
||||
|
||||
service := types.Service{
|
||||
ID: s.ID,
|
||||
|
||||
|
@ -29,9 +35,10 @@ func ServiceFromGRPC(s swarmapi.Service) types.Service {
|
|||
RestartPolicy: restartPolicyFromGRPC(s.Spec.Task.Restart),
|
||||
Placement: placementFromGRPC(s.Spec.Task.Placement),
|
||||
LogDriver: driverFromGRPC(s.Spec.Task.LogDriver),
|
||||
Networks: taskNetworks,
|
||||
},
|
||||
|
||||
Networks: networks,
|
||||
Networks: serviceNetworks,
|
||||
EndpointSpec: endpointSpecFromGRPC(s.Spec.Endpoint),
|
||||
},
|
||||
Endpoint: endpointFromGRPC(s.Endpoint),
|
||||
|
@ -99,9 +106,14 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
|
|||
name = namesgenerator.GetRandomName(0)
|
||||
}
|
||||
|
||||
networks := make([]*swarmapi.ServiceSpec_NetworkAttachmentConfig, 0, len(s.Networks))
|
||||
serviceNetworks := make([]*swarmapi.NetworkAttachmentConfig, 0, len(s.Networks))
|
||||
for _, n := range s.Networks {
|
||||
networks = append(networks, &swarmapi.ServiceSpec_NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
|
||||
serviceNetworks = append(serviceNetworks, &swarmapi.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
|
||||
}
|
||||
|
||||
taskNetworks := make([]*swarmapi.NetworkAttachmentConfig, 0, len(s.TaskTemplate.Networks))
|
||||
for _, n := range s.TaskTemplate.Networks {
|
||||
taskNetworks = append(taskNetworks, &swarmapi.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
|
||||
}
|
||||
|
||||
spec := swarmapi.ServiceSpec{
|
||||
|
@ -112,8 +124,9 @@ func ServiceSpecToGRPC(s types.ServiceSpec) (swarmapi.ServiceSpec, error) {
|
|||
Task: swarmapi.TaskSpec{
|
||||
Resources: resourcesToGRPC(s.TaskTemplate.Resources),
|
||||
LogDriver: driverToGRPC(s.TaskTemplate.LogDriver),
|
||||
Networks: taskNetworks,
|
||||
},
|
||||
Networks: networks,
|
||||
Networks: serviceNetworks,
|
||||
}
|
||||
|
||||
containerSpec, err := containerToGRPC(s.TaskTemplate.ContainerSpec)
|
||||
|
|
|
@ -12,6 +12,11 @@ import (
|
|||
func TaskFromGRPC(t swarmapi.Task) types.Task {
|
||||
containerConfig := t.Spec.Runtime.(*swarmapi.TaskSpec_Container).Container
|
||||
containerStatus := t.Status.GetContainer()
|
||||
networks := make([]types.NetworkAttachmentConfig, 0, len(t.Spec.Networks))
|
||||
for _, n := range t.Spec.Networks {
|
||||
networks = append(networks, types.NetworkAttachmentConfig{Target: n.Target, Aliases: n.Aliases})
|
||||
}
|
||||
|
||||
task := types.Task{
|
||||
ID: t.ID,
|
||||
ServiceID: t.ServiceID,
|
||||
|
@ -23,6 +28,7 @@ func TaskFromGRPC(t swarmapi.Task) types.Task {
|
|||
RestartPolicy: restartPolicyFromGRPC(t.Spec.Restart),
|
||||
Placement: placementFromGRPC(t.Spec.Placement),
|
||||
LogDriver: driverFromGRPC(t.Spec.LogDriver),
|
||||
Networks: networks,
|
||||
},
|
||||
Status: types.TaskStatus{
|
||||
State: types.TaskState(strings.ToLower(t.Status.State.String())),
|
||||
|
|
|
@ -40,4 +40,6 @@ type Backend interface {
|
|||
IsSwarmCompatible() error
|
||||
SubscribeToEvents(since, until time.Time, filter filters.Args) ([]events.Message, chan interface{})
|
||||
UnsubscribeFromEvents(listener chan interface{})
|
||||
UpdateAttachment(string, string, string, *network.NetworkingConfig) error
|
||||
WaitForDetachment(context.Context, string, string, string, string) error
|
||||
}
|
||||
|
|
|
@ -144,6 +144,44 @@ func (c *containerAdapter) removeNetworks(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *containerAdapter) networkAttach(ctx context.Context) error {
|
||||
config := c.container.createNetworkingConfig()
|
||||
|
||||
var (
|
||||
networkName string
|
||||
networkID string
|
||||
)
|
||||
|
||||
if config != nil {
|
||||
for n, epConfig := range config.EndpointsConfig {
|
||||
networkName = n
|
||||
networkID = epConfig.NetworkID
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return c.backend.UpdateAttachment(networkName, networkID, c.container.id(), config)
|
||||
}
|
||||
|
||||
func (c *containerAdapter) waitForDetach(ctx context.Context) error {
|
||||
config := c.container.createNetworkingConfig()
|
||||
|
||||
var (
|
||||
networkName string
|
||||
networkID string
|
||||
)
|
||||
|
||||
if config != nil {
|
||||
for n, epConfig := range config.EndpointsConfig {
|
||||
networkName = n
|
||||
networkID = epConfig.NetworkID
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return c.backend.WaitForDetachment(ctx, networkName, networkID, c.container.taskID(), c.container.id())
|
||||
}
|
||||
|
||||
func (c *containerAdapter) create(ctx context.Context) error {
|
||||
var cr types.ContainerCreateResponse
|
||||
var err error
|
||||
|
@ -233,7 +271,7 @@ func (c *containerAdapter) events(ctx context.Context) <-chan events.Message {
|
|||
}
|
||||
|
||||
func (c *containerAdapter) wait(ctx context.Context) error {
|
||||
return c.backend.ContainerWaitWithContext(ctx, c.container.name())
|
||||
return c.backend.ContainerWaitWithContext(ctx, c.container.nameOrID())
|
||||
}
|
||||
|
||||
func (c *containerAdapter) shutdown(ctx context.Context) error {
|
||||
|
|
80
daemon/cluster/executor/container/attachment.go
Normal file
80
daemon/cluster/executor/container/attachment.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
executorpkg "github.com/docker/docker/daemon/cluster/executor"
|
||||
"github.com/docker/swarmkit/api"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// networkAttacherController implements agent.Controller against docker's API.
|
||||
//
|
||||
// networkAttacherController manages the lifecycle of network
|
||||
// attachment of a docker unmanaged container managed as a task from
|
||||
// agent point of view. It provides network attachment information to
|
||||
// the unmanaged container for it to attach to the network and run.
|
||||
type networkAttacherController struct {
|
||||
backend executorpkg.Backend
|
||||
task *api.Task
|
||||
adapter *containerAdapter
|
||||
closed chan struct{}
|
||||
}
|
||||
|
||||
func newNetworkAttacherController(b executorpkg.Backend, task *api.Task) (*networkAttacherController, error) {
|
||||
adapter, err := newContainerAdapter(b, task)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &networkAttacherController{
|
||||
backend: b,
|
||||
task: task,
|
||||
adapter: adapter,
|
||||
closed: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (nc *networkAttacherController) Update(ctx context.Context, t *api.Task) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nc *networkAttacherController) Prepare(ctx context.Context) error {
|
||||
// Make sure all the networks that the task needs are created.
|
||||
if err := nc.adapter.createNetworks(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nc *networkAttacherController) Start(ctx context.Context) error {
|
||||
return nc.adapter.networkAttach(ctx)
|
||||
}
|
||||
|
||||
func (nc *networkAttacherController) Wait(pctx context.Context) error {
|
||||
ctx, cancel := context.WithCancel(pctx)
|
||||
defer cancel()
|
||||
|
||||
return nc.adapter.waitForDetach(ctx)
|
||||
}
|
||||
|
||||
func (nc *networkAttacherController) Shutdown(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nc *networkAttacherController) Terminate(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nc *networkAttacherController) Remove(ctx context.Context) error {
|
||||
// Try removing the network referenced in this task in case this
|
||||
// task is the last one referencing it
|
||||
if err := nc.adapter.removeNetworks(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nc *networkAttacherController) Close() error {
|
||||
return nil
|
||||
}
|
|
@ -44,17 +44,19 @@ func newContainerConfig(t *api.Task) (*containerConfig, error) {
|
|||
}
|
||||
|
||||
func (c *containerConfig) setTask(t *api.Task) error {
|
||||
container := t.Spec.GetContainer()
|
||||
if container == nil {
|
||||
if t.Spec.GetContainer() == nil && t.Spec.GetAttachment() == nil {
|
||||
return exec.ErrRuntimeUnsupported
|
||||
}
|
||||
|
||||
if container.Image == "" {
|
||||
return ErrImageRequired
|
||||
}
|
||||
container := t.Spec.GetContainer()
|
||||
if container != nil {
|
||||
if container.Image == "" {
|
||||
return ErrImageRequired
|
||||
}
|
||||
|
||||
if err := validateMounts(container.Mounts); err != nil {
|
||||
return err
|
||||
if err := validateMounts(container.Mounts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// index the networks by name
|
||||
|
@ -67,6 +69,19 @@ func (c *containerConfig) setTask(t *api.Task) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *containerConfig) id() string {
|
||||
attachment := c.task.Spec.GetAttachment()
|
||||
if attachment == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return attachment.ContainerID
|
||||
}
|
||||
|
||||
func (c *containerConfig) taskID() string {
|
||||
return c.task.ID
|
||||
}
|
||||
|
||||
func (c *containerConfig) endpoint() *api.Endpoint {
|
||||
return c.task.Endpoint
|
||||
}
|
||||
|
@ -75,6 +90,14 @@ func (c *containerConfig) spec() *api.ContainerSpec {
|
|||
return c.task.Spec.GetContainer()
|
||||
}
|
||||
|
||||
func (c *containerConfig) nameOrID() string {
|
||||
if c.task.Spec.GetContainer() != nil {
|
||||
return c.name()
|
||||
}
|
||||
|
||||
return c.id()
|
||||
}
|
||||
|
||||
func (c *containerConfig) name() string {
|
||||
if c.task.Annotations.Name != "" {
|
||||
// if set, use the container Annotations.Name field, set in the orchestrator.
|
||||
|
@ -342,7 +365,7 @@ func (c *containerConfig) resources() enginecontainer.Resources {
|
|||
// Docker daemon supports just 1 network during container create.
|
||||
func (c *containerConfig) createNetworkingConfig() *network.NetworkingConfig {
|
||||
var networks []*api.NetworkAttachment
|
||||
if c.task.Spec.GetContainer() != nil {
|
||||
if c.task.Spec.GetContainer() != nil || c.task.Spec.GetAttachment() != nil {
|
||||
networks = c.task.Networks
|
||||
}
|
||||
|
||||
|
@ -392,6 +415,7 @@ func getEndpointConfig(na *api.NetworkAttachment) *network.EndpointSettings {
|
|||
}
|
||||
|
||||
return &network.EndpointSettings{
|
||||
NetworkID: na.Network.ID,
|
||||
IPAMConfig: &network.EndpointIPAMConfig{
|
||||
IPv4Address: ipv4,
|
||||
IPv6Address: ipv6,
|
||||
|
|
|
@ -121,6 +121,10 @@ func (e *executor) Configure(ctx context.Context, node *api.Node) error {
|
|||
|
||||
// Controller returns a docker container runner.
|
||||
func (e *executor) Controller(t *api.Task) (exec.Controller, error) {
|
||||
if t.Spec.GetAttachment() != nil {
|
||||
return newNetworkAttacherController(e.backend, t)
|
||||
}
|
||||
|
||||
ctlr, err := newController(e.backend, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -178,7 +178,7 @@ func (daemon *Daemon) buildSandboxOptions(container *container.Container) ([]lib
|
|||
// return if this call to build join options is not for default bridge network
|
||||
// Legacy Link is only supported by docker run --link
|
||||
bridgeSettings, ok := container.NetworkSettings.Networks[defaultNetName]
|
||||
if !ok {
|
||||
if !ok || bridgeSettings.EndpointSettings == nil {
|
||||
return sboxOptions, nil
|
||||
}
|
||||
|
||||
|
@ -238,9 +238,9 @@ func (daemon *Daemon) buildSandboxOptions(container *container.Container) ([]lib
|
|||
return sboxOptions, nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) updateNetworkSettings(container *container.Container, n libnetwork.Network) error {
|
||||
func (daemon *Daemon) updateNetworkSettings(container *container.Container, n libnetwork.Network, endpointConfig *networktypes.EndpointSettings) error {
|
||||
if container.NetworkSettings == nil {
|
||||
container.NetworkSettings = &network.Settings{Networks: make(map[string]*networktypes.EndpointSettings)}
|
||||
container.NetworkSettings = &network.Settings{Networks: make(map[string]*network.EndpointSettings)}
|
||||
}
|
||||
|
||||
if !container.HostConfig.NetworkMode.IsHost() && containertypes.NetworkMode(n.Type()).IsHost() {
|
||||
|
@ -268,7 +268,9 @@ func (daemon *Daemon) updateNetworkSettings(container *container.Container, n li
|
|||
}
|
||||
|
||||
if _, ok := container.NetworkSettings.Networks[n.Name()]; !ok {
|
||||
container.NetworkSettings.Networks[n.Name()] = new(networktypes.EndpointSettings)
|
||||
container.NetworkSettings.Networks[n.Name()] = &network.EndpointSettings{
|
||||
EndpointSettings: endpointConfig,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -331,12 +333,63 @@ func errClusterNetworkOnRun(n string) error {
|
|||
return fmt.Errorf("swarm-scoped network (%s) is not compatible with `docker create` or `docker run`. This network can only be used by a docker service", n)
|
||||
}
|
||||
|
||||
func (daemon *Daemon) findAndAttachNetwork(container *container.Container, idOrName string, epConfig *networktypes.EndpointSettings) (libnetwork.Network, *networktypes.NetworkingConfig, error) {
|
||||
n, err := daemon.FindNetwork(idOrName)
|
||||
if err != nil {
|
||||
// We should always be able to find the network for a
|
||||
// managed container.
|
||||
if container.Managed {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a network and if it is not dynamically created
|
||||
// we should never attempt to attach to that network here.
|
||||
if n != nil {
|
||||
if container.Managed || !n.Info().Dynamic() {
|
||||
return n, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
var addresses []string
|
||||
if epConfig != nil && epConfig.IPAMConfig != nil {
|
||||
if epConfig.IPAMConfig.IPv4Address != "" {
|
||||
addresses = append(addresses, epConfig.IPAMConfig.IPv4Address)
|
||||
}
|
||||
|
||||
if epConfig.IPAMConfig.IPv6Address != "" {
|
||||
addresses = append(addresses, epConfig.IPAMConfig.IPv6Address)
|
||||
}
|
||||
}
|
||||
|
||||
// In all other cases, attempt to attach to the network to
|
||||
// trigger attachment in the swarm cluster manager.
|
||||
var config *networktypes.NetworkingConfig
|
||||
if daemon.clusterProvider != nil {
|
||||
var err error
|
||||
config, err = daemon.clusterProvider.AttachNetwork(idOrName, container.ID, addresses)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
n, err = daemon.FindNetwork(idOrName)
|
||||
if err != nil {
|
||||
if daemon.clusterProvider != nil {
|
||||
if err := daemon.clusterProvider.DetachNetwork(idOrName, container.ID); err != nil {
|
||||
logrus.Warnf("Could not rollback attachment for container %s to network %s: %v", container.ID, idOrName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return n, config, nil
|
||||
}
|
||||
|
||||
// updateContainerNetworkSettings update the network settings
|
||||
func (daemon *Daemon) updateContainerNetworkSettings(container *container.Container, endpointsConfig map[string]*networktypes.EndpointSettings) error {
|
||||
var (
|
||||
n libnetwork.Network
|
||||
err error
|
||||
)
|
||||
var n libnetwork.Network
|
||||
|
||||
mode := container.HostConfig.NetworkMode
|
||||
if container.Config.NetworkDisabled || mode.IsContainer() {
|
||||
|
@ -347,26 +400,48 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
|
|||
if mode.IsDefault() {
|
||||
networkName = daemon.netController.Config().Daemon.DefaultNetwork
|
||||
}
|
||||
|
||||
if mode.IsUserDefined() {
|
||||
var err error
|
||||
|
||||
n, err = daemon.FindNetwork(networkName)
|
||||
if err != nil {
|
||||
return err
|
||||
if err == nil {
|
||||
networkName = n.Name()
|
||||
}
|
||||
if !container.Managed && n.Info().Dynamic() {
|
||||
return errClusterNetworkOnRun(networkName)
|
||||
}
|
||||
networkName = n.Name()
|
||||
}
|
||||
|
||||
if container.NetworkSettings == nil {
|
||||
container.NetworkSettings = &network.Settings{}
|
||||
}
|
||||
|
||||
if len(endpointsConfig) > 0 {
|
||||
container.NetworkSettings.Networks = endpointsConfig
|
||||
if container.NetworkSettings.Networks == nil {
|
||||
container.NetworkSettings.Networks = make(map[string]*network.EndpointSettings)
|
||||
}
|
||||
|
||||
for name, epConfig := range endpointsConfig {
|
||||
container.NetworkSettings.Networks[name] = &network.EndpointSettings{
|
||||
EndpointSettings: epConfig,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if container.NetworkSettings.Networks == nil {
|
||||
container.NetworkSettings.Networks = make(map[string]*networktypes.EndpointSettings)
|
||||
container.NetworkSettings.Networks[networkName] = new(networktypes.EndpointSettings)
|
||||
container.NetworkSettings.Networks = make(map[string]*network.EndpointSettings)
|
||||
container.NetworkSettings.Networks[networkName] = &network.EndpointSettings{
|
||||
EndpointSettings: &networktypes.EndpointSettings{},
|
||||
}
|
||||
}
|
||||
|
||||
// Convert any settings added by client in default name to
|
||||
// engine's default network name key
|
||||
if mode.IsDefault() {
|
||||
if nConf, ok := container.NetworkSettings.Networks[mode.NetworkName()]; ok {
|
||||
container.NetworkSettings.Networks[networkName] = nConf
|
||||
delete(container.NetworkSettings.Networks, mode.NetworkName())
|
||||
}
|
||||
}
|
||||
|
||||
if !mode.IsUserDefined() {
|
||||
return nil
|
||||
}
|
||||
|
@ -374,10 +449,13 @@ func (daemon *Daemon) updateContainerNetworkSettings(container *container.Contai
|
|||
if _, ok := container.NetworkSettings.Networks[networkName]; ok {
|
||||
return nil
|
||||
}
|
||||
if nwConfig, ok := container.NetworkSettings.Networks[n.ID()]; ok {
|
||||
container.NetworkSettings.Networks[networkName] = nwConfig
|
||||
delete(container.NetworkSettings.Networks, n.ID())
|
||||
return nil
|
||||
|
||||
if n != nil {
|
||||
if nwConfig, ok := container.NetworkSettings.Networks[n.ID()]; ok {
|
||||
container.NetworkSettings.Networks[networkName] = nwConfig
|
||||
delete(container.NetworkSettings.Networks, n.ID())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -414,16 +492,27 @@ func (daemon *Daemon) allocateNetwork(container *container.Container) error {
|
|||
// on first network connecting.
|
||||
defaultNetName := runconfig.DefaultDaemonNetworkMode().NetworkName()
|
||||
if nConf, ok := container.NetworkSettings.Networks[defaultNetName]; ok {
|
||||
if err := daemon.connectToNetwork(container, defaultNetName, nConf, updateSettings); err != nil {
|
||||
if err := daemon.connectToNetwork(container, defaultNetName, nConf.EndpointSettings, updateSettings); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
for n, nConf := range container.NetworkSettings.Networks {
|
||||
var (
|
||||
networks []string
|
||||
epConfigs []*network.EndpointSettings
|
||||
)
|
||||
|
||||
for n, epConf := range container.NetworkSettings.Networks {
|
||||
if n == defaultNetName {
|
||||
continue
|
||||
}
|
||||
if err := daemon.connectToNetwork(container, n, nConf, updateSettings); err != nil {
|
||||
|
||||
networks = append(networks, n)
|
||||
epConfigs = append(epConfigs, epConf)
|
||||
}
|
||||
|
||||
for i, epConf := range epConfigs {
|
||||
if err := daemon.connectToNetwork(container, networks[i], epConf.EndpointSettings, updateSettings); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -488,7 +577,7 @@ func validateNetworkingConfig(n libnetwork.Network, epConfig *networktypes.Endpo
|
|||
}
|
||||
|
||||
// cleanOperationalData resets the operational data from the passed endpoint settings
|
||||
func cleanOperationalData(es *networktypes.EndpointSettings) {
|
||||
func cleanOperationalData(es *network.EndpointSettings) {
|
||||
es.EndpointID = ""
|
||||
es.Gateway = ""
|
||||
es.IPAddress = ""
|
||||
|
@ -497,25 +586,18 @@ func cleanOperationalData(es *networktypes.EndpointSettings) {
|
|||
es.GlobalIPv6Address = ""
|
||||
es.GlobalIPv6PrefixLen = 0
|
||||
es.MacAddress = ""
|
||||
if es.IPAMOperational {
|
||||
es.IPAMConfig = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (daemon *Daemon) updateNetworkConfig(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (libnetwork.Network, error) {
|
||||
if container.HostConfig.NetworkMode.IsContainer() {
|
||||
return nil, runconfig.ErrConflictSharedNetwork
|
||||
}
|
||||
|
||||
if containertypes.NetworkMode(idOrName).IsBridge() &&
|
||||
daemon.configStore.DisableBridge {
|
||||
container.Config.NetworkDisabled = true
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !containertypes.NetworkMode(idOrName).IsUserDefined() {
|
||||
func (daemon *Daemon) updateNetworkConfig(container *container.Container, n libnetwork.Network, endpointConfig *networktypes.EndpointSettings, updateSettings bool) error {
|
||||
if !containertypes.NetworkMode(n.Name()).IsUserDefined() {
|
||||
if hasUserDefinedIPAddress(endpointConfig) && !enableIPOnPredefinedNetwork() {
|
||||
return nil, runconfig.ErrUnsupportedNetworkAndIP
|
||||
return runconfig.ErrUnsupportedNetworkAndIP
|
||||
}
|
||||
if endpointConfig != nil && len(endpointConfig.Aliases) > 0 {
|
||||
return nil, runconfig.ErrUnsupportedNetworkAndAlias
|
||||
return runconfig.ErrUnsupportedNetworkAndAlias
|
||||
}
|
||||
} else {
|
||||
addShortID := true
|
||||
|
@ -531,28 +613,34 @@ func (daemon *Daemon) updateNetworkConfig(container *container.Container, idOrNa
|
|||
}
|
||||
}
|
||||
|
||||
n, err := daemon.FindNetwork(idOrName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := validateNetworkingConfig(n, endpointConfig); err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
if updateSettings {
|
||||
if err := daemon.updateNetworkSettings(container, n); err != nil {
|
||||
return nil, err
|
||||
if err := daemon.updateNetworkSettings(container, n, endpointConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName string, endpointConfig *networktypes.EndpointSettings, updateSettings bool) (err error) {
|
||||
if container.HostConfig.NetworkMode.IsContainer() {
|
||||
return runconfig.ErrConflictSharedNetwork
|
||||
}
|
||||
|
||||
if containertypes.NetworkMode(idOrName).IsBridge() &&
|
||||
daemon.configStore.DisableBridge {
|
||||
container.Config.NetworkDisabled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
if endpointConfig == nil {
|
||||
endpointConfig = &networktypes.EndpointSettings{}
|
||||
}
|
||||
n, err := daemon.updateNetworkConfig(container, idOrName, endpointConfig, updateSettings)
|
||||
|
||||
n, config, err := daemon.findAndAttachNetwork(container, idOrName, endpointConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -560,6 +648,25 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
|
|||
return nil
|
||||
}
|
||||
|
||||
var operIPAM bool
|
||||
if config != nil {
|
||||
if epConfig, ok := config.EndpointsConfig[n.Name()]; ok {
|
||||
if endpointConfig.IPAMConfig == nil ||
|
||||
(endpointConfig.IPAMConfig.IPv4Address == "" &&
|
||||
endpointConfig.IPAMConfig.IPv6Address == "" &&
|
||||
len(endpointConfig.IPAMConfig.LinkLocalIPs) == 0) {
|
||||
operIPAM = true
|
||||
}
|
||||
|
||||
endpointConfig = epConfig
|
||||
}
|
||||
}
|
||||
|
||||
err = daemon.updateNetworkConfig(container, n, endpointConfig, updateSettings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
controller := daemon.netController
|
||||
|
||||
sb := daemon.getNetworkSandbox(container)
|
||||
|
@ -580,7 +687,13 @@ func (daemon *Daemon) connectToNetwork(container *container.Container, idOrName
|
|||
}
|
||||
}
|
||||
}()
|
||||
container.NetworkSettings.Networks[n.Name()] = endpointConfig
|
||||
container.NetworkSettings.Networks[n.Name()] = &network.EndpointSettings{
|
||||
EndpointSettings: endpointConfig,
|
||||
IPAMOperational: operIPAM,
|
||||
}
|
||||
if _, ok := container.NetworkSettings.Networks[n.ID()]; ok {
|
||||
delete(container.NetworkSettings.Networks, n.ID())
|
||||
}
|
||||
|
||||
if err := daemon.updateEndpointNetworkSettings(container, n, ep); err != nil {
|
||||
return err
|
||||
|
@ -632,7 +745,7 @@ func (daemon *Daemon) ForceEndpointDelete(name string, networkName string) error
|
|||
return ep.Delete(true)
|
||||
}
|
||||
|
||||
func disconnectFromNetwork(container *container.Container, n libnetwork.Network, force bool) error {
|
||||
func (daemon *Daemon) disconnectFromNetwork(container *container.Container, n libnetwork.Network, force bool) error {
|
||||
var (
|
||||
ep libnetwork.Endpoint
|
||||
sbox libnetwork.Sandbox
|
||||
|
@ -678,6 +791,13 @@ func disconnectFromNetwork(container *container.Container, n libnetwork.Network,
|
|||
}
|
||||
|
||||
delete(container.NetworkSettings.Networks, n.Name())
|
||||
|
||||
if daemon.clusterProvider != nil && n.Info().Dynamic() && !container.Managed {
|
||||
if err := daemon.clusterProvider.DetachNetwork(n.Name(), container.ID); err != nil {
|
||||
logrus.Warnf("error detaching from network %s: %v", n, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -751,6 +871,11 @@ func (daemon *Daemon) releaseNetwork(container *container.Container) {
|
|||
if nw, err := daemon.FindNetwork(n); err == nil {
|
||||
networks = append(networks, nw)
|
||||
}
|
||||
|
||||
if epSettings.EndpointSettings == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
cleanOperationalData(epSettings)
|
||||
}
|
||||
|
||||
|
@ -765,6 +890,12 @@ func (daemon *Daemon) releaseNetwork(container *container.Container) {
|
|||
}
|
||||
|
||||
for _, nw := range networks {
|
||||
if daemon.clusterProvider != nil && nw.Info().Dynamic() && !container.Managed {
|
||||
if err := daemon.clusterProvider.DetachNetwork(nw.Name(), container.ID); err != nil {
|
||||
logrus.Warnf("error detaching from network %s: %v", nw.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
attributes := map[string]string{
|
||||
"container": container.ID,
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
networktypes "github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/container"
|
||||
"github.com/docker/docker/daemon/links"
|
||||
"github.com/docker/docker/daemon/network"
|
||||
"github.com/docker/docker/pkg/fileutils"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
|
@ -35,7 +36,7 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s
|
|||
children := daemon.children(container)
|
||||
|
||||
bridgeSettings := container.NetworkSettings.Networks[runconfig.DefaultDaemonNetworkMode().NetworkName()]
|
||||
if bridgeSettings == nil {
|
||||
if bridgeSettings == nil || bridgeSettings.EndpointSettings == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@ -45,7 +46,7 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s
|
|||
}
|
||||
|
||||
childBridgeSettings := child.NetworkSettings.Networks[runconfig.DefaultDaemonNetworkMode().NetworkName()]
|
||||
if childBridgeSettings == nil {
|
||||
if childBridgeSettings == nil || childBridgeSettings.EndpointSettings == nil {
|
||||
return nil, fmt.Errorf("container %s not attached to default bridge network", child.ID)
|
||||
}
|
||||
|
||||
|
@ -107,10 +108,17 @@ func (daemon *Daemon) ConnectToNetwork(container *container.Container, idOrName
|
|||
if container.RemovalInProgress || container.Dead {
|
||||
return errRemovalContainer(container.ID)
|
||||
}
|
||||
if _, err := daemon.updateNetworkConfig(container, idOrName, endpointConfig, true); err != nil {
|
||||
return err
|
||||
|
||||
n, err := daemon.FindNetwork(idOrName)
|
||||
if err == nil && n != nil {
|
||||
if err := daemon.updateNetworkConfig(container, n, endpointConfig, true); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
container.NetworkSettings.Networks[idOrName] = &network.EndpointSettings{
|
||||
EndpointSettings: endpointConfig,
|
||||
}
|
||||
}
|
||||
container.NetworkSettings.Networks[idOrName] = endpointConfig
|
||||
} else {
|
||||
if err := daemon.connectToNetwork(container, idOrName, endpointConfig, true); err != nil {
|
||||
return err
|
||||
|
@ -143,7 +151,7 @@ func (daemon *Daemon) DisconnectFromNetwork(container *container.Container, netw
|
|||
return runconfig.ErrConflictHostNetwork
|
||||
}
|
||||
|
||||
if err := disconnectFromNetwork(container, n, false); err != nil {
|
||||
if err := daemon.disconnectFromNetwork(container, n, false); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -252,7 +252,7 @@ func (daemon *Daemon) verifyNetworkingConfig(nwConfig *networktypes.NetworkingCo
|
|||
}
|
||||
if len(nwConfig.EndpointsConfig) == 1 {
|
||||
for _, v := range nwConfig.EndpointsConfig {
|
||||
if v.IPAMConfig != nil {
|
||||
if v != nil && v.IPAMConfig != nil {
|
||||
if v.IPAMConfig.IPv4Address != "" && net.ParseIP(v.IPAMConfig.IPv4Address).To4() == nil {
|
||||
return errors.NewBadRequestError(fmt.Errorf("invalid IPv4 address: %s", v.IPAMConfig.IPv4Address))
|
||||
}
|
||||
|
|
|
@ -42,6 +42,13 @@ func (daemon *Daemon) ContainerInspectCurrent(name string, size bool) (*types.Co
|
|||
return nil, err
|
||||
}
|
||||
|
||||
apiNetworks := make(map[string]*networktypes.EndpointSettings)
|
||||
for name, epConf := range container.NetworkSettings.Networks {
|
||||
if epConf.EndpointSettings != nil {
|
||||
apiNetworks[name] = epConf.EndpointSettings
|
||||
}
|
||||
}
|
||||
|
||||
mountPoints := addMountPoints(container)
|
||||
networkSettings := &types.NetworkSettings{
|
||||
NetworkSettingsBase: types.NetworkSettingsBase{
|
||||
|
@ -56,7 +63,7 @@ func (daemon *Daemon) ContainerInspectCurrent(name string, size bool) (*types.Co
|
|||
SecondaryIPv6Addresses: container.NetworkSettings.SecondaryIPv6Addresses,
|
||||
},
|
||||
DefaultNetworkSettings: daemon.getDefaultNetworkSettings(container.NetworkSettings.Networks),
|
||||
Networks: container.NetworkSettings.Networks,
|
||||
Networks: apiNetworks,
|
||||
}
|
||||
|
||||
return &types.ContainerJSON{
|
||||
|
@ -236,10 +243,10 @@ func (daemon *Daemon) getBackwardsCompatibleNetworkSettings(settings *network.Se
|
|||
|
||||
// getDefaultNetworkSettings creates the deprecated structure that holds the information
|
||||
// about the bridge network for a container.
|
||||
func (daemon *Daemon) getDefaultNetworkSettings(networks map[string]*networktypes.EndpointSettings) types.DefaultNetworkSettings {
|
||||
func (daemon *Daemon) getDefaultNetworkSettings(networks map[string]*network.EndpointSettings) types.DefaultNetworkSettings {
|
||||
var settings types.DefaultNetworkSettings
|
||||
|
||||
if defaultNetwork, ok := networks["bridge"]; ok {
|
||||
if defaultNetwork, ok := networks["bridge"]; ok && defaultNetwork.EndpointSettings != nil {
|
||||
settings.EndpointID = defaultNetwork.EndpointID
|
||||
settings.Gateway = defaultNetwork.Gateway
|
||||
settings.GlobalIPv6Address = defaultNetwork.GlobalIPv6Address
|
||||
|
|
|
@ -400,6 +400,9 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
|
|||
return networkExist
|
||||
}
|
||||
for _, nw := range container.NetworkSettings.Networks {
|
||||
if nw.EndpointSettings == nil {
|
||||
continue
|
||||
}
|
||||
if nw.NetworkID == value {
|
||||
return networkExist
|
||||
}
|
||||
|
@ -460,7 +463,7 @@ func (daemon *Daemon) transformContainer(container *container.Container, ctx *li
|
|||
// copy networks to avoid races
|
||||
networks := make(map[string]*networktypes.EndpointSettings)
|
||||
for name, network := range container.NetworkSettings.Networks {
|
||||
if network == nil {
|
||||
if network == nil || network.EndpointSettings == nil {
|
||||
continue
|
||||
}
|
||||
networks[name] = &networktypes.EndpointSettings{
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/libnetwork"
|
||||
networktypes "github.com/docker/libnetwork/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// NetworkControllerEnabled checks if the networking stack is enabled.
|
||||
|
@ -186,6 +187,29 @@ func (daemon *Daemon) SetNetworkBootstrapKeys(keys []*networktypes.EncryptionKey
|
|||
return daemon.netController.SetKeys(keys)
|
||||
}
|
||||
|
||||
// UpdateAttachment notifies the attacher about the attachment config.
|
||||
func (daemon *Daemon) UpdateAttachment(networkName, networkID, containerID string, config *network.NetworkingConfig) error {
|
||||
if daemon.clusterProvider == nil {
|
||||
return fmt.Errorf("cluster provider is not initialized")
|
||||
}
|
||||
|
||||
if err := daemon.clusterProvider.UpdateAttachment(networkName, containerID, config); err != nil {
|
||||
return daemon.clusterProvider.UpdateAttachment(networkID, containerID, config)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForDetachment makes the cluster manager wait for detachment of
|
||||
// the container from the network.
|
||||
func (daemon *Daemon) WaitForDetachment(ctx context.Context, networkName, networkID, taskID, containerID string) error {
|
||||
if daemon.clusterProvider == nil {
|
||||
return fmt.Errorf("cluster provider is not initialized")
|
||||
}
|
||||
|
||||
return daemon.clusterProvider.WaitForDetachment(ctx, networkName, networkID, taskID, containerID)
|
||||
}
|
||||
|
||||
// CreateManagedNetwork creates an agent network.
|
||||
func (daemon *Daemon) CreateManagedNetwork(create clustertypes.NetworkCreateRequest) error {
|
||||
_, err := daemon.createNetwork(create.NetworkCreateRequest, create.ID, true)
|
||||
|
|
|
@ -14,7 +14,7 @@ type Settings struct {
|
|||
HairpinMode bool
|
||||
LinkLocalIPv6Address string
|
||||
LinkLocalIPv6PrefixLen int
|
||||
Networks map[string]*networktypes.EndpointSettings
|
||||
Networks map[string]*EndpointSettings
|
||||
Service *clustertypes.ServiceConfig
|
||||
Ports nat.PortMap
|
||||
SandboxKey string
|
||||
|
@ -22,3 +22,11 @@ type Settings struct {
|
|||
SecondaryIPv6Addresses []networktypes.Address
|
||||
IsAnonymousEndpoint bool
|
||||
}
|
||||
|
||||
// EndpointSettings is a package local wrapper for
|
||||
// networktypes.EndpointSettings which stores Endpoint state that
|
||||
// needs to be persisted to disk but not exposed in the api.
|
||||
type EndpointSettings struct {
|
||||
*networktypes.EndpointSettings
|
||||
IPAMOperational bool
|
||||
}
|
||||
|
|
|
@ -71,8 +71,8 @@ clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837
|
|||
clone git github.com/imdario/mergo 0.2.1
|
||||
|
||||
#get libnetwork packages
|
||||
clone git github.com/docker/libnetwork 82fb373e3eaa4e9bbb5b5ac148b0a3a71f80fca6
|
||||
clone git github.com/docker/go-events afb2b9f2c23f33ada1a22b03651775fdc65a5089
|
||||
clone git github.com/docker/libnetwork 00e7660daeb4b6108a333319d289bf7dc8b9932e
|
||||
clone git github.com/docker/go-events 18b43f1bc85d9cdd42c05a6cd2d444c7a200a894
|
||||
clone git github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80
|
||||
clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
|
||||
clone git github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b
|
||||
|
@ -85,7 +85,7 @@ clone git github.com/vishvananda/netlink e73bad418fd727ed3a02830b1af1ad0283a1de6
|
|||
clone git github.com/BurntSushi/toml f706d00e3de6abe700c994cdd545a1a4915af060
|
||||
clone git github.com/samuel/go-zookeeper d0e0d8e11f318e000a8cc434616d69e329edc374
|
||||
clone git github.com/deckarep/golang-set ef32fa3046d9f249d399f98ebaf9be944430fd1d
|
||||
clone git github.com/coreos/etcd 06e2338108fdc694349aed923f4a7e45cf0cec1f
|
||||
clone git github.com/coreos/etcd 3a49cbb769ebd8d1dd25abb1e83386e9883a5707
|
||||
clone git github.com/ugorji/go f1f1a805ed361a0e078bb537e4ea78cd37dcf065
|
||||
clone git github.com/hashicorp/consul v0.5.2
|
||||
clone git github.com/boltdb/bolt v1.2.1
|
||||
|
@ -144,11 +144,11 @@ clone git github.com/docker/docker-credential-helpers v0.3.0
|
|||
clone git github.com/docker/containerd 8508d2bec90b96403143a1104cdcbd56f6aeb361
|
||||
|
||||
# cluster
|
||||
clone git github.com/docker/swarmkit 8a761950fb4d9251c335dc6149a8a02756cb3b10
|
||||
clone git github.com/docker/swarmkit 27fbaef4ceed648bb575969ccc9083a6e104a719
|
||||
clone git github.com/golang/mock bd3c8e81be01eef76d4b503f5e687d2d1354d2d9
|
||||
clone git github.com/gogo/protobuf 43a2e0b1c32252bfbbdf81f7faa7a88fb3fa4028
|
||||
clone git github.com/cloudflare/cfssl b895b0549c0ff676f92cf09ba971ae02bb41367b
|
||||
clone git github.com/google/certificate-transparency 025a5cab06f6a819c455d9fdc9e2a1b6d0982284
|
||||
clone git github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a
|
||||
clone git github.com/google/certificate-transparency 0f6e3d1d1ba4d03fdaab7cd716f36255c2e48341
|
||||
clone git golang.org/x/crypto 3fbbcd23f1cb824e69491a5930cfeff09b12f4d2 https://github.com/golang/crypto.git
|
||||
clone git golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb https://github.com/golang/time.git
|
||||
clone git github.com/mreiferson/go-httpclient 63fe23f7434723dc904c901043af07931f293c47
|
||||
|
|
|
@ -298,6 +298,8 @@ func (s *DockerSwarmSuite) TearDownTest(c *check.C) {
|
|||
if err := os.RemoveAll(walDir); err != nil {
|
||||
c.Logf("error removing %v: %v", walDir, err)
|
||||
}
|
||||
|
||||
cleanupExecRoot(c, d.execRoot)
|
||||
}
|
||||
s.daemons = nil
|
||||
s.daemonsLock.Unlock()
|
||||
|
|
|
@ -2,7 +2,29 @@
|
|||
|
||||
package main
|
||||
|
||||
import "syscall"
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
func cleanupExecRoot(c *check.C, execRoot string) {
|
||||
// Cleanup network namespaces in the exec root of this
|
||||
// daemon because this exec root is specific to this
|
||||
// daemon instance and has no chance of getting
|
||||
// cleaned up when a new daemon is instantiated with a
|
||||
// new exec root.
|
||||
netnsPath := filepath.Join(execRoot, "netns")
|
||||
filepath.Walk(netnsPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err := syscall.Unmount(path, syscall.MNT_FORCE); err != nil {
|
||||
c.Logf("unmount of %s failed: %v", path, err)
|
||||
}
|
||||
os.Remove(path)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func signalDaemonDump(pid int) {
|
||||
syscall.Kill(pid, syscall.SIGQUIT)
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
func openEvent(desiredAccess uint32, inheritHandle bool, name string, proc *syscall.LazyProc) (handle syscall.Handle, err error) {
|
||||
|
@ -45,3 +47,6 @@ func signalDaemonDump(pid int) {
|
|||
func signalDaemonReload(pid int) error {
|
||||
return fmt.Errorf("daemon reload not supported")
|
||||
}
|
||||
|
||||
func cleanupExecRoot(c *check.C, execRoot string) {
|
||||
}
|
||||
|
|
|
@ -69,3 +69,7 @@ JSON dictionary:
|
|||
or
|
||||
|
||||
{"driver":"postgres","data_source":"postgres://user:password@host/db"}
|
||||
|
||||
or
|
||||
|
||||
{"driver":"mysql","data_source":"user:password@tcp(hostname:3306)/db?parseTime=true"}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/json"
|
||||
|
@ -59,26 +60,35 @@ type AuthRemote struct {
|
|||
AuthKeyName string `json:"auth_key"`
|
||||
}
|
||||
|
||||
// CAConstraint specifies various CA constraints on the signed certificate.
|
||||
// CAConstraint would verify against (and override) the CA
|
||||
// extensions in the given CSR.
|
||||
type CAConstraint struct {
|
||||
IsCA bool `json:"is_ca"`
|
||||
MaxPathLen int `json:"max_path_len"`
|
||||
MaxPathLenZero bool `json:"max_path_len_zero"`
|
||||
}
|
||||
|
||||
// A SigningProfile stores information that the CA needs to store
|
||||
// signature policy.
|
||||
type SigningProfile struct {
|
||||
Usage []string `json:"usages"`
|
||||
IssuerURL []string `json:"issuer_urls"`
|
||||
OCSP string `json:"ocsp_url"`
|
||||
CRL string `json:"crl_url"`
|
||||
CA bool `json:"is_ca"`
|
||||
OCSPNoCheck bool `json:"ocsp_no_check"`
|
||||
ExpiryString string `json:"expiry"`
|
||||
BackdateString string `json:"backdate"`
|
||||
AuthKeyName string `json:"auth_key"`
|
||||
RemoteName string `json:"remote"`
|
||||
NotBefore time.Time `json:"not_before"`
|
||||
NotAfter time.Time `json:"not_after"`
|
||||
NameWhitelistString string `json:"name_whitelist"`
|
||||
AuthRemote AuthRemote `json:"auth_remote"`
|
||||
CTLogServers []string `json:"ct_log_servers"`
|
||||
AllowedExtensions []OID `json:"allowed_extensions"`
|
||||
CertStore string `json:"cert_store"`
|
||||
Usage []string `json:"usages"`
|
||||
IssuerURL []string `json:"issuer_urls"`
|
||||
OCSP string `json:"ocsp_url"`
|
||||
CRL string `json:"crl_url"`
|
||||
CAConstraint CAConstraint `json:"ca_constraint"`
|
||||
OCSPNoCheck bool `json:"ocsp_no_check"`
|
||||
ExpiryString string `json:"expiry"`
|
||||
BackdateString string `json:"backdate"`
|
||||
AuthKeyName string `json:"auth_key"`
|
||||
RemoteName string `json:"remote"`
|
||||
NotBefore time.Time `json:"not_before"`
|
||||
NotAfter time.Time `json:"not_after"`
|
||||
NameWhitelistString string `json:"name_whitelist"`
|
||||
AuthRemote AuthRemote `json:"auth_remote"`
|
||||
CTLogServers []string `json:"ct_log_servers"`
|
||||
AllowedExtensions []OID `json:"allowed_extensions"`
|
||||
CertStore string `json:"cert_store"`
|
||||
|
||||
Policies []CertificatePolicy
|
||||
Expiry time.Duration
|
||||
|
@ -86,6 +96,8 @@ type SigningProfile struct {
|
|||
Provider auth.Provider
|
||||
RemoteProvider auth.Provider
|
||||
RemoteServer string
|
||||
RemoteCAs *x509.CertPool
|
||||
ClientCert *tls.Certificate
|
||||
CSRWhitelist *CSRWhitelist
|
||||
NameWhitelist *regexp.Regexp
|
||||
ExtensionWhitelist map[string]bool
|
||||
|
@ -303,6 +315,44 @@ func (p *Signing) OverrideRemotes(remote string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SetClientCertKeyPairFromFile updates the properties to set client certificates for mutual
|
||||
// authenticated TLS remote requests
|
||||
func (p *Signing) SetClientCertKeyPairFromFile(certFile string, keyFile string) error {
|
||||
if certFile != "" && keyFile != "" {
|
||||
cert, err := helpers.LoadClientCertificate(certFile, keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, profile := range p.Profiles {
|
||||
profile.ClientCert = cert
|
||||
}
|
||||
p.Default.ClientCert = cert
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRemoteCAsFromFile reads root CAs from file and updates the properties to set remote CAs for TLS
|
||||
// remote requests
|
||||
func (p *Signing) SetRemoteCAsFromFile(caFile string) error {
|
||||
if caFile != "" {
|
||||
remoteCAs, err := helpers.LoadPEMCertPool(caFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.SetRemoteCAs(remoteCAs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRemoteCAs updates the properties to set remote CAs for TLS
|
||||
// remote requests
|
||||
func (p *Signing) SetRemoteCAs(remoteCAs *x509.CertPool) {
|
||||
for _, profile := range p.Profiles {
|
||||
profile.RemoteCAs = remoteCAs
|
||||
}
|
||||
p.Default.RemoteCAs = remoteCAs
|
||||
}
|
||||
|
||||
// NeedsRemoteSigner returns true if one of the profiles has a remote set
|
||||
func (p *Signing) NeedsRemoteSigner() bool {
|
||||
for _, profile := range p.Profiles {
|
||||
|
@ -360,6 +410,11 @@ func (p *SigningProfile) validProfile(isDefault bool) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
if p.AuthRemote.RemoteName == "" && p.AuthRemote.AuthKeyName != "" {
|
||||
log.Debugf("invalid auth remote profile: no remote signer specified")
|
||||
return false
|
||||
}
|
||||
|
||||
if p.RemoteName != "" {
|
||||
log.Debugf("validate remote profile")
|
||||
|
||||
|
@ -375,6 +430,7 @@ func (p *SigningProfile) validProfile(isDefault bool) bool {
|
|||
|
||||
if p.AuthRemote.RemoteName != "" {
|
||||
log.Debugf("invalid remote profile: auth remote is also specified")
|
||||
return false
|
||||
}
|
||||
} else if p.AuthRemote.RemoteName != "" {
|
||||
log.Debugf("validate auth remote profile")
|
||||
|
@ -409,6 +465,43 @@ func (p *SigningProfile) validProfile(isDefault bool) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// This checks if the SigningProfile object contains configurations that are only effective with a local signer
|
||||
// which has access to CA private key.
|
||||
func (p *SigningProfile) hasLocalConfig() bool {
|
||||
if p.Usage != nil ||
|
||||
p.IssuerURL != nil ||
|
||||
p.OCSP != "" ||
|
||||
p.ExpiryString != "" ||
|
||||
p.BackdateString != "" ||
|
||||
p.CAConstraint.IsCA != false ||
|
||||
!p.NotBefore.IsZero() ||
|
||||
!p.NotAfter.IsZero() ||
|
||||
p.NameWhitelistString != "" ||
|
||||
len(p.CTLogServers) != 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// warnSkippedSettings prints a log warning message about skipped settings
|
||||
// in a SigningProfile, usually due to remote signer.
|
||||
func (p *Signing) warnSkippedSettings() {
|
||||
const warningMessage = `The configuration value by "usages", "issuer_urls", "ocsp_url", "crl_url", "ca_constraint", "expiry", "backdate", "not_before", "not_after", "cert_store" and "ct_log_servers" are skipped`
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if (p.Default.RemoteName != "" || p.Default.AuthRemote.RemoteName != "") && p.Default.hasLocalConfig() {
|
||||
log.Warning("default profile points to a remote signer: ", warningMessage)
|
||||
}
|
||||
|
||||
for name, profile := range p.Profiles {
|
||||
if (profile.RemoteName != "" || profile.AuthRemote.RemoteName != "") && profile.hasLocalConfig() {
|
||||
log.Warningf("Profiles[%s] points to a remote signer: %s", name, warningMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Signing codifies the signature configuration policy for a CA.
|
||||
type Signing struct {
|
||||
Profiles map[string]*SigningProfile `json:"profiles"`
|
||||
|
@ -450,6 +543,9 @@ func (p *Signing) Valid() bool {
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
||||
p.warnSkippedSettings()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -149,6 +149,8 @@ const (
|
|||
|
||||
// UnknownProfile indicates that the profile does not exist.
|
||||
UnknownProfile // 54XX
|
||||
|
||||
UnmatchedWhitelist // 55xx
|
||||
)
|
||||
|
||||
// The following are API client related errors, and should be
|
||||
|
@ -313,6 +315,8 @@ func New(category Category, reason Reason) *Error {
|
|||
msg = "Policy violation request"
|
||||
case UnknownProfile:
|
||||
msg = "Unknown policy profile"
|
||||
case UnmatchedWhitelist:
|
||||
msg = "Request does not match policy whitelist"
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported CFSSL error reason %d under category PolicyError.",
|
||||
reason))
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
|
@ -311,11 +312,23 @@ func ParseOneCertificateFromPEM(certsPEM []byte) ([]*x509.Certificate, []byte, e
|
|||
|
||||
// LoadPEMCertPool loads a pool of PEM certificates from file.
|
||||
func LoadPEMCertPool(certsFile string) (*x509.CertPool, error) {
|
||||
if certsFile == "" {
|
||||
return nil, nil
|
||||
}
|
||||
pemCerts, err := ioutil.ReadFile(certsFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return PEMToCertPool(pemCerts)
|
||||
}
|
||||
|
||||
// PEMToCertPool concerts PEM certificates to a CertPool.
|
||||
func PEMToCertPool(pemCerts []byte) (*x509.CertPool, error) {
|
||||
if len(pemCerts) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
certPool := x509.NewCertPool()
|
||||
if !certPool.AppendCertsFromPEM(pemCerts) {
|
||||
return nil, errors.New("failed to load cert pool")
|
||||
|
@ -477,3 +490,29 @@ func SignerAlgo(priv crypto.Signer) x509.SignatureAlgorithm {
|
|||
return x509.UnknownSignatureAlgorithm
|
||||
}
|
||||
}
|
||||
|
||||
// LoadClientCertificate load key/certificate from pem files
|
||||
func LoadClientCertificate(certFile string, keyFile string) (*tls.Certificate, error) {
|
||||
if certFile != "" && keyFile != "" {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
log.Critical("Unable to read client certificate from file: %s or key from file: %s", certFile, keyFile)
|
||||
return nil, err
|
||||
}
|
||||
log.Debug("Client certificate loaded ")
|
||||
return &cert, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// CreateTLSConfig creates a tls.Config object from certs and roots
|
||||
func CreateTLSConfig(remoteCAs *x509.CertPool, cert *tls.Certificate) *tls.Config {
|
||||
var certs []tls.Certificate
|
||||
if cert != nil {
|
||||
certs = []tls.Certificate{*cert}
|
||||
}
|
||||
return &tls.Config{
|
||||
Certificates: certs,
|
||||
RootCAs: remoteCAs,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,13 +48,16 @@ func New(req *csr.CertificateRequest) (cert, csrPEM, key []byte, err error) {
|
|||
if req.CA.Expiry != "" {
|
||||
policy.Default.ExpiryString = req.CA.Expiry
|
||||
policy.Default.Expiry, err = time.ParseDuration(req.CA.Expiry)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
signer.MaxPathLen = req.CA.PathLength
|
||||
policy.Default.CAConstraint.MaxPathLen = req.CA.PathLength
|
||||
if req.CA.PathLength != 0 && req.CA.PathLenZero == true {
|
||||
log.Infof("ignore invalid 'pathlenzero' value")
|
||||
} else {
|
||||
signer.MaxPathLenZero = req.CA.PathLenZero
|
||||
policy.Default.CAConstraint.MaxPathLenZero = req.CA.PathLenZero
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,12 +75,11 @@ func New(req *csr.CertificateRequest) (cert, csrPEM, key []byte, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
s, err := local.NewSigner(priv, nil, signer.DefaultSigAlgo(priv), nil)
|
||||
s, err := local.NewSigner(priv, nil, signer.DefaultSigAlgo(priv), policy)
|
||||
if err != nil {
|
||||
log.Errorf("failed to create signer: %v", err)
|
||||
return
|
||||
}
|
||||
s.SetPolicy(policy)
|
||||
|
||||
signReq := signer.SignRequest{Hosts: req.Hosts, Request: string(csrPEM)}
|
||||
cert, err = s.Sign(signReq)
|
||||
|
@ -143,11 +145,11 @@ func NewFromSigner(req *csr.CertificateRequest, priv crypto.Signer) (cert, csrPE
|
|||
}
|
||||
}
|
||||
|
||||
signer.MaxPathLen = req.CA.PathLength
|
||||
policy.Default.CAConstraint.MaxPathLen = req.CA.PathLength
|
||||
if req.CA.PathLength != 0 && req.CA.PathLenZero == true {
|
||||
log.Infof("ignore invalid 'pathlenzero' value")
|
||||
} else {
|
||||
signer.MaxPathLenZero = req.CA.PathLenZero
|
||||
policy.Default.CAConstraint.MaxPathLenZero = req.CA.PathLenZero
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,12 +158,11 @@ func NewFromSigner(req *csr.CertificateRequest, priv crypto.Signer) (cert, csrPE
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
s, err := local.NewSigner(priv, nil, signer.DefaultSigAlgo(priv), nil)
|
||||
s, err := local.NewSigner(priv, nil, signer.DefaultSigAlgo(priv), policy)
|
||||
if err != nil {
|
||||
log.Errorf("failed to create signer: %v", err)
|
||||
return
|
||||
}
|
||||
s.SetPolicy(policy)
|
||||
|
||||
signReq := signer.SignRequest{Request: string(csrPEM)}
|
||||
cert, err = s.Sign(signReq)
|
||||
|
@ -217,7 +218,7 @@ var CAPolicy = func() *config.Signing {
|
|||
Usage: []string{"cert sign", "crl sign"},
|
||||
ExpiryString: "43800h",
|
||||
Expiry: 5 * helpers.OneYear,
|
||||
CA: true,
|
||||
CAConstraint: config.CAConstraint{IsCA: true},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
package log
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
@ -63,13 +62,6 @@ func SetLogger(logger SyslogWriter) {
|
|||
syslogWriter = logger
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Only define loglevel flag once.
|
||||
if flag.Lookup("loglevel") == nil {
|
||||
flag.IntVar(&Level, "loglevel", LevelInfo, "Log level (0 = DEBUG, 5 = FATAL)")
|
||||
}
|
||||
}
|
||||
|
||||
func print(l int, msg string) {
|
||||
if l >= Level {
|
||||
if syslogWriter != nil {
|
||||
|
|
|
@ -115,9 +115,6 @@ func (s *Signer) sign(template *x509.Certificate, profile *config.SigningProfile
|
|||
template.EmailAddresses = nil
|
||||
s.ca = template
|
||||
initRoot = true
|
||||
} else if template.IsCA {
|
||||
template.DNSNames = nil
|
||||
template.EmailAddresses = nil
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, template, s.ca, template.PublicKey, s.priv)
|
||||
|
@ -250,18 +247,21 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
|
|||
}
|
||||
|
||||
if safeTemplate.IsCA {
|
||||
if !profile.CA {
|
||||
return nil, cferr.New(cferr.CertificateError, cferr.InvalidRequest)
|
||||
if !profile.CAConstraint.IsCA {
|
||||
log.Error("local signer policy disallows issuing CA certificate")
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidRequest)
|
||||
}
|
||||
|
||||
if s.ca != nil && s.ca.MaxPathLen > 0 {
|
||||
if safeTemplate.MaxPathLen >= s.ca.MaxPathLen {
|
||||
log.Error("local signer certificate disallows CA MaxPathLen extending")
|
||||
// do not sign a cert with pathlen > current
|
||||
return nil, cferr.New(cferr.CertificateError, cferr.InvalidRequest)
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidRequest)
|
||||
}
|
||||
} else if s.ca != nil && s.ca.MaxPathLen == 0 && s.ca.MaxPathLenZero {
|
||||
log.Error("local signer certificate disallows issuing CA certificate")
|
||||
// signer has pathlen of 0, do not sign more intermediate CAs
|
||||
return nil, cferr.New(cferr.CertificateError, cferr.InvalidRequest)
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidRequest)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -272,17 +272,17 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
|
|||
if profile.NameWhitelist != nil {
|
||||
if safeTemplate.Subject.CommonName != "" {
|
||||
if profile.NameWhitelist.Find([]byte(safeTemplate.Subject.CommonName)) == nil {
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.UnmatchedWhitelist)
|
||||
}
|
||||
}
|
||||
for _, name := range safeTemplate.DNSNames {
|
||||
if profile.NameWhitelist.Find([]byte(name)) == nil {
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.UnmatchedWhitelist)
|
||||
}
|
||||
}
|
||||
for _, name := range safeTemplate.EmailAddresses {
|
||||
if profile.NameWhitelist.Find([]byte(name)) == nil {
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.InvalidPolicy)
|
||||
return nil, cferr.New(cferr.PolicyError, cferr.UnmatchedWhitelist)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -352,7 +352,7 @@ func (s *Signer) Sign(req signer.SignRequest) (cert []byte, err error) {
|
|||
|
||||
for _, server := range profile.CTLogServers {
|
||||
log.Infof("submitting poisoned precertificate to %s", server)
|
||||
var ctclient = client.New(server)
|
||||
var ctclient = client.New(server, nil)
|
||||
var resp *ct.SignedCertificateTimestamp
|
||||
resp, err = ctclient.AddPreChain(prechain)
|
||||
if err != nil {
|
||||
|
|
|
@ -23,12 +23,6 @@ import (
|
|||
"github.com/cloudflare/cfssl/info"
|
||||
)
|
||||
|
||||
// MaxPathLen is the default path length for a new CA certificate.
|
||||
var MaxPathLen = 2
|
||||
|
||||
// MaxPathLenZero indicates whether a new CA certificate has pathlen=0
|
||||
var MaxPathLenZero = false
|
||||
|
||||
// Subject contains the information that should be used to override the
|
||||
// subject information when signing a certificate.
|
||||
type Subject struct {
|
||||
|
@ -294,7 +288,15 @@ func FillTemplate(template *x509.Certificate, defaultProfile, profile *config.Si
|
|||
template.KeyUsage = ku
|
||||
template.ExtKeyUsage = eku
|
||||
template.BasicConstraintsValid = true
|
||||
template.IsCA = profile.CA
|
||||
template.IsCA = profile.CAConstraint.IsCA
|
||||
if template.IsCA {
|
||||
template.MaxPathLen = profile.CAConstraint.MaxPathLen
|
||||
if template.MaxPathLen == 0 {
|
||||
template.MaxPathLenZero = profile.CAConstraint.MaxPathLenZero
|
||||
}
|
||||
template.DNSNames = nil
|
||||
template.EmailAddresses = nil
|
||||
}
|
||||
template.SubjectKeyId = ski
|
||||
|
||||
if ocspURL != "" {
|
||||
|
|
|
@ -96,3 +96,26 @@ func Exist(name string) bool {
|
|||
_, err := os.Stat(name)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// ZeroToEnd zeros a file starting from SEEK_CUR to its SEEK_END. May temporarily
|
||||
// shorten the length of the file.
|
||||
func ZeroToEnd(f *os.File) error {
|
||||
// TODO: support FALLOC_FL_ZERO_RANGE
|
||||
off, err := f.Seek(0, os.SEEK_CUR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lenf, lerr := f.Seek(0, os.SEEK_END)
|
||||
if lerr != nil {
|
||||
return lerr
|
||||
}
|
||||
if err = f.Truncate(off); err != nil {
|
||||
return err
|
||||
}
|
||||
// make sure blocks remain allocated
|
||||
if err = Preallocate(f, lenf, true); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = f.Seek(off, os.SEEK_SET)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -232,7 +232,7 @@ func (l *raftLog) term(i uint64) (uint64, error) {
|
|||
if err == nil {
|
||||
return t, nil
|
||||
}
|
||||
if err == ErrCompacted {
|
||||
if err == ErrCompacted || err == ErrUnavailable {
|
||||
return 0, err
|
||||
}
|
||||
panic(err) // TODO(bdarnell)
|
||||
|
@ -339,7 +339,7 @@ func (l *raftLog) mustCheckOutOfBounds(lo, hi uint64) error {
|
|||
return ErrCompacted
|
||||
}
|
||||
|
||||
length := l.lastIndex() - fi + 1
|
||||
length := l.lastIndex() + 1 - fi
|
||||
if lo < fi || hi > fi+length {
|
||||
l.logger.Panicf("slice[%d,%d) out of bound [%d,%d]", lo, hi, fi, l.lastIndex())
|
||||
}
|
||||
|
|
12
vendor/src/github.com/coreos/etcd/raft/node.go
vendored
12
vendor/src/github.com/coreos/etcd/raft/node.go
vendored
|
@ -144,6 +144,9 @@ type Node interface {
|
|||
// to match MemoryStorage.Compact.
|
||||
ApplyConfChange(cc pb.ConfChange) *pb.ConfState
|
||||
|
||||
// TransferLeadership attempts to transfer leadership to the given transferee.
|
||||
TransferLeadership(ctx context.Context, lead, transferee uint64)
|
||||
|
||||
// ReadIndex request a read state. The read state will be set in the ready.
|
||||
// Read state has a read index. Once the application advances further than the read
|
||||
// index, any linearizable read requests issued before the read request can be
|
||||
|
@ -485,6 +488,15 @@ func (n *node) ReportSnapshot(id uint64, status SnapshotStatus) {
|
|||
}
|
||||
}
|
||||
|
||||
func (n *node) TransferLeadership(ctx context.Context, lead, transferee uint64) {
|
||||
select {
|
||||
// manually set 'from' and 'to', so that leader can voluntarily transfers its leadership
|
||||
case n.recvc <- pb.Message{Type: pb.MsgTransferLeader, From: transferee, To: lead}:
|
||||
case <-n.done:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) ReadIndex(ctx context.Context, rctx []byte) error {
|
||||
return n.step(ctx, pb.Message{Type: pb.MsgReadIndex, Entries: []pb.Entry{{Data: rctx}}})
|
||||
}
|
||||
|
|
12
vendor/src/github.com/coreos/etcd/raft/raft.go
vendored
12
vendor/src/github.com/coreos/etcd/raft/raft.go
vendored
|
@ -590,11 +590,6 @@ func (r *raft) Step(m pb.Message) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
if m.Type == pb.MsgTransferLeader {
|
||||
if r.state != StateLeader {
|
||||
r.logger.Debugf("%x [term %d state %v] ignoring MsgTransferLeader to %x", r.id, r.Term, r.state, m.From)
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case m.Term == 0:
|
||||
|
@ -874,6 +869,13 @@ func stepFollower(r *raft, m pb.Message) {
|
|||
r.id, r.raftLog.lastTerm(), r.raftLog.lastIndex(), r.Vote, m.From, m.LogTerm, m.Index, r.Term)
|
||||
r.send(pb.Message{To: m.From, Type: pb.MsgVoteResp, Reject: true})
|
||||
}
|
||||
case pb.MsgTransferLeader:
|
||||
if r.lead == None {
|
||||
r.logger.Infof("%x no leader at term %d; dropping leader transfer msg", r.id, r.Term)
|
||||
return
|
||||
}
|
||||
m.To = r.lead
|
||||
r.send(m)
|
||||
case pb.MsgTimeoutNow:
|
||||
r.logger.Infof("%x [term %d] received MsgTimeoutNow from %x and starts an election to get leadership.", r.id, r.Term, m.From)
|
||||
r.campaign(campaignTransfer)
|
||||
|
|
|
@ -130,6 +130,9 @@ func (ms *MemoryStorage) Term(i uint64) (uint64, error) {
|
|||
if i < offset {
|
||||
return 0, ErrCompacted
|
||||
}
|
||||
if int(i-offset) >= len(ms.ents) {
|
||||
return 0, ErrUnavailable
|
||||
}
|
||||
return ms.ents[i-offset].Term, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ func max(a, b uint64) uint64 {
|
|||
|
||||
func IsLocalMsg(msgt pb.MessageType) bool {
|
||||
return msgt == pb.MsgHup || msgt == pb.MsgBeat || msgt == pb.MsgUnreachable ||
|
||||
msgt == pb.MsgSnapStatus || msgt == pb.MsgCheckQuorum || msgt == pb.MsgTransferLeader
|
||||
msgt == pb.MsgSnapStatus || msgt == pb.MsgCheckQuorum
|
||||
}
|
||||
|
||||
func IsResponseMsg(msgt pb.MessageType) bool {
|
||||
|
|
30
vendor/src/github.com/coreos/etcd/wal/wal.go
vendored
30
vendor/src/github.com/coreos/etcd/wal/wal.go
vendored
|
@ -131,22 +131,7 @@ func Create(dirpath string, metadata []byte) (*WAL, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// rename of directory with locked files doesn't work on windows; close
|
||||
// the WAL to release the locks so the directory can be renamed
|
||||
w.Close()
|
||||
if err := os.Rename(tmpdirpath, dirpath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// reopen and relock
|
||||
newWAL, oerr := Open(dirpath, walpb.Snapshot{})
|
||||
if oerr != nil {
|
||||
return nil, oerr
|
||||
}
|
||||
if _, _, _, err := newWAL.ReadAll(); err != nil {
|
||||
newWAL.Close()
|
||||
return nil, err
|
||||
}
|
||||
return newWAL, nil
|
||||
return w.renameWal(tmpdirpath)
|
||||
}
|
||||
|
||||
// Open opens the WAL at the given snap.
|
||||
|
@ -301,6 +286,18 @@ func (w *WAL) ReadAll() (metadata []byte, state raftpb.HardState, ents []raftpb.
|
|||
state.Reset()
|
||||
return nil, state, nil, err
|
||||
}
|
||||
// decodeRecord() will return io.EOF if it detects a zero record,
|
||||
// but this zero record may be followed by non-zero records from
|
||||
// a torn write. Overwriting some of these non-zero records, but
|
||||
// not all, will cause CRC errors on WAL open. Since the records
|
||||
// were never fully synced to disk in the first place, it's safe
|
||||
// to zero them out to avoid any CRC errors from new writes.
|
||||
if _, err = w.tail().Seek(w.decoder.lastOffset(), os.SEEK_SET); err != nil {
|
||||
return nil, state, nil, err
|
||||
}
|
||||
if err = fileutil.ZeroToEnd(w.tail().File); err != nil {
|
||||
return nil, state, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = nil
|
||||
|
@ -319,7 +316,6 @@ func (w *WAL) ReadAll() (metadata []byte, state raftpb.HardState, ents []raftpb.
|
|||
|
||||
if w.tail() != nil {
|
||||
// create encoder (chain crc with the decoder), enable appending
|
||||
_, err = w.tail().Seek(w.decoder.lastOffset(), os.SEEK_SET)
|
||||
w.encoder = newEncoder(w.tail(), w.decoder.lastCRC())
|
||||
}
|
||||
w.decoder = nil
|
||||
|
|
38
vendor/src/github.com/coreos/etcd/wal/wal_unix.go
vendored
Normal file
38
vendor/src/github.com/coreos/etcd/wal/wal_unix.go
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// 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.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package wal
|
||||
|
||||
import "os"
|
||||
|
||||
func (w *WAL) renameWal(tmpdirpath string) (*WAL, error) {
|
||||
// On non-Windows platforms, hold the lock while renaming. Releasing
|
||||
// the lock and trying to reacquire it quickly can be flaky because
|
||||
// it's possible the process will fork to spawn a process while this is
|
||||
// happening. The fds are set up as close-on-exec by the Go runtime,
|
||||
// but there is a window between the fork and the exec where another
|
||||
// process holds the lock.
|
||||
|
||||
if err := os.RemoveAll(w.dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := os.Rename(tmpdirpath, w.dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w.fp = newFilePipeline(w.dir, SegmentSizeBytes)
|
||||
return w, nil
|
||||
}
|
41
vendor/src/github.com/coreos/etcd/wal/wal_windows.go
vendored
Normal file
41
vendor/src/github.com/coreos/etcd/wal/wal_windows.go
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2016 The etcd Authors
|
||||
//
|
||||
// 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 wal
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/coreos/etcd/wal/walpb"
|
||||
)
|
||||
|
||||
func (w *WAL) renameWal(tmpdirpath string) (*WAL, error) {
|
||||
// rename of directory with locked files doesn't work on
|
||||
// windows; close the WAL to release the locks so the directory
|
||||
// can be renamed
|
||||
w.Close()
|
||||
if err := os.Rename(tmpdirpath, w.dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// reopen and relock
|
||||
newWAL, oerr := Open(w.dir, walpb.Snapshot{})
|
||||
if oerr != nil {
|
||||
return nil, oerr
|
||||
}
|
||||
if _, _, _, err := newWAL.ReadAll(); err != nil {
|
||||
newWAL.Close()
|
||||
return nil, err
|
||||
}
|
||||
return newWAL, nil
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// CheckpointCreate creates a checkpoint from the given container with the given name
|
||||
func (cli *Client) CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error {
|
||||
resp, err := cli.post(ctx, "/containers/"+container+"/checkpoints", nil, options, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// CheckpointDelete deletes the checkpoint with the given name from the given container
|
||||
func (cli *Client) CheckpointDelete(ctx context.Context, containerID string, checkpointID string) error {
|
||||
resp, err := cli.delete(ctx, "/containers/"+containerID+"/checkpoints/"+checkpointID, nil, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// CheckpointList returns the volumes configured in the docker host.
|
||||
func (cli *Client) CheckpointList(ctx context.Context, container string) ([]types.Checkpoint, error) {
|
||||
var checkpoints []types.Checkpoint
|
||||
|
||||
resp, err := cli.get(ctx, "/containers/"+container+"/checkpoints", nil, nil)
|
||||
if err != nil {
|
||||
return checkpoints, err
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.body).Decode(&checkpoints)
|
||||
ensureReaderClosed(resp)
|
||||
return checkpoints, err
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/engine-api/client/transport"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
)
|
||||
|
||||
// DefaultVersion is the version of the current stable API
|
||||
const DefaultVersion string = "1.23"
|
||||
|
||||
// Client is the API client that performs all operations
|
||||
// against a docker server.
|
||||
type Client struct {
|
||||
// host holds the server address to connect to
|
||||
host string
|
||||
// proto holds the client protocol i.e. unix.
|
||||
proto string
|
||||
// addr holds the client address.
|
||||
addr string
|
||||
// basePath holds the path to prepend to the requests.
|
||||
basePath string
|
||||
// transport is the interface to send request with, it implements transport.Client.
|
||||
transport transport.Client
|
||||
// version of the server to talk to.
|
||||
version string
|
||||
// custom http headers configured by users.
|
||||
customHTTPHeaders map[string]string
|
||||
}
|
||||
|
||||
// NewEnvClient initializes a new API client based on environment variables.
|
||||
// Use DOCKER_HOST to set the url to the docker server.
|
||||
// Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
|
||||
// Use DOCKER_CERT_PATH to load the tls certificates from.
|
||||
// Use DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default.
|
||||
func NewEnvClient() (*Client, error) {
|
||||
var client *http.Client
|
||||
if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" {
|
||||
options := tlsconfig.Options{
|
||||
CAFile: filepath.Join(dockerCertPath, "ca.pem"),
|
||||
CertFile: filepath.Join(dockerCertPath, "cert.pem"),
|
||||
KeyFile: filepath.Join(dockerCertPath, "key.pem"),
|
||||
InsecureSkipVerify: os.Getenv("DOCKER_TLS_VERIFY") == "",
|
||||
}
|
||||
tlsc, err := tlsconfig.Client(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsc,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
host := os.Getenv("DOCKER_HOST")
|
||||
if host == "" {
|
||||
host = DefaultDockerHost
|
||||
}
|
||||
|
||||
version := os.Getenv("DOCKER_API_VERSION")
|
||||
if version == "" {
|
||||
version = DefaultVersion
|
||||
}
|
||||
|
||||
return NewClient(host, version, client, nil)
|
||||
}
|
||||
|
||||
// NewClient initializes a new API client for the given host and API version.
|
||||
// It uses the given http client as transport.
|
||||
// It also initializes the custom http headers to add to each request.
|
||||
//
|
||||
// It won't send any version information if the version number is empty. It is
|
||||
// highly recommended that you set a version or your client may break if the
|
||||
// server is upgraded.
|
||||
func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
|
||||
proto, addr, basePath, err := ParseHost(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transport, err := transport.NewTransportWithHTTP(proto, addr, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{
|
||||
host: host,
|
||||
proto: proto,
|
||||
addr: addr,
|
||||
basePath: basePath,
|
||||
transport: transport,
|
||||
version: version,
|
||||
customHTTPHeaders: httpHeaders,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getAPIPath returns the versioned request path to call the api.
|
||||
// It appends the query parameters to the path if they are not empty.
|
||||
func (cli *Client) getAPIPath(p string, query url.Values) string {
|
||||
var apiPath string
|
||||
if cli.version != "" {
|
||||
v := strings.TrimPrefix(cli.version, "v")
|
||||
apiPath = fmt.Sprintf("%s/v%s%s", cli.basePath, v, p)
|
||||
} else {
|
||||
apiPath = fmt.Sprintf("%s%s", cli.basePath, p)
|
||||
}
|
||||
|
||||
u := &url.URL{
|
||||
Path: apiPath,
|
||||
}
|
||||
if len(query) > 0 {
|
||||
u.RawQuery = query.Encode()
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// ClientVersion returns the version string associated with this
|
||||
// instance of the Client. Note that this value can be changed
|
||||
// via the DOCKER_API_VERSION env var.
|
||||
func (cli *Client) ClientVersion() string {
|
||||
return cli.version
|
||||
}
|
||||
|
||||
// UpdateClientVersion updates the version string associated with this
|
||||
// instance of the Client.
|
||||
func (cli *Client) UpdateClientVersion(v string) {
|
||||
cli.version = v
|
||||
}
|
||||
|
||||
// ParseHost verifies that the given host strings is valid.
|
||||
func ParseHost(host string) (string, string, string, error) {
|
||||
protoAddrParts := strings.SplitN(host, "://", 2)
|
||||
if len(protoAddrParts) == 1 {
|
||||
return "", "", "", fmt.Errorf("unable to parse docker host `%s`", host)
|
||||
}
|
||||
|
||||
var basePath string
|
||||
proto, addr := protoAddrParts[0], protoAddrParts[1]
|
||||
if proto == "tcp" {
|
||||
parsed, err := url.Parse("tcp://" + addr)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
addr = parsed.Host
|
||||
basePath = parsed.Path
|
||||
}
|
||||
return proto, addr, basePath, nil
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
// +build linux freebsd solaris openbsd darwin
|
||||
|
||||
package client
|
||||
|
||||
// DefaultDockerHost defines os specific default if DOCKER_HOST is unset
|
||||
const DefaultDockerHost = "unix:///var/run/docker.sock"
|
|
@ -1,4 +0,0 @@
|
|||
package client
|
||||
|
||||
// DefaultDockerHost defines os specific default if DOCKER_HOST is unset
|
||||
const DefaultDockerHost = "npipe:////./pipe/docker_engine"
|
|
@ -1,34 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ContainerAttach attaches a connection to a container in the server.
|
||||
// It returns a types.HijackedConnection with the hijacked connection
|
||||
// and the a reader to get output. It's up to the called to close
|
||||
// the hijacked connection by calling types.HijackedResponse.Close.
|
||||
func (cli *Client) ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error) {
|
||||
query := url.Values{}
|
||||
if options.Stream {
|
||||
query.Set("stream", "1")
|
||||
}
|
||||
if options.Stdin {
|
||||
query.Set("stdin", "1")
|
||||
}
|
||||
if options.Stdout {
|
||||
query.Set("stdout", "1")
|
||||
}
|
||||
if options.Stderr {
|
||||
query.Set("stderr", "1")
|
||||
}
|
||||
if options.DetachKeys != "" {
|
||||
query.Set("detachKeys", options.DetachKeys)
|
||||
}
|
||||
|
||||
headers := map[string][]string{"Content-Type": {"text/plain"}}
|
||||
return cli.postHijacked(ctx, "/containers/"+container+"/attach", query, nil, headers)
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
|
||||
distreference "github.com/docker/distribution/reference"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/reference"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ContainerCommit applies changes into a container and creates a new tagged image.
|
||||
func (cli *Client) ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) {
|
||||
var repository, tag string
|
||||
if options.Reference != "" {
|
||||
distributionRef, err := distreference.ParseNamed(options.Reference)
|
||||
if err != nil {
|
||||
return types.ContainerCommitResponse{}, err
|
||||
}
|
||||
|
||||
if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical {
|
||||
return types.ContainerCommitResponse{}, errors.New("refusing to create a tag with a digest reference")
|
||||
}
|
||||
|
||||
tag = reference.GetTagFromNamedRef(distributionRef)
|
||||
repository = distributionRef.Name()
|
||||
}
|
||||
|
||||
query := url.Values{}
|
||||
query.Set("container", container)
|
||||
query.Set("repo", repository)
|
||||
query.Set("tag", tag)
|
||||
query.Set("comment", options.Comment)
|
||||
query.Set("author", options.Author)
|
||||
for _, change := range options.Changes {
|
||||
query.Add("changes", change)
|
||||
}
|
||||
if options.Pause != true {
|
||||
query.Set("pause", "0")
|
||||
}
|
||||
|
||||
var response types.ContainerCommitResponse
|
||||
resp, err := cli.post(ctx, "/commit", query, options.Config, nil)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.body).Decode(&response)
|
||||
ensureReaderClosed(resp)
|
||||
return response, err
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
)
|
||||
|
||||
// ContainerStatPath returns Stat information about a path inside the container filesystem.
|
||||
func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path string) (types.ContainerPathStat, error) {
|
||||
query := url.Values{}
|
||||
query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
|
||||
|
||||
urlStr := fmt.Sprintf("/containers/%s/archive", containerID)
|
||||
response, err := cli.head(ctx, urlStr, query, nil)
|
||||
if err != nil {
|
||||
return types.ContainerPathStat{}, err
|
||||
}
|
||||
defer ensureReaderClosed(response)
|
||||
return getContainerPathStatFromHeader(response.header)
|
||||
}
|
||||
|
||||
// CopyToContainer copies content into the container filesystem.
|
||||
func (cli *Client) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error {
|
||||
query := url.Values{}
|
||||
query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
|
||||
// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
|
||||
if !options.AllowOverwriteDirWithFile {
|
||||
query.Set("noOverwriteDirNonDir", "true")
|
||||
}
|
||||
|
||||
apiPath := fmt.Sprintf("/containers/%s/archive", container)
|
||||
|
||||
response, err := cli.putRaw(ctx, apiPath, query, content, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ensureReaderClosed(response)
|
||||
|
||||
if response.statusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyFromContainer gets the content from the container and returns it as a Reader
|
||||
// to manipulate it in the host. It's up to the caller to close the reader.
|
||||
func (cli *Client) CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
||||
query := make(url.Values, 1)
|
||||
query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
|
||||
|
||||
apiPath := fmt.Sprintf("/containers/%s/archive", container)
|
||||
response, err := cli.get(ctx, apiPath, query, nil)
|
||||
if err != nil {
|
||||
return nil, types.ContainerPathStat{}, err
|
||||
}
|
||||
|
||||
if response.statusCode != http.StatusOK {
|
||||
return nil, types.ContainerPathStat{}, fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
|
||||
}
|
||||
|
||||
// In order to get the copy behavior right, we need to know information
|
||||
// about both the source and the destination. The response headers include
|
||||
// stat info about the source that we can use in deciding exactly how to
|
||||
// copy it locally. Along with the stat info about the local destination,
|
||||
// we have everything we need to handle the multiple possibilities there
|
||||
// can be when copying a file/dir from one location to another file/dir.
|
||||
stat, err := getContainerPathStatFromHeader(response.header)
|
||||
if err != nil {
|
||||
return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err)
|
||||
}
|
||||
return response.body, stat, err
|
||||
}
|
||||
|
||||
func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) {
|
||||
var stat types.ContainerPathStat
|
||||
|
||||
encodedStat := header.Get("X-Docker-Container-Path-Stat")
|
||||
statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat))
|
||||
|
||||
err := json.NewDecoder(statDecoder).Decode(&stat)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to decode container path stat header: %s", err)
|
||||
}
|
||||
|
||||
return stat, err
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/container"
|
||||
"github.com/docker/engine-api/types/network"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type configWrapper struct {
|
||||
*container.Config
|
||||
HostConfig *container.HostConfig
|
||||
NetworkingConfig *network.NetworkingConfig
|
||||
}
|
||||
|
||||
// ContainerCreate creates a new container based in the given configuration.
|
||||
// It can be associated with a name, but it's not mandatory.
|
||||
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (types.ContainerCreateResponse, error) {
|
||||
var response types.ContainerCreateResponse
|
||||
query := url.Values{}
|
||||
if containerName != "" {
|
||||
query.Set("name", containerName)
|
||||
}
|
||||
|
||||
body := configWrapper{
|
||||
Config: config,
|
||||
HostConfig: hostConfig,
|
||||
NetworkingConfig: networkingConfig,
|
||||
}
|
||||
|
||||
serverResp, err := cli.post(ctx, "/containers/create", query, body, nil)
|
||||
if err != nil {
|
||||
if serverResp.statusCode == 404 && strings.Contains(err.Error(), "No such image") {
|
||||
return response, imageNotFoundError{config.Image}
|
||||
}
|
||||
return response, err
|
||||
}
|
||||
|
||||
err = json.NewDecoder(serverResp.body).Decode(&response)
|
||||
ensureReaderClosed(serverResp)
|
||||
return response, err
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ContainerDiff shows differences in a container filesystem since it was started.
|
||||
func (cli *Client) ContainerDiff(ctx context.Context, containerID string) ([]types.ContainerChange, error) {
|
||||
var changes []types.ContainerChange
|
||||
|
||||
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil)
|
||||
if err != nil {
|
||||
return changes, err
|
||||
}
|
||||
|
||||
err = json.NewDecoder(serverResp.body).Decode(&changes)
|
||||
ensureReaderClosed(serverResp)
|
||||
return changes, err
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ContainerExecCreate creates a new exec configuration to run an exec process.
|
||||
func (cli *Client) ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.ContainerExecCreateResponse, error) {
|
||||
var response types.ContainerExecCreateResponse
|
||||
resp, err := cli.post(ctx, "/containers/"+container+"/exec", nil, config, nil)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
err = json.NewDecoder(resp.body).Decode(&response)
|
||||
ensureReaderClosed(resp)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// ContainerExecStart starts an exec process already created in the docker host.
|
||||
func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error {
|
||||
resp, err := cli.post(ctx, "/exec/"+execID+"/start", nil, config, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
||||
|
||||
// ContainerExecAttach attaches a connection to an exec process in the server.
|
||||
// It returns a types.HijackedConnection with the hijacked connection
|
||||
// and the a reader to get output. It's up to the called to close
|
||||
// the hijacked connection by calling types.HijackedResponse.Close.
|
||||
func (cli *Client) ContainerExecAttach(ctx context.Context, execID string, config types.ExecConfig) (types.HijackedResponse, error) {
|
||||
headers := map[string][]string{"Content-Type": {"application/json"}}
|
||||
return cli.postHijacked(ctx, "/exec/"+execID+"/start", nil, config, headers)
|
||||
}
|
||||
|
||||
// ContainerExecInspect returns information about a specific exec process on the docker host.
|
||||
func (cli *Client) ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error) {
|
||||
var response types.ContainerExecInspect
|
||||
resp, err := cli.get(ctx, "/exec/"+execID+"/json", nil, nil)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.body).Decode(&response)
|
||||
ensureReaderClosed(resp)
|
||||
return response, err
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ContainerExport retrieves the raw contents of a container
|
||||
// and returns them as an io.ReadCloser. It's up to the caller
|
||||
// to close the stream.
|
||||
func (cli *Client) ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) {
|
||||
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return serverResp.body, nil
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ContainerInspect returns the container information.
|
||||
func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) {
|
||||
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil)
|
||||
if err != nil {
|
||||
if serverResp.statusCode == http.StatusNotFound {
|
||||
return types.ContainerJSON{}, containerNotFoundError{containerID}
|
||||
}
|
||||
return types.ContainerJSON{}, err
|
||||
}
|
||||
|
||||
var response types.ContainerJSON
|
||||
err = json.NewDecoder(serverResp.body).Decode(&response)
|
||||
ensureReaderClosed(serverResp)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// ContainerInspectWithRaw returns the container information and its raw representation.
|
||||
func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID string, getSize bool) (types.ContainerJSON, []byte, error) {
|
||||
query := url.Values{}
|
||||
if getSize {
|
||||
query.Set("size", "1")
|
||||
}
|
||||
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil)
|
||||
if err != nil {
|
||||
if serverResp.statusCode == http.StatusNotFound {
|
||||
return types.ContainerJSON{}, nil, containerNotFoundError{containerID}
|
||||
}
|
||||
return types.ContainerJSON{}, nil, err
|
||||
}
|
||||
defer ensureReaderClosed(serverResp)
|
||||
|
||||
body, err := ioutil.ReadAll(serverResp.body)
|
||||
if err != nil {
|
||||
return types.ContainerJSON{}, nil, err
|
||||
}
|
||||
|
||||
var response types.ContainerJSON
|
||||
rdr := bytes.NewReader(body)
|
||||
err = json.NewDecoder(rdr).Decode(&response)
|
||||
return response, body, err
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ContainerKill terminates the container process but does not remove the container from the docker host.
|
||||
func (cli *Client) ContainerKill(ctx context.Context, containerID, signal string) error {
|
||||
query := url.Values{}
|
||||
query.Set("signal", signal)
|
||||
|
||||
resp, err := cli.post(ctx, "/containers/"+containerID+"/kill", query, nil, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ContainerList returns the list of containers in the docker host.
|
||||
func (cli *Client) ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
|
||||
query := url.Values{}
|
||||
|
||||
if options.All {
|
||||
query.Set("all", "1")
|
||||
}
|
||||
|
||||
if options.Limit != -1 {
|
||||
query.Set("limit", strconv.Itoa(options.Limit))
|
||||
}
|
||||
|
||||
if options.Since != "" {
|
||||
query.Set("since", options.Since)
|
||||
}
|
||||
|
||||
if options.Before != "" {
|
||||
query.Set("before", options.Before)
|
||||
}
|
||||
|
||||
if options.Size {
|
||||
query.Set("size", "1")
|
||||
}
|
||||
|
||||
if options.Filter.Len() > 0 {
|
||||
filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filter)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query.Set("filters", filterJSON)
|
||||
}
|
||||
|
||||
resp, err := cli.get(ctx, "/containers/json", query, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var containers []types.Container
|
||||
err = json.NewDecoder(resp.body).Decode(&containers)
|
||||
ensureReaderClosed(resp)
|
||||
return containers, err
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
timetypes "github.com/docker/engine-api/types/time"
|
||||
)
|
||||
|
||||
// ContainerLogs returns the logs generated by a container in an io.ReadCloser.
|
||||
// It's up to the caller to close the stream.
|
||||
func (cli *Client) ContainerLogs(ctx context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error) {
|
||||
query := url.Values{}
|
||||
if options.ShowStdout {
|
||||
query.Set("stdout", "1")
|
||||
}
|
||||
|
||||
if options.ShowStderr {
|
||||
query.Set("stderr", "1")
|
||||
}
|
||||
|
||||
if options.Since != "" {
|
||||
ts, err := timetypes.GetTimestamp(options.Since, time.Now())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query.Set("since", ts)
|
||||
}
|
||||
|
||||
if options.Timestamps {
|
||||
query.Set("timestamps", "1")
|
||||
}
|
||||
|
||||
if options.Details {
|
||||
query.Set("details", "1")
|
||||
}
|
||||
|
||||
if options.Follow {
|
||||
query.Set("follow", "1")
|
||||
}
|
||||
query.Set("tail", options.Tail)
|
||||
|
||||
resp, err := cli.get(ctx, "/containers/"+container+"/logs", query, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.body, nil
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package client
|
||||
|
||||
import "golang.org/x/net/context"
|
||||
|
||||
// ContainerPause pauses the main process of a given container without terminating it.
|
||||
func (cli *Client) ContainerPause(ctx context.Context, containerID string) error {
|
||||
resp, err := cli.post(ctx, "/containers/"+containerID+"/pause", nil, nil, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ContainerRemove kills and removes a container from the docker host.
|
||||
func (cli *Client) ContainerRemove(ctx context.Context, containerID string, options types.ContainerRemoveOptions) error {
|
||||
query := url.Values{}
|
||||
if options.RemoveVolumes {
|
||||
query.Set("v", "1")
|
||||
}
|
||||
if options.RemoveLinks {
|
||||
query.Set("link", "1")
|
||||
}
|
||||
|
||||
if options.Force {
|
||||
query.Set("force", "1")
|
||||
}
|
||||
|
||||
resp, err := cli.delete(ctx, "/containers/"+containerID, query, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ContainerRename changes the name of a given container.
|
||||
func (cli *Client) ContainerRename(ctx context.Context, containerID, newContainerName string) error {
|
||||
query := url.Values{}
|
||||
query.Set("name", newContainerName)
|
||||
resp, err := cli.post(ctx, "/containers/"+containerID+"/rename", query, nil, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ContainerResize changes the size of the tty for a container.
|
||||
func (cli *Client) ContainerResize(ctx context.Context, containerID string, options types.ResizeOptions) error {
|
||||
return cli.resize(ctx, "/containers/"+containerID, options.Height, options.Width)
|
||||
}
|
||||
|
||||
// ContainerExecResize changes the size of the tty for an exec process running inside a container.
|
||||
func (cli *Client) ContainerExecResize(ctx context.Context, execID string, options types.ResizeOptions) error {
|
||||
return cli.resize(ctx, "/exec/"+execID, options.Height, options.Width)
|
||||
}
|
||||
|
||||
func (cli *Client) resize(ctx context.Context, basePath string, height, width int) error {
|
||||
query := url.Values{}
|
||||
query.Set("h", strconv.Itoa(height))
|
||||
query.Set("w", strconv.Itoa(width))
|
||||
|
||||
resp, err := cli.post(ctx, basePath+"/resize", query, nil, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
timetypes "github.com/docker/engine-api/types/time"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ContainerRestart stops and starts a container again.
|
||||
// It makes the daemon to wait for the container to be up again for
|
||||
// a specific amount of time, given the timeout.
|
||||
func (cli *Client) ContainerRestart(ctx context.Context, containerID string, timeout *time.Duration) error {
|
||||
query := url.Values{}
|
||||
if timeout != nil {
|
||||
query.Set("t", timetypes.DurationToSecondsString(*timeout))
|
||||
}
|
||||
resp, err := cli.post(ctx, "/containers/"+containerID+"/restart", query, nil, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
)
|
||||
|
||||
// ContainerStart sends a request to the docker daemon to start a container.
|
||||
func (cli *Client) ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error {
|
||||
query := url.Values{}
|
||||
if len(options.CheckpointID) != 0 {
|
||||
query.Set("checkpoint", options.CheckpointID)
|
||||
}
|
||||
|
||||
resp, err := cli.post(ctx, "/containers/"+containerID+"/start", query, nil, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ContainerStats returns near realtime stats for a given container.
|
||||
// It's up to the caller to close the io.ReadCloser returned.
|
||||
func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (io.ReadCloser, error) {
|
||||
query := url.Values{}
|
||||
query.Set("stream", "0")
|
||||
if stream {
|
||||
query.Set("stream", "1")
|
||||
}
|
||||
|
||||
resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.body, err
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
timetypes "github.com/docker/engine-api/types/time"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ContainerStop stops a container without terminating the process.
|
||||
// The process is blocked until the container stops or the timeout expires.
|
||||
func (cli *Client) ContainerStop(ctx context.Context, containerID string, timeout *time.Duration) error {
|
||||
query := url.Values{}
|
||||
if timeout != nil {
|
||||
query.Set("t", timetypes.DurationToSecondsString(*timeout))
|
||||
}
|
||||
resp, err := cli.post(ctx, "/containers/"+containerID+"/stop", query, nil, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ContainerTop shows process information from within a container.
|
||||
func (cli *Client) ContainerTop(ctx context.Context, containerID string, arguments []string) (types.ContainerProcessList, error) {
|
||||
var response types.ContainerProcessList
|
||||
query := url.Values{}
|
||||
if len(arguments) > 0 {
|
||||
query.Set("ps_args", strings.Join(arguments, " "))
|
||||
}
|
||||
|
||||
resp, err := cli.get(ctx, "/containers/"+containerID+"/top", query, nil)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.body).Decode(&response)
|
||||
ensureReaderClosed(resp)
|
||||
return response, err
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package client
|
||||
|
||||
import "golang.org/x/net/context"
|
||||
|
||||
// ContainerUnpause resumes the process execution within a container
|
||||
func (cli *Client) ContainerUnpause(ctx context.Context, containerID string) error {
|
||||
resp, err := cli.post(ctx, "/containers/"+containerID+"/unpause", nil, nil, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/container"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ContainerUpdate updates resources of a container
|
||||
func (cli *Client) ContainerUpdate(ctx context.Context, containerID string, updateConfig container.UpdateConfig) (types.ContainerUpdateResponse, error) {
|
||||
var response types.ContainerUpdateResponse
|
||||
serverResp, err := cli.post(ctx, "/containers/"+containerID+"/update", nil, updateConfig, nil)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
err = json.NewDecoder(serverResp.body).Decode(&response)
|
||||
|
||||
ensureReaderClosed(serverResp)
|
||||
return response, err
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
)
|
||||
|
||||
// ContainerWait pauses execution until a container exits.
|
||||
// It returns the API status code as response of its readiness.
|
||||
func (cli *Client) ContainerWait(ctx context.Context, containerID string) (int, error) {
|
||||
resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", nil, nil, nil)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
defer ensureReaderClosed(resp)
|
||||
|
||||
var res types.ContainerWaitResponse
|
||||
if err := json.NewDecoder(resp.body).Decode(&res); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return res.StatusCode, nil
|
||||
}
|
|
@ -1,208 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrConnectionFailed is an error raised when the connection between the client and the server failed.
|
||||
var ErrConnectionFailed = errors.New("Cannot connect to the Docker daemon. Is the docker daemon running on this host?")
|
||||
|
||||
// ErrorConnectionFailed returns an error with host in the error message when connection to docker daemon failed.
|
||||
func ErrorConnectionFailed(host string) error {
|
||||
return fmt.Errorf("Cannot connect to the Docker daemon at %s. Is the docker daemon running?", host)
|
||||
}
|
||||
|
||||
type notFound interface {
|
||||
error
|
||||
NotFound() bool // Is the error a NotFound error
|
||||
}
|
||||
|
||||
// IsErrNotFound returns true if the error is caused with an
|
||||
// object (image, container, network, volume, …) is not found in the docker host.
|
||||
func IsErrNotFound(err error) bool {
|
||||
te, ok := err.(notFound)
|
||||
return ok && te.NotFound()
|
||||
}
|
||||
|
||||
// imageNotFoundError implements an error returned when an image is not in the docker host.
|
||||
type imageNotFoundError struct {
|
||||
imageID string
|
||||
}
|
||||
|
||||
// NoFound indicates that this error type is of NotFound
|
||||
func (e imageNotFoundError) NotFound() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Error returns a string representation of an imageNotFoundError
|
||||
func (e imageNotFoundError) Error() string {
|
||||
return fmt.Sprintf("Error: No such image: %s", e.imageID)
|
||||
}
|
||||
|
||||
// IsErrImageNotFound returns true if the error is caused
|
||||
// when an image is not found in the docker host.
|
||||
func IsErrImageNotFound(err error) bool {
|
||||
return IsErrNotFound(err)
|
||||
}
|
||||
|
||||
// containerNotFoundError implements an error returned when a container is not in the docker host.
|
||||
type containerNotFoundError struct {
|
||||
containerID string
|
||||
}
|
||||
|
||||
// NoFound indicates that this error type is of NotFound
|
||||
func (e containerNotFoundError) NotFound() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Error returns a string representation of a containerNotFoundError
|
||||
func (e containerNotFoundError) Error() string {
|
||||
return fmt.Sprintf("Error: No such container: %s", e.containerID)
|
||||
}
|
||||
|
||||
// IsErrContainerNotFound returns true if the error is caused
|
||||
// when a container is not found in the docker host.
|
||||
func IsErrContainerNotFound(err error) bool {
|
||||
return IsErrNotFound(err)
|
||||
}
|
||||
|
||||
// networkNotFoundError implements an error returned when a network is not in the docker host.
|
||||
type networkNotFoundError struct {
|
||||
networkID string
|
||||
}
|
||||
|
||||
// NoFound indicates that this error type is of NotFound
|
||||
func (e networkNotFoundError) NotFound() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Error returns a string representation of a networkNotFoundError
|
||||
func (e networkNotFoundError) Error() string {
|
||||
return fmt.Sprintf("Error: No such network: %s", e.networkID)
|
||||
}
|
||||
|
||||
// IsErrNetworkNotFound returns true if the error is caused
|
||||
// when a network is not found in the docker host.
|
||||
func IsErrNetworkNotFound(err error) bool {
|
||||
return IsErrNotFound(err)
|
||||
}
|
||||
|
||||
// volumeNotFoundError implements an error returned when a volume is not in the docker host.
|
||||
type volumeNotFoundError struct {
|
||||
volumeID string
|
||||
}
|
||||
|
||||
// NoFound indicates that this error type is of NotFound
|
||||
func (e volumeNotFoundError) NotFound() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Error returns a string representation of a networkNotFoundError
|
||||
func (e volumeNotFoundError) Error() string {
|
||||
return fmt.Sprintf("Error: No such volume: %s", e.volumeID)
|
||||
}
|
||||
|
||||
// IsErrVolumeNotFound returns true if the error is caused
|
||||
// when a volume is not found in the docker host.
|
||||
func IsErrVolumeNotFound(err error) bool {
|
||||
return IsErrNotFound(err)
|
||||
}
|
||||
|
||||
// unauthorizedError represents an authorization error in a remote registry.
|
||||
type unauthorizedError struct {
|
||||
cause error
|
||||
}
|
||||
|
||||
// Error returns a string representation of an unauthorizedError
|
||||
func (u unauthorizedError) Error() string {
|
||||
return u.cause.Error()
|
||||
}
|
||||
|
||||
// IsErrUnauthorized returns true if the error is caused
|
||||
// when a remote registry authentication fails
|
||||
func IsErrUnauthorized(err error) bool {
|
||||
_, ok := err.(unauthorizedError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// nodeNotFoundError implements an error returned when a node is not found.
|
||||
type nodeNotFoundError struct {
|
||||
nodeID string
|
||||
}
|
||||
|
||||
// Error returns a string representation of a nodeNotFoundError
|
||||
func (e nodeNotFoundError) Error() string {
|
||||
return fmt.Sprintf("Error: No such node: %s", e.nodeID)
|
||||
}
|
||||
|
||||
// NoFound indicates that this error type is of NotFound
|
||||
func (e nodeNotFoundError) NotFound() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsErrNodeNotFound returns true if the error is caused
|
||||
// when a node is not found.
|
||||
func IsErrNodeNotFound(err error) bool {
|
||||
_, ok := err.(nodeNotFoundError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// serviceNotFoundError implements an error returned when a service is not found.
|
||||
type serviceNotFoundError struct {
|
||||
serviceID string
|
||||
}
|
||||
|
||||
// Error returns a string representation of a serviceNotFoundError
|
||||
func (e serviceNotFoundError) Error() string {
|
||||
return fmt.Sprintf("Error: No such service: %s", e.serviceID)
|
||||
}
|
||||
|
||||
// NoFound indicates that this error type is of NotFound
|
||||
func (e serviceNotFoundError) NotFound() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsErrServiceNotFound returns true if the error is caused
|
||||
// when a service is not found.
|
||||
func IsErrServiceNotFound(err error) bool {
|
||||
_, ok := err.(serviceNotFoundError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// taskNotFoundError implements an error returned when a task is not found.
|
||||
type taskNotFoundError struct {
|
||||
taskID string
|
||||
}
|
||||
|
||||
// Error returns a string representation of a taskNotFoundError
|
||||
func (e taskNotFoundError) Error() string {
|
||||
return fmt.Sprintf("Error: No such task: %s", e.taskID)
|
||||
}
|
||||
|
||||
// NoFound indicates that this error type is of NotFound
|
||||
func (e taskNotFoundError) NotFound() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsErrTaskNotFound returns true if the error is caused
|
||||
// when a task is not found.
|
||||
func IsErrTaskNotFound(err error) bool {
|
||||
_, ok := err.(taskNotFoundError)
|
||||
return ok
|
||||
}
|
||||
|
||||
type pluginPermissionDenied struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (e pluginPermissionDenied) Error() string {
|
||||
return "Permission denied while installing plugin " + e.name
|
||||
}
|
||||
|
||||
// IsErrPluginPermissionDenied returns true if the error is caused
|
||||
// when a user denies a plugin's permissions
|
||||
func IsErrPluginPermissionDenied(err error) bool {
|
||||
_, ok := err.(pluginPermissionDenied)
|
||||
return ok
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
timetypes "github.com/docker/engine-api/types/time"
|
||||
)
|
||||
|
||||
// Events returns a stream of events in the daemon in a ReadCloser.
|
||||
// It's up to the caller to close the stream.
|
||||
func (cli *Client) Events(ctx context.Context, options types.EventsOptions) (io.ReadCloser, error) {
|
||||
query := url.Values{}
|
||||
ref := time.Now()
|
||||
|
||||
if options.Since != "" {
|
||||
ts, err := timetypes.GetTimestamp(options.Since, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query.Set("since", ts)
|
||||
}
|
||||
if options.Until != "" {
|
||||
ts, err := timetypes.GetTimestamp(options.Until, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query.Set("until", ts)
|
||||
}
|
||||
if options.Filters.Len() > 0 {
|
||||
filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query.Set("filters", filterJSON)
|
||||
}
|
||||
|
||||
serverResponse, err := cli.get(ctx, "/events", query, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return serverResponse.body, nil
|
||||
}
|
|
@ -1,174 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/go-connections/sockets"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// tlsClientCon holds tls information and a dialed connection.
|
||||
type tlsClientCon struct {
|
||||
*tls.Conn
|
||||
rawConn net.Conn
|
||||
}
|
||||
|
||||
func (c *tlsClientCon) CloseWrite() error {
|
||||
// Go standard tls.Conn doesn't provide the CloseWrite() method so we do it
|
||||
// on its underlying connection.
|
||||
if conn, ok := c.rawConn.(types.CloseWriter); ok {
|
||||
return conn.CloseWrite()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// postHijacked sends a POST request and hijacks the connection.
|
||||
func (cli *Client) postHijacked(ctx context.Context, path string, query url.Values, body interface{}, headers map[string][]string) (types.HijackedResponse, error) {
|
||||
bodyEncoded, err := encodeData(body)
|
||||
if err != nil {
|
||||
return types.HijackedResponse{}, err
|
||||
}
|
||||
|
||||
req, err := cli.newRequest("POST", path, query, bodyEncoded, headers)
|
||||
if err != nil {
|
||||
return types.HijackedResponse{}, err
|
||||
}
|
||||
req.Host = cli.addr
|
||||
|
||||
req.Header.Set("Connection", "Upgrade")
|
||||
req.Header.Set("Upgrade", "tcp")
|
||||
|
||||
conn, err := dial(cli.proto, cli.addr, cli.transport.TLSConfig())
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return types.HijackedResponse{}, fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
|
||||
}
|
||||
return types.HijackedResponse{}, err
|
||||
}
|
||||
|
||||
// When we set up a TCP connection for hijack, there could be long periods
|
||||
// of inactivity (a long running command with no output) that in certain
|
||||
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
||||
// state. Setting TCP KeepAlive on the socket connection will prohibit
|
||||
// ECONNTIMEOUT unless the socket connection truly is broken
|
||||
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||
tcpConn.SetKeepAlive(true)
|
||||
tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
||||
}
|
||||
|
||||
clientconn := httputil.NewClientConn(conn, nil)
|
||||
defer clientconn.Close()
|
||||
|
||||
// Server hijacks the connection, error 'connection closed' expected
|
||||
_, err = clientconn.Do(req)
|
||||
|
||||
rwc, br := clientconn.Hijack()
|
||||
|
||||
return types.HijackedResponse{Conn: rwc, Reader: br}, err
|
||||
}
|
||||
|
||||
func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
|
||||
return tlsDialWithDialer(new(net.Dialer), network, addr, config)
|
||||
}
|
||||
|
||||
// We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in
|
||||
// order to return our custom tlsClientCon struct which holds both the tls.Conn
|
||||
// object _and_ its underlying raw connection. The rationale for this is that
|
||||
// we need to be able to close the write end of the connection when attaching,
|
||||
// which tls.Conn does not provide.
|
||||
func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
|
||||
// We want the Timeout and Deadline values from dialer to cover the
|
||||
// whole process: TCP connection and TLS handshake. This means that we
|
||||
// also need to start our own timers now.
|
||||
timeout := dialer.Timeout
|
||||
|
||||
if !dialer.Deadline.IsZero() {
|
||||
deadlineTimeout := dialer.Deadline.Sub(time.Now())
|
||||
if timeout == 0 || deadlineTimeout < timeout {
|
||||
timeout = deadlineTimeout
|
||||
}
|
||||
}
|
||||
|
||||
var errChannel chan error
|
||||
|
||||
if timeout != 0 {
|
||||
errChannel = make(chan error, 2)
|
||||
time.AfterFunc(timeout, func() {
|
||||
errChannel <- errors.New("")
|
||||
})
|
||||
}
|
||||
|
||||
proxyDialer, err := sockets.DialerFromEnvironment(dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawConn, err := proxyDialer.Dial(network, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// When we set up a TCP connection for hijack, there could be long periods
|
||||
// of inactivity (a long running command with no output) that in certain
|
||||
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
||||
// state. Setting TCP KeepAlive on the socket connection will prohibit
|
||||
// ECONNTIMEOUT unless the socket connection truly is broken
|
||||
if tcpConn, ok := rawConn.(*net.TCPConn); ok {
|
||||
tcpConn.SetKeepAlive(true)
|
||||
tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
||||
}
|
||||
|
||||
colonPos := strings.LastIndex(addr, ":")
|
||||
if colonPos == -1 {
|
||||
colonPos = len(addr)
|
||||
}
|
||||
hostname := addr[:colonPos]
|
||||
|
||||
// If no ServerName is set, infer the ServerName
|
||||
// from the hostname we're connecting to.
|
||||
if config.ServerName == "" {
|
||||
// Make a copy to avoid polluting argument or default.
|
||||
c := *config
|
||||
c.ServerName = hostname
|
||||
config = &c
|
||||
}
|
||||
|
||||
conn := tls.Client(rawConn, config)
|
||||
|
||||
if timeout == 0 {
|
||||
err = conn.Handshake()
|
||||
} else {
|
||||
go func() {
|
||||
errChannel <- conn.Handshake()
|
||||
}()
|
||||
|
||||
err = <-errChannel
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
rawConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This is Docker difference with standard's crypto/tls package: returned a
|
||||
// wrapper which holds both the TLS and raw connections.
|
||||
return &tlsClientCon{conn, rawConn}, nil
|
||||
}
|
||||
|
||||
func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
|
||||
if tlsConfig != nil && proto != "unix" && proto != "npipe" {
|
||||
// Notice this isn't Go standard's tls.Dial function
|
||||
return tlsDial(proto, addr, tlsConfig)
|
||||
}
|
||||
if proto == "npipe" {
|
||||
return sockets.DialPipe(addr, 32*time.Second)
|
||||
}
|
||||
return net.Dial(proto, addr)
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/container"
|
||||
)
|
||||
|
||||
var headerRegexp = regexp.MustCompile(`\ADocker/.+\s\((.+)\)\z`)
|
||||
|
||||
// ImageBuild sends request to the daemon to build images.
|
||||
// The Body in the response implement an io.ReadCloser and it's up to the caller to
|
||||
// close it.
|
||||
func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
|
||||
query, err := imageBuildOptionsToQuery(options)
|
||||
if err != nil {
|
||||
return types.ImageBuildResponse{}, err
|
||||
}
|
||||
|
||||
headers := http.Header(make(map[string][]string))
|
||||
buf, err := json.Marshal(options.AuthConfigs)
|
||||
if err != nil {
|
||||
return types.ImageBuildResponse{}, err
|
||||
}
|
||||
headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
|
||||
headers.Set("Content-Type", "application/tar")
|
||||
|
||||
serverResp, err := cli.postRaw(ctx, "/build", query, buildContext, headers)
|
||||
if err != nil {
|
||||
return types.ImageBuildResponse{}, err
|
||||
}
|
||||
|
||||
osType := getDockerOS(serverResp.header.Get("Server"))
|
||||
|
||||
return types.ImageBuildResponse{
|
||||
Body: serverResp.body,
|
||||
OSType: osType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, error) {
|
||||
query := url.Values{
|
||||
"t": options.Tags,
|
||||
}
|
||||
if options.SuppressOutput {
|
||||
query.Set("q", "1")
|
||||
}
|
||||
if options.RemoteContext != "" {
|
||||
query.Set("remote", options.RemoteContext)
|
||||
}
|
||||
if options.NoCache {
|
||||
query.Set("nocache", "1")
|
||||
}
|
||||
if options.Remove {
|
||||
query.Set("rm", "1")
|
||||
} else {
|
||||
query.Set("rm", "0")
|
||||
}
|
||||
|
||||
if options.ForceRemove {
|
||||
query.Set("forcerm", "1")
|
||||
}
|
||||
|
||||
if options.PullParent {
|
||||
query.Set("pull", "1")
|
||||
}
|
||||
|
||||
if options.Squash {
|
||||
query.Set("squash", "1")
|
||||
}
|
||||
|
||||
if !container.Isolation.IsDefault(options.Isolation) {
|
||||
query.Set("isolation", string(options.Isolation))
|
||||
}
|
||||
|
||||
query.Set("cpusetcpus", options.CPUSetCPUs)
|
||||
query.Set("cpusetmems", options.CPUSetMems)
|
||||
query.Set("cpushares", strconv.FormatInt(options.CPUShares, 10))
|
||||
query.Set("cpuquota", strconv.FormatInt(options.CPUQuota, 10))
|
||||
query.Set("cpuperiod", strconv.FormatInt(options.CPUPeriod, 10))
|
||||
query.Set("memory", strconv.FormatInt(options.Memory, 10))
|
||||
query.Set("memswap", strconv.FormatInt(options.MemorySwap, 10))
|
||||
query.Set("cgroupparent", options.CgroupParent)
|
||||
query.Set("shmsize", strconv.FormatInt(options.ShmSize, 10))
|
||||
query.Set("dockerfile", options.Dockerfile)
|
||||
|
||||
ulimitsJSON, err := json.Marshal(options.Ulimits)
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
query.Set("ulimits", string(ulimitsJSON))
|
||||
|
||||
buildArgsJSON, err := json.Marshal(options.BuildArgs)
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
query.Set("buildargs", string(buildArgsJSON))
|
||||
|
||||
labelsJSON, err := json.Marshal(options.Labels)
|
||||
if err != nil {
|
||||
return query, err
|
||||
}
|
||||
query.Set("labels", string(labelsJSON))
|
||||
return query, nil
|
||||
}
|
||||
|
||||
func getDockerOS(serverHeader string) string {
|
||||
var osType string
|
||||
matches := headerRegexp.FindStringSubmatch(serverHeader)
|
||||
if len(matches) > 0 {
|
||||
osType = matches[1]
|
||||
}
|
||||
return osType
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/reference"
|
||||
)
|
||||
|
||||
// ImageCreate creates a new image based in the parent options.
|
||||
// It returns the JSON content in the response body.
|
||||
func (cli *Client) ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) {
|
||||
repository, tag, err := reference.Parse(parentReference)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := url.Values{}
|
||||
query.Set("fromImage", repository)
|
||||
query.Set("tag", tag)
|
||||
resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.body, nil
|
||||
}
|
||||
|
||||
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
|
||||
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
|
||||
return cli.post(ctx, "/images/create", query, nil, headers)
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ImageHistory returns the changes in an image in history format.
|
||||
func (cli *Client) ImageHistory(ctx context.Context, imageID string) ([]types.ImageHistory, error) {
|
||||
var history []types.ImageHistory
|
||||
serverResp, err := cli.get(ctx, "/images/"+imageID+"/history", url.Values{}, nil)
|
||||
if err != nil {
|
||||
return history, err
|
||||
}
|
||||
|
||||
err = json.NewDecoder(serverResp.body).Decode(&history)
|
||||
ensureReaderClosed(serverResp)
|
||||
return history, err
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/engine-api/types"
|
||||
)
|
||||
|
||||
// ImageImport creates a new image based in the source options.
|
||||
// It returns the JSON content in the response body.
|
||||
func (cli *Client) ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) {
|
||||
if ref != "" {
|
||||
//Check if the given image name can be resolved
|
||||
if _, err := reference.ParseNamed(ref); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
query := url.Values{}
|
||||
query.Set("fromSrc", source.SourceName)
|
||||
query.Set("repo", ref)
|
||||
query.Set("tag", options.Tag)
|
||||
query.Set("message", options.Message)
|
||||
for _, change := range options.Changes {
|
||||
query.Add("changes", change)
|
||||
}
|
||||
|
||||
resp, err := cli.postRaw(ctx, "/images/create", query, source.Source, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.body, nil
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ImageInspectWithRaw returns the image information and its raw representation.
|
||||
func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) {
|
||||
serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", nil, nil)
|
||||
if err != nil {
|
||||
if serverResp.statusCode == http.StatusNotFound {
|
||||
return types.ImageInspect{}, nil, imageNotFoundError{imageID}
|
||||
}
|
||||
return types.ImageInspect{}, nil, err
|
||||
}
|
||||
defer ensureReaderClosed(serverResp)
|
||||
|
||||
body, err := ioutil.ReadAll(serverResp.body)
|
||||
if err != nil {
|
||||
return types.ImageInspect{}, nil, err
|
||||
}
|
||||
|
||||
var response types.ImageInspect
|
||||
rdr := bytes.NewReader(body)
|
||||
err = json.NewDecoder(rdr).Decode(&response)
|
||||
return response, body, err
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ImageList returns a list of images in the docker host.
|
||||
func (cli *Client) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.Image, error) {
|
||||
var images []types.Image
|
||||
query := url.Values{}
|
||||
|
||||
if options.Filters.Len() > 0 {
|
||||
filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
|
||||
if err != nil {
|
||||
return images, err
|
||||
}
|
||||
query.Set("filters", filterJSON)
|
||||
}
|
||||
if options.MatchName != "" {
|
||||
// FIXME rename this parameter, to not be confused with the filters flag
|
||||
query.Set("filter", options.MatchName)
|
||||
}
|
||||
if options.All {
|
||||
query.Set("all", "1")
|
||||
}
|
||||
|
||||
serverResp, err := cli.get(ctx, "/images/json", query, nil)
|
||||
if err != nil {
|
||||
return images, err
|
||||
}
|
||||
|
||||
err = json.NewDecoder(serverResp.body).Decode(&images)
|
||||
ensureReaderClosed(serverResp)
|
||||
return images, err
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
)
|
||||
|
||||
// ImageLoad loads an image in the docker host from the client host.
|
||||
// It's up to the caller to close the io.ReadCloser in the
|
||||
// ImageLoadResponse returned by this function.
|
||||
func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
|
||||
v := url.Values{}
|
||||
v.Set("quiet", "0")
|
||||
if quiet {
|
||||
v.Set("quiet", "1")
|
||||
}
|
||||
headers := map[string][]string{"Content-Type": {"application/x-tar"}}
|
||||
resp, err := cli.postRaw(ctx, "/images/load", v, input, headers)
|
||||
if err != nil {
|
||||
return types.ImageLoadResponse{}, err
|
||||
}
|
||||
return types.ImageLoadResponse{
|
||||
Body: resp.body,
|
||||
JSON: resp.header.Get("Content-Type") == "application/json",
|
||||
}, nil
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/reference"
|
||||
)
|
||||
|
||||
// ImagePull requests the docker host to pull an image from a remote registry.
|
||||
// It executes the privileged function if the operation is unauthorized
|
||||
// and it tries one more time.
|
||||
// It's up to the caller to handle the io.ReadCloser and close it properly.
|
||||
//
|
||||
// FIXME(vdemeester): there is currently used in a few way in docker/docker
|
||||
// - if not in trusted content, ref is used to pass the whole reference, and tag is empty
|
||||
// - if in trusted content, ref is used to pass the reference name, and tag for the digest
|
||||
func (cli *Client) ImagePull(ctx context.Context, ref string, options types.ImagePullOptions) (io.ReadCloser, error) {
|
||||
repository, tag, err := reference.Parse(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := url.Values{}
|
||||
query.Set("fromImage", repository)
|
||||
if tag != "" && !options.All {
|
||||
query.Set("tag", tag)
|
||||
}
|
||||
|
||||
resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth)
|
||||
if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
|
||||
newAuthHeader, privilegeErr := options.PrivilegeFunc()
|
||||
if privilegeErr != nil {
|
||||
return nil, privilegeErr
|
||||
}
|
||||
resp, err = cli.tryImageCreate(ctx, query, newAuthHeader)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.body, nil
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
distreference "github.com/docker/distribution/reference"
|
||||
"github.com/docker/engine-api/types"
|
||||
)
|
||||
|
||||
// ImagePush requests the docker host to push an image to a remote registry.
|
||||
// It executes the privileged function if the operation is unauthorized
|
||||
// and it tries one more time.
|
||||
// It's up to the caller to handle the io.ReadCloser and close it properly.
|
||||
func (cli *Client) ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) {
|
||||
distributionRef, err := distreference.ParseNamed(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical {
|
||||
return nil, errors.New("cannot push a digest reference")
|
||||
}
|
||||
|
||||
var tag = ""
|
||||
if nameTaggedRef, isNamedTagged := distributionRef.(distreference.NamedTagged); isNamedTagged {
|
||||
tag = nameTaggedRef.Tag()
|
||||
}
|
||||
|
||||
query := url.Values{}
|
||||
query.Set("tag", tag)
|
||||
|
||||
resp, err := cli.tryImagePush(ctx, distributionRef.Name(), query, options.RegistryAuth)
|
||||
if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
|
||||
newAuthHeader, privilegeErr := options.PrivilegeFunc()
|
||||
if privilegeErr != nil {
|
||||
return nil, privilegeErr
|
||||
}
|
||||
resp, err = cli.tryImagePush(ctx, distributionRef.Name(), query, newAuthHeader)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.body, nil
|
||||
}
|
||||
|
||||
func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, registryAuth string) (serverResponse, error) {
|
||||
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
|
||||
return cli.post(ctx, "/images/"+imageID+"/push", query, nil, headers)
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ImageRemove removes an image from the docker host.
|
||||
func (cli *Client) ImageRemove(ctx context.Context, imageID string, options types.ImageRemoveOptions) ([]types.ImageDelete, error) {
|
||||
query := url.Values{}
|
||||
|
||||
if options.Force {
|
||||
query.Set("force", "1")
|
||||
}
|
||||
if !options.PruneChildren {
|
||||
query.Set("noprune", "1")
|
||||
}
|
||||
|
||||
resp, err := cli.delete(ctx, "/images/"+imageID, query, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dels []types.ImageDelete
|
||||
err = json.NewDecoder(resp.body).Decode(&dels)
|
||||
ensureReaderClosed(resp)
|
||||
return dels, err
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ImageSave retrieves one or more images from the docker host as an io.ReadCloser.
|
||||
// It's up to the caller to store the images and close the stream.
|
||||
func (cli *Client) ImageSave(ctx context.Context, imageIDs []string) (io.ReadCloser, error) {
|
||||
query := url.Values{
|
||||
"names": imageIDs,
|
||||
}
|
||||
|
||||
resp, err := cli.get(ctx, "/images/get", query, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.body, nil
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"github.com/docker/engine-api/types/registry"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// ImageSearch makes the docker host to search by a term in a remote registry.
|
||||
// The list of results is not sorted in any fashion.
|
||||
func (cli *Client) ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error) {
|
||||
var results []registry.SearchResult
|
||||
query := url.Values{}
|
||||
query.Set("term", term)
|
||||
query.Set("limit", fmt.Sprintf("%d", options.Limit))
|
||||
|
||||
if options.Filters.Len() > 0 {
|
||||
filterJSON, err := filters.ToParam(options.Filters)
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
query.Set("filters", filterJSON)
|
||||
}
|
||||
|
||||
resp, err := cli.tryImageSearch(ctx, query, options.RegistryAuth)
|
||||
if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
|
||||
newAuthHeader, privilegeErr := options.PrivilegeFunc()
|
||||
if privilegeErr != nil {
|
||||
return results, privilegeErr
|
||||
}
|
||||
resp, err = cli.tryImageSearch(ctx, query, newAuthHeader)
|
||||
}
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.body).Decode(&results)
|
||||
ensureReaderClosed(resp)
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (cli *Client) tryImageSearch(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
|
||||
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
|
||||
return cli.get(ctx, "/images/search", query, headers)
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
distreference "github.com/docker/distribution/reference"
|
||||
"github.com/docker/engine-api/types/reference"
|
||||
)
|
||||
|
||||
// ImageTag tags an image in the docker host
|
||||
func (cli *Client) ImageTag(ctx context.Context, imageID, ref string) error {
|
||||
distributionRef, err := distreference.ParseNamed(ref)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error parsing reference: %q is not a valid repository/tag", ref)
|
||||
}
|
||||
|
||||
if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical {
|
||||
return errors.New("refusing to create a tag with a digest reference")
|
||||
}
|
||||
|
||||
tag := reference.GetTagFromNamedRef(distributionRef)
|
||||
|
||||
query := url.Values{}
|
||||
query.Set("repo", distributionRef.Name())
|
||||
query.Set("tag", tag)
|
||||
|
||||
resp, err := cli.post(ctx, "/images/"+imageID+"/tag", query, nil, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Info returns information about the docker server.
|
||||
func (cli *Client) Info(ctx context.Context) (types.Info, error) {
|
||||
var info types.Info
|
||||
serverResp, err := cli.get(ctx, "/info", url.Values{}, nil)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
defer ensureReaderClosed(serverResp)
|
||||
|
||||
if err := json.NewDecoder(serverResp.body).Decode(&info); err != nil {
|
||||
return info, fmt.Errorf("Error reading remote info: %v", err)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/container"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"github.com/docker/engine-api/types/network"
|
||||
"github.com/docker/engine-api/types/registry"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// CommonAPIClient is the common methods between stable and experimental versions of APIClient.
|
||||
type CommonAPIClient interface {
|
||||
ContainerAPIClient
|
||||
ImageAPIClient
|
||||
NodeAPIClient
|
||||
NetworkAPIClient
|
||||
ServiceAPIClient
|
||||
SwarmAPIClient
|
||||
SystemAPIClient
|
||||
VolumeAPIClient
|
||||
ClientVersion() string
|
||||
ServerVersion(ctx context.Context) (types.Version, error)
|
||||
UpdateClientVersion(v string)
|
||||
}
|
||||
|
||||
// ContainerAPIClient defines API client methods for the containers
|
||||
type ContainerAPIClient interface {
|
||||
ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error)
|
||||
ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.ContainerCommitResponse, error)
|
||||
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (types.ContainerCreateResponse, error)
|
||||
ContainerDiff(ctx context.Context, container string) ([]types.ContainerChange, error)
|
||||
ContainerExecAttach(ctx context.Context, execID string, config types.ExecConfig) (types.HijackedResponse, error)
|
||||
ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.ContainerExecCreateResponse, error)
|
||||
ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error)
|
||||
ContainerExecResize(ctx context.Context, execID string, options types.ResizeOptions) error
|
||||
ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error
|
||||
ContainerExport(ctx context.Context, container string) (io.ReadCloser, error)
|
||||
ContainerInspect(ctx context.Context, container string) (types.ContainerJSON, error)
|
||||
ContainerInspectWithRaw(ctx context.Context, container string, getSize bool) (types.ContainerJSON, []byte, error)
|
||||
ContainerKill(ctx context.Context, container, signal string) error
|
||||
ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error)
|
||||
ContainerLogs(ctx context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error)
|
||||
ContainerPause(ctx context.Context, container string) error
|
||||
ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) error
|
||||
ContainerRename(ctx context.Context, container, newContainerName string) error
|
||||
ContainerResize(ctx context.Context, container string, options types.ResizeOptions) error
|
||||
ContainerRestart(ctx context.Context, container string, timeout *time.Duration) error
|
||||
ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error)
|
||||
ContainerStats(ctx context.Context, container string, stream bool) (io.ReadCloser, error)
|
||||
ContainerStart(ctx context.Context, container string, options types.ContainerStartOptions) error
|
||||
ContainerStop(ctx context.Context, container string, timeout *time.Duration) error
|
||||
ContainerTop(ctx context.Context, container string, arguments []string) (types.ContainerProcessList, error)
|
||||
ContainerUnpause(ctx context.Context, container string) error
|
||||
ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (types.ContainerUpdateResponse, error)
|
||||
ContainerWait(ctx context.Context, container string) (int, 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
|
||||
}
|
||||
|
||||
// ImageAPIClient defines API client methods for the images
|
||||
type ImageAPIClient interface {
|
||||
ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error)
|
||||
ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error)
|
||||
ImageHistory(ctx context.Context, image string) ([]types.ImageHistory, error)
|
||||
ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
|
||||
ImageInspectWithRaw(ctx context.Context, image string) (types.ImageInspect, []byte, error)
|
||||
ImageList(ctx context.Context, options types.ImageListOptions) ([]types.Image, error)
|
||||
ImageLoad(ctx context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error)
|
||||
ImagePull(ctx context.Context, ref string, options types.ImagePullOptions) (io.ReadCloser, error)
|
||||
ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error)
|
||||
ImageRemove(ctx context.Context, image string, options types.ImageRemoveOptions) ([]types.ImageDelete, error)
|
||||
ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error)
|
||||
ImageSave(ctx context.Context, images []string) (io.ReadCloser, error)
|
||||
ImageTag(ctx context.Context, image, ref string) error
|
||||
}
|
||||
|
||||
// NetworkAPIClient defines API client methods for the networks
|
||||
type NetworkAPIClient interface {
|
||||
NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error
|
||||
NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
|
||||
NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error
|
||||
NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error)
|
||||
NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error)
|
||||
NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
|
||||
NetworkRemove(ctx context.Context, networkID string) error
|
||||
}
|
||||
|
||||
// NodeAPIClient defines API client methods for the nodes
|
||||
type NodeAPIClient interface {
|
||||
NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error)
|
||||
NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error)
|
||||
NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error
|
||||
NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error
|
||||
}
|
||||
|
||||
// ServiceAPIClient defines API client methods for the services
|
||||
type ServiceAPIClient interface {
|
||||
ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error)
|
||||
ServiceInspectWithRaw(ctx context.Context, serviceID string) (swarm.Service, []byte, error)
|
||||
ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error)
|
||||
ServiceRemove(ctx context.Context, serviceID string) error
|
||||
ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) error
|
||||
TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error)
|
||||
TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error)
|
||||
}
|
||||
|
||||
// SwarmAPIClient defines API client methods for the swarm
|
||||
type SwarmAPIClient interface {
|
||||
SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error)
|
||||
SwarmJoin(ctx context.Context, req swarm.JoinRequest) error
|
||||
SwarmLeave(ctx context.Context, force bool) error
|
||||
SwarmInspect(ctx context.Context) (swarm.Swarm, error)
|
||||
SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags swarm.UpdateFlags) error
|
||||
}
|
||||
|
||||
// SystemAPIClient defines API client methods for the system
|
||||
type SystemAPIClient interface {
|
||||
Events(ctx context.Context, options types.EventsOptions) (io.ReadCloser, error)
|
||||
Info(ctx context.Context) (types.Info, error)
|
||||
RegistryLogin(ctx context.Context, auth types.AuthConfig) (types.AuthResponse, error)
|
||||
}
|
||||
|
||||
// VolumeAPIClient defines API client methods for the volumes
|
||||
type VolumeAPIClient interface {
|
||||
VolumeCreate(ctx context.Context, options types.VolumeCreateRequest) (types.Volume, error)
|
||||
VolumeInspect(ctx context.Context, volumeID string) (types.Volume, error)
|
||||
VolumeInspectWithRaw(ctx context.Context, volumeID string) (types.Volume, []byte, error)
|
||||
VolumeList(ctx context.Context, filter filters.Args) (types.VolumesListResponse, error)
|
||||
VolumeRemove(ctx context.Context, volumeID string, force bool) error
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// +build experimental
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// APIClient is an interface that clients that talk with a docker server must implement.
|
||||
type APIClient interface {
|
||||
CommonAPIClient
|
||||
CheckpointAPIClient
|
||||
PluginAPIClient
|
||||
}
|
||||
|
||||
// CheckpointAPIClient defines API client methods for the checkpoints
|
||||
type CheckpointAPIClient interface {
|
||||
CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error
|
||||
CheckpointDelete(ctx context.Context, container string, checkpointID string) error
|
||||
CheckpointList(ctx context.Context, container string) ([]types.Checkpoint, error)
|
||||
}
|
||||
|
||||
// PluginAPIClient defines API client methods for the plugins
|
||||
type PluginAPIClient interface {
|
||||
PluginList(ctx context.Context) (types.PluginsListResponse, error)
|
||||
PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error
|
||||
PluginEnable(ctx context.Context, name string) error
|
||||
PluginDisable(ctx context.Context, name string) error
|
||||
PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) error
|
||||
PluginPush(ctx context.Context, name string, registryAuth string) error
|
||||
PluginSet(ctx context.Context, name string, args []string) error
|
||||
PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error)
|
||||
}
|
||||
|
||||
// Ensure that Client always implements APIClient.
|
||||
var _ APIClient = &Client{}
|
|
@ -1,11 +0,0 @@
|
|||
// +build !experimental
|
||||
|
||||
package client
|
||||
|
||||
// APIClient is an interface that clients that talk with a docker server must implement.
|
||||
type APIClient interface {
|
||||
CommonAPIClient
|
||||
}
|
||||
|
||||
// Ensure that Client always implements APIClient.
|
||||
var _ APIClient = &Client{}
|
|
@ -1,28 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// RegistryLogin authenticates the docker server with a given docker registry.
|
||||
// It returns UnauthorizerError when the authentication fails.
|
||||
func (cli *Client) RegistryLogin(ctx context.Context, auth types.AuthConfig) (types.AuthResponse, error) {
|
||||
resp, err := cli.post(ctx, "/auth", url.Values{}, auth, nil)
|
||||
|
||||
if resp.statusCode == http.StatusUnauthorized {
|
||||
return types.AuthResponse{}, unauthorizedError{err}
|
||||
}
|
||||
if err != nil {
|
||||
return types.AuthResponse{}, err
|
||||
}
|
||||
|
||||
var response types.AuthResponse
|
||||
err = json.NewDecoder(resp.body).Decode(&response)
|
||||
ensureReaderClosed(resp)
|
||||
return response, err
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/network"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// NetworkConnect connects a container to an existent network in the docker host.
|
||||
func (cli *Client) NetworkConnect(ctx context.Context, networkID, containerID string, config *network.EndpointSettings) error {
|
||||
nc := types.NetworkConnect{
|
||||
Container: containerID,
|
||||
EndpointConfig: config,
|
||||
}
|
||||
resp, err := cli.post(ctx, "/networks/"+networkID+"/connect", nil, nc, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// NetworkCreate creates a new network in the docker host.
|
||||
func (cli *Client) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) {
|
||||
networkCreateRequest := types.NetworkCreateRequest{
|
||||
NetworkCreate: options,
|
||||
Name: name,
|
||||
}
|
||||
var response types.NetworkCreateResponse
|
||||
serverResp, err := cli.post(ctx, "/networks/create", nil, networkCreateRequest, nil)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
json.NewDecoder(serverResp.body).Decode(&response)
|
||||
ensureReaderClosed(serverResp)
|
||||
return response, err
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// NetworkDisconnect disconnects a container from an existent network in the docker host.
|
||||
func (cli *Client) NetworkDisconnect(ctx context.Context, networkID, containerID string, force bool) error {
|
||||
nd := types.NetworkDisconnect{Container: containerID, Force: force}
|
||||
resp, err := cli.post(ctx, "/networks/"+networkID+"/disconnect", nil, nd, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// NetworkInspect returns the information for a specific network configured in the docker host.
|
||||
func (cli *Client) NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error) {
|
||||
networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID)
|
||||
return networkResource, err
|
||||
}
|
||||
|
||||
// NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation.
|
||||
func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error) {
|
||||
var networkResource types.NetworkResource
|
||||
resp, err := cli.get(ctx, "/networks/"+networkID, nil, nil)
|
||||
if err != nil {
|
||||
if resp.statusCode == http.StatusNotFound {
|
||||
return networkResource, nil, networkNotFoundError{networkID}
|
||||
}
|
||||
return networkResource, nil, err
|
||||
}
|
||||
defer ensureReaderClosed(resp)
|
||||
|
||||
body, err := ioutil.ReadAll(resp.body)
|
||||
if err != nil {
|
||||
return networkResource, nil, err
|
||||
}
|
||||
rdr := bytes.NewReader(body)
|
||||
err = json.NewDecoder(rdr).Decode(&networkResource)
|
||||
return networkResource, body, err
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// NetworkList returns the list of networks configured in the docker host.
|
||||
func (cli *Client) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
|
||||
query := url.Values{}
|
||||
if options.Filters.Len() > 0 {
|
||||
filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query.Set("filters", filterJSON)
|
||||
}
|
||||
var networkResources []types.NetworkResource
|
||||
resp, err := cli.get(ctx, "/networks", query, nil)
|
||||
if err != nil {
|
||||
return networkResources, err
|
||||
}
|
||||
err = json.NewDecoder(resp.body).Decode(&networkResources)
|
||||
ensureReaderClosed(resp)
|
||||
return networkResources, err
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package client
|
||||
|
||||
import "golang.org/x/net/context"
|
||||
|
||||
// NetworkRemove removes an existent network from the docker host.
|
||||
func (cli *Client) NetworkRemove(ctx context.Context, networkID string) error {
|
||||
resp, err := cli.delete(ctx, "/networks/"+networkID, nil, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// NodeInspectWithRaw returns the node information.
|
||||
func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) {
|
||||
serverResp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil)
|
||||
if err != nil {
|
||||
if serverResp.statusCode == http.StatusNotFound {
|
||||
return swarm.Node{}, nil, nodeNotFoundError{nodeID}
|
||||
}
|
||||
return swarm.Node{}, nil, err
|
||||
}
|
||||
defer ensureReaderClosed(serverResp)
|
||||
|
||||
body, err := ioutil.ReadAll(serverResp.body)
|
||||
if err != nil {
|
||||
return swarm.Node{}, nil, err
|
||||
}
|
||||
|
||||
var response swarm.Node
|
||||
rdr := bytes.NewReader(body)
|
||||
err = json.NewDecoder(rdr).Decode(&response)
|
||||
return response, body, err
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// NodeList returns the list of nodes.
|
||||
func (cli *Client) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
|
||||
query := url.Values{}
|
||||
|
||||
if options.Filter.Len() > 0 {
|
||||
filterJSON, err := filters.ToParam(options.Filter)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query.Set("filters", filterJSON)
|
||||
}
|
||||
|
||||
resp, err := cli.get(ctx, "/nodes", query, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var nodes []swarm.Node
|
||||
err = json.NewDecoder(resp.body).Decode(&nodes)
|
||||
ensureReaderClosed(resp)
|
||||
return nodes, err
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/docker/engine-api/types"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// NodeRemove removes a Node.
|
||||
func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error {
|
||||
query := url.Values{}
|
||||
if options.Force {
|
||||
query.Set("force", "1")
|
||||
}
|
||||
|
||||
resp, err := cli.delete(ctx, "/nodes/"+nodeID, query, nil)
|
||||
ensureReaderClosed(resp)
|
||||
return err
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue