merge master

This commit is contained in:
Manuel Vogel 2018-07-16 11:43:33 +02:00
commit 64e491f7b9
No known key found for this signature in database
GPG key ID: 533006C7B61DB1CE
57 changed files with 694 additions and 7750 deletions

3
.gitignore vendored
View file

@ -31,3 +31,6 @@ website/vendor
!command/test-fixtures/**/.terraform/
scripts/testing/auth
scripts/testing/certs
# build outputs
results

View file

@ -25,10 +25,11 @@ install:
- sudo service docker restart
script:
- make test
- make testacc
- make vendor-status
- make vet
- make test
- make testacc
- make compile
- make website-test
branches:

View file

@ -1,13 +1,14 @@
## 1.0.0 (Unreleased)
## 1.0.1 (Unreleased)
## 1.0.0 (July 03, 2018)
NOTES:
* Update `go-dockerclient` to `bf3bc17bb` [#46](https://github.com/terraform-providers/terraform-provider-docker/pull/46)
* The `links` property on `resource_docker_container` is now marked as deprecated [#47](https://github.com/terraform-providers/terraform-provider-docker/pull/47)
FEATURES:
* Add `swarm` capabilities [GH-29] [#40](https://github.com/terraform-providers/terraform-provider-docker/pull/40) with fixes [#66](https://github.com/terraform-providers/terraform-provider-docker/pull/66) up to Docker `18.03.1` and API Version `1.37` [GH-64]
* Add `swarm` capabilities ([#29](https://github.com/terraform-providers/terraform-provider-docker/issues/29)] [#40](https://github.com/terraform-providers/terraform-provider-docker/pull/40) with fixes [#66](https://github.com/terraform-providers/terraform-provider-docker/pull/66) up to Docker `18.03.1` and API Version `1.37` [[#64](https://github.com/terraform-providers/terraform-provider-docker/issues/64))
* Add ability to upload executable files [#55](https://github.com/terraform-providers/terraform-provider-docker/pull/55)
* Add support to attach devices to containers [GH-30] [#54](https://github.com/terraform-providers/terraform-provider-docker/pull/54)
* Add support to attach devices to containers [[#30](https://github.com/terraform-providers/terraform-provider-docker/issues/30)] [#54](https://github.com/terraform-providers/terraform-provider-docker/pull/54)
* Add Ulimits to containers [#35](https://github.com/terraform-providers/terraform-provider-docker/pull/35)
IMPROVEMENTS:
@ -18,7 +19,7 @@ IMPROVEMENTS:
* Add prefix `library` only to official images in the path [#27](https://github.com/terraform-providers/terraform-provider-docker/pull/27)
BUG FIXES
* Update documentation for private registries [GH-45]
* Update documentation for private registries ([#45](https://github.com/terraform-providers/terraform-provider-docker/issues/45))
## 0.1.1 (November 21, 2017)

View file

@ -16,6 +16,9 @@ test: fmtcheck
testacc: fmtcheck
@sh -c "'$(CURDIR)/scripts/runAccTests.sh'"
compile: fmtcheck
@sh -c "'$(CURDIR)/scripts/compile.sh'"
vet:
@echo "go vet ."
@go vet $$(go list ./... | grep -v vendor/) ; if [ $$? -eq 1 ]; then \

View file

@ -5,12 +5,15 @@ import (
"path/filepath"
"strings"
dc "github.com/fsouza/go-dockerclient"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
)
// DockerConfig is the structure that stores the configuration to talk to a
const apiVersion = "1.37"
// Config is the structure that stores the configuration to talk to a
// Docker API compatible host.
type DockerConfig struct {
type Config struct {
Host string
Ca string
Cert string
@ -18,8 +21,8 @@ type DockerConfig struct {
CertPath string
}
// NewClient() returns a new Docker client.
func (c *DockerConfig) NewClient() (*dc.Client, error) {
// NewClient returns a new Docker client.
func (c *Config) NewClient() (*client.Client, error) {
if c.Ca != "" || c.Cert != "" || c.Key != "" {
if c.Ca == "" || c.Cert == "" || c.Key == "" {
return nil, fmt.Errorf("ca_material, cert_material, and key_material must be specified")
@ -29,7 +32,11 @@ func (c *DockerConfig) NewClient() (*dc.Client, error) {
return nil, fmt.Errorf("cert_path must not be specified")
}
return dc.NewTLSClientFromBytes(c.Host, []byte(c.Cert), []byte(c.Key), []byte(c.Ca))
return client.NewClientWithOpts(
client.WithHost(c.Host),
client.WithTLSClientConfig(c.Ca, c.Cert, c.Key),
client.WithVersion(apiVersion),
)
}
if c.CertPath != "" {
@ -37,22 +44,29 @@ func (c *DockerConfig) NewClient() (*dc.Client, error) {
ca := filepath.Join(c.CertPath, "ca.pem")
cert := filepath.Join(c.CertPath, "cert.pem")
key := filepath.Join(c.CertPath, "key.pem")
return dc.NewTLSClient(c.Host, cert, key, ca)
return client.NewClientWithOpts(
client.WithHost(c.Host),
client.WithTLSClientConfig(ca, cert, key),
client.WithVersion(apiVersion),
)
}
// If there is no cert information, then just return the direct client
return dc.NewClient(c.Host)
return client.NewClientWithOpts(
client.WithHost(c.Host),
client.WithVersion(apiVersion),
)
}
// Data structure for holding data that we fetch from Docker.
type Data struct {
DockerImages map[string]*dc.APIImages
DockerImages map[string]*types.ImageSummary
}
// ProviderConfig for the custom registry provider
type ProviderConfig struct {
DockerClient *dc.Client
AuthConfigs *dc.AuthConfigurations
DockerClient *client.Client
AuthConfigs *AuthConfigs
}
// The registry address can be referenced in various places (registry auth, docker config file, image name)

View file

@ -171,10 +171,10 @@ func getImageDigest(registry, image, tag, username, password string, fallback bo
}
return digestResponse.Header.Get("Docker-Content-Digest"), nil
} else {
return "", fmt.Errorf("Bad credentials: " + resp.Status)
}
return "", fmt.Errorf("Bad credentials: " + resp.Status)
// Some unexpected status was given, return an error
default:
return "", fmt.Errorf("Got bad response from registry: " + resp.Status)

View file

@ -1,12 +1,18 @@
package docker
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/user"
"strings"
dc "github.com/fsouza/go-dockerclient"
"github.com/docker/docker/api/types"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
@ -107,7 +113,7 @@ func Provider() terraform.ResourceProvider {
}
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config := DockerConfig{
config := Config{
Host: d.Get("host").(string),
Ca: d.Get("ca_material").(string),
Cert: d.Get("cert_material").(string),
@ -120,12 +126,13 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
return nil, fmt.Errorf("Error initializing Docker client: %s", err)
}
err = client.Ping()
ctx := context.Background()
_, err = client.Ping(ctx)
if err != nil {
return nil, fmt.Errorf("Error pinging Docker server: %s", err)
}
authConfigs := &dc.AuthConfigurations{}
authConfigs := &AuthConfigs{}
if v, ok := d.GetOk("registry_auth"); ok {
authConfigs, err = providerSetToRegistryAuth(v.(*schema.Set))
@ -143,15 +150,31 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
return &providerConfig, nil
}
// ErrCannotParseDockercfg is the error returned by NewAuthConfigurations when the dockercfg cannot be parsed.
var ErrCannotParseDockercfg = errors.New("Failed to read authentication from dockercfg")
// AuthConfigs represents authentication options to use for the
// PushImage method accommodating the new X-Registry-Config header
type AuthConfigs struct {
Configs map[string]types.AuthConfig `json:"configs"`
}
// dockerConfig represents a registry authentation configuration from the
// .dockercfg file.
type dockerConfig struct {
Auth string `json:"auth"`
Email string `json:"email"`
}
// Take the given registry_auth schemas and return a map of registry auth configurations
func providerSetToRegistryAuth(authSet *schema.Set) (*dc.AuthConfigurations, error) {
authConfigs := dc.AuthConfigurations{
Configs: make(map[string]dc.AuthConfiguration),
func providerSetToRegistryAuth(authSet *schema.Set) (*AuthConfigs, error) {
authConfigs := AuthConfigs{
Configs: make(map[string]types.AuthConfig),
}
for _, authInt := range authSet.List() {
auth := authInt.(map[string]interface{})
authConfig := dc.AuthConfiguration{}
authConfig := types.AuthConfig{}
authConfig.ServerAddress = normalizeRegistryAddress(auth["address"].(string))
// For each registry_auth block, generate an AuthConfiguration using either
@ -174,7 +197,7 @@ func providerSetToRegistryAuth(authSet *schema.Set) (*dc.AuthConfigurations, err
return nil, fmt.Errorf("Error opening docker registry config file: %v", err)
}
auths, err := dc.NewAuthConfigurations(r)
auths, err := newAuthConfigurations(r)
if err != nil {
return nil, fmt.Errorf("Error parsing docker registry config json: %v", err)
}
@ -199,3 +222,68 @@ func providerSetToRegistryAuth(authSet *schema.Set) (*dc.AuthConfigurations, err
return &authConfigs, nil
}
// newAuthConfigurations returns AuthConfigs from a JSON encoded string in the
// same format as the .dockercfg file.
func newAuthConfigurations(r io.Reader) (*AuthConfigs, error) {
var auth *AuthConfigs
confs, err := parseDockerConfig(r)
if err != nil {
return nil, err
}
auth, err = authConfigs(confs)
if err != nil {
return nil, err
}
return auth, nil
}
// parseDockerConfig parses the docker config file for auths
func parseDockerConfig(r io.Reader) (map[string]dockerConfig, error) {
buf := new(bytes.Buffer)
buf.ReadFrom(r)
byteData := buf.Bytes()
confsWrapper := struct {
Auths map[string]dockerConfig `json:"auths"`
}{}
if err := json.Unmarshal(byteData, &confsWrapper); err == nil {
if len(confsWrapper.Auths) > 0 {
return confsWrapper.Auths, nil
}
}
var confs map[string]dockerConfig
if err := json.Unmarshal(byteData, &confs); err != nil {
return nil, err
}
return confs, nil
}
// authConfigs converts a dockerConfigs map to a AuthConfigs object.
func authConfigs(confs map[string]dockerConfig) (*AuthConfigs, error) {
c := &AuthConfigs{
Configs: make(map[string]types.AuthConfig),
}
for reg, conf := range confs {
if conf.Auth == "" {
continue
}
data, err := base64.StdEncoding.DecodeString(conf.Auth)
if err != nil {
return nil, err
}
userpass := strings.SplitN(string(data), ":", 2)
if len(userpass) != 2 {
return nil, ErrCannotParseDockercfg
}
c.Configs[reg] = types.AuthConfig{
Email: conf.Email,
Username: userpass[0],
Password: userpass[1],
ServerAddress: reg,
Auth: conf.Auth,
}
}
return c, nil
}

View file

@ -4,8 +4,9 @@ import (
"encoding/base64"
"log"
"context"
"github.com/docker/docker/api/types/swarm"
dc "github.com/fsouza/go-dockerclient"
"github.com/hashicorp/terraform/helper/schema"
)
@ -39,16 +40,14 @@ func resourceDockerConfigCreate(d *schema.ResourceData, meta interface{}) error
client := meta.(*ProviderConfig).DockerClient
data, _ := base64.StdEncoding.DecodeString(d.Get("data").(string))
createConfigOpts := dc.CreateConfigOptions{
ConfigSpec: swarm.ConfigSpec{
Annotations: swarm.Annotations{
Name: d.Get("name").(string),
},
Data: data,
configSpec := swarm.ConfigSpec{
Annotations: swarm.Annotations{
Name: d.Get("name").(string),
},
Data: data,
}
config, err := client.CreateConfig(createConfigOpts)
config, err := client.ConfigCreate(context.Background(), configSpec)
if err != nil {
return err
}
@ -59,15 +58,12 @@ func resourceDockerConfigCreate(d *schema.ResourceData, meta interface{}) error
func resourceDockerConfigRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ProviderConfig).DockerClient
config, err := client.InspectConfig(d.Id())
config, _, err := client.ConfigInspectWithRaw(context.Background(), d.Id())
if err != nil {
if _, ok := err.(*dc.NoSuchConfig); ok {
log.Printf("[WARN] Config (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}
return err
log.Printf("[WARN] Config (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}
d.SetId(config.ID)
return nil
@ -75,9 +71,7 @@ func resourceDockerConfigRead(d *schema.ResourceData, meta interface{}) error {
func resourceDockerConfigDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ProviderConfig).DockerClient
err := client.RemoveConfig(dc.RemoveConfigOptions{
ID: d.Id(),
})
err := client.ConfigRemove(context.Background(), d.Id())
if err != nil {
return err
}

View file

@ -4,6 +4,7 @@ import (
"fmt"
"testing"
"context"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
@ -82,9 +83,9 @@ func testCheckDockerConfigDestroy(s *terraform.State) error {
}
id := rs.Primary.Attributes["id"]
config, err := client.InspectConfig(id)
_, _, err := client.ConfigInspectWithRaw(context.Background(), id)
if err == nil || config != nil {
if err == nil {
return fmt.Errorf("Config with id '%s' still exists", id)
}
return nil

View file

@ -1,10 +1,6 @@
package docker
import (
"fmt"
"regexp"
"github.com/hashicorp/terraform/helper/schema"
)
@ -443,13 +439,3 @@ func resourceDockerContainer() *schema.Resource {
},
}
}
func validateDockerContainerPath(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[a-zA-Z]:\\|^/`).MatchString(value) {
errors = append(errors, fmt.Errorf("%q must be an absolute path", k))
}
return
}

View file

@ -8,8 +8,15 @@ import (
"strconv"
"time"
dc "github.com/fsouza/go-dockerclient"
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
"github.com/hashicorp/terraform/helper/schema"
"math/rand"
)
var (
@ -33,27 +40,19 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
image = image + ":latest"
}
// The awesome, wonderful, splendiferous, sensical
// Docker API now lets you specify a HostConfig in
// CreateContainerOptions, but in my testing it still only
// actually applies HostConfig options set in StartContainer.
// How cool is that?
createOpts := dc.CreateContainerOptions{
Name: d.Get("name").(string),
Config: &dc.Config{
Image: image,
Hostname: d.Get("hostname").(string),
Domainname: d.Get("domainname").(string),
},
config := &container.Config{
Image: image,
Hostname: d.Get("hostname").(string),
Domainname: d.Get("domainname").(string),
}
if v, ok := d.GetOk("env"); ok {
createOpts.Config.Env = stringSetToStringSlice(v.(*schema.Set))
config.Env = stringSetToStringSlice(v.(*schema.Set))
}
if v, ok := d.GetOk("command"); ok {
createOpts.Config.Cmd = stringListToStringSlice(v.([]interface{}))
for _, v := range createOpts.Config.Cmd {
config.Cmd = stringListToStringSlice(v.([]interface{}))
for _, v := range config.Cmd {
if v == "" {
return fmt.Errorf("values for command may not be empty")
}
@ -61,21 +60,21 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
}
if v, ok := d.GetOk("entrypoint"); ok {
createOpts.Config.Entrypoint = stringListToStringSlice(v.([]interface{}))
config.Entrypoint = stringListToStringSlice(v.([]interface{}))
}
if v, ok := d.GetOk("user"); ok {
createOpts.Config.User = v.(string)
config.User = v.(string)
}
exposedPorts := map[dc.Port]struct{}{}
portBindings := map[dc.Port][]dc.PortBinding{}
exposedPorts := map[nat.Port]struct{}{}
portBindings := map[nat.Port][]nat.PortBinding{}
if v, ok := d.GetOk("ports"); ok {
exposedPorts, portBindings = portSetToDockerPorts(v.(*schema.Set))
}
if len(exposedPorts) != 0 {
createOpts.Config.ExposedPorts = exposedPorts
config.ExposedPorts = exposedPorts
}
extraHosts := []string{}
@ -83,7 +82,7 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
extraHosts = extraHostsSetToDockerExtraHosts(v.(*schema.Set))
}
extraUlimits := []dc.ULimit{}
extraUlimits := []*units.Ulimit{}
if v, ok := d.GetOk("ulimit"); ok {
extraUlimits = ulimitsToDockerUlimits(v.(*schema.Set))
}
@ -98,21 +97,21 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
}
}
if len(volumes) != 0 {
createOpts.Config.Volumes = volumes
config.Volumes = volumes
}
if v, ok := d.GetOk("labels"); ok {
createOpts.Config.Labels = mapTypeMapValsToString(v.(map[string]interface{}))
config.Labels = mapTypeMapValsToString(v.(map[string]interface{}))
}
hostConfig := &dc.HostConfig{
hostConfig := &container.HostConfig{
Privileged: d.Get("privileged").(bool),
PublishAllPorts: d.Get("publish_all_ports").(bool),
RestartPolicy: dc.RestartPolicy{
RestartPolicy: container.RestartPolicy{
Name: d.Get("restart").(string),
MaximumRetryCount: d.Get("max_retry_count").(int),
},
LogConfig: dc.LogConfig{
LogConfig: container.LogConfig{
Type: d.Get("log_driver").(string),
},
}
@ -182,36 +181,29 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
hostConfig.LogConfig.Config = mapTypeMapValsToString(v.(map[string]interface{}))
}
networkingConfig := &network.NetworkingConfig{}
if v, ok := d.GetOk("network_mode"); ok {
hostConfig.NetworkMode = v.(string)
hostConfig.NetworkMode = container.NetworkMode(v.(string))
}
createOpts.HostConfig = hostConfig
var retContainer container.ContainerCreateCreatedBody
var retContainer *dc.Container
if retContainer, err = client.CreateContainer(createOpts); err != nil {
if retContainer, err = client.ContainerCreate(context.Background(), config, hostConfig, networkingConfig, d.Get("name").(string)); err != nil {
return fmt.Errorf("Unable to create container: %s", err)
}
if retContainer == nil {
return fmt.Errorf("Returned container is nil")
}
d.SetId(retContainer.ID)
if v, ok := d.GetOk("networks"); ok {
var connectionOpts dc.NetworkConnectionOptions
endpointConfig := &network.EndpointSettings{}
if v, ok := d.GetOk("network_alias"); ok {
endpointConfig := &dc.EndpointConfig{}
endpointConfig.Aliases = stringSetToStringSlice(v.(*schema.Set))
connectionOpts = dc.NetworkConnectionOptions{Container: retContainer.ID, EndpointConfig: endpointConfig}
} else {
connectionOpts = dc.NetworkConnectionOptions{Container: retContainer.ID}
}
for _, rawNetwork := range v.(*schema.Set).List() {
network := rawNetwork.(string)
if err := client.ConnectNetwork(network, connectionOpts); err != nil {
return fmt.Errorf("Unable to connect to network '%s': %s", network, err)
networkID := rawNetwork.(string)
if err := client.NetworkConnect(context.Background(), networkID, retContainer.ID, endpointConfig); err != nil {
return fmt.Errorf("Unable to connect to network '%s': %s", networkID, err)
}
}
}
@ -246,19 +238,18 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err
return fmt.Errorf("Error creating tar archive: %s", err)
}
uploadOpts := dc.UploadToContainerOptions{
InputStream: bytes.NewReader(buf.Bytes()),
Path: "/",
}
if err := client.UploadToContainer(retContainer.ID, uploadOpts); err != nil {
dstPath := "/"
uploadContent := bytes.NewReader(buf.Bytes())
options := types.CopyToContainerOptions{}
if err := client.CopyToContainer(context.Background(), retContainer.ID, dstPath, uploadContent, options); err != nil {
return fmt.Errorf("Unable to upload volume content: %s", err)
}
}
}
creationTime = time.Now()
if err := client.StartContainer(retContainer.ID, nil); err != nil {
options := types.ContainerStartOptions{}
if err := client.ContainerStart(context.Background(), retContainer.ID, options); err != nil {
return fmt.Errorf("Unable to start container: %s", err)
}
@ -278,7 +269,7 @@ func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error
return nil
}
var container *dc.Container
var container types.ContainerJSON
// TODO fix this with statefunc
loops := 1 // if it hasn't just been created, don't delay
@ -288,7 +279,7 @@ func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error
sleepTime := 500 * time.Millisecond
for i := loops; i > 0; i-- {
container, err = client.InspectContainer(apiContainer.ID)
container, err = client.ContainerInspect(context.Background(), apiContainer.ID)
if err != nil {
return fmt.Errorf("Error inspecting container %s: %s", apiContainer.ID, err)
}
@ -302,7 +293,11 @@ func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error
return resourceDockerContainerDelete(d, meta)
}
if container.State.FinishedAt.After(creationTime) {
finishTime, err := time.Parse(time.RFC3339, container.State.FinishedAt)
if err != nil {
return fmt.Errorf("Container finish time could not be parsed: %s", container.State.FinishedAt)
}
if finishTime.After(creationTime) {
// It exited immediately, so error out so dependent containers
// aren't started
resourceDockerContainerDelete(d, meta)
@ -338,19 +333,20 @@ func resourceDockerContainerDelete(d *schema.ResourceData, meta interface{}) err
// Stop the container before removing if destroy_grace_seconds is defined
if d.Get("destroy_grace_seconds").(int) > 0 {
var timeout = uint(d.Get("destroy_grace_seconds").(int))
if err := client.StopContainer(d.Id(), timeout); err != nil {
mapped := int32(d.Get("destroy_grace_seconds").(int))
timeoutInSeconds := rand.Int31n(mapped)
timeout := time.Duration(time.Duration(timeoutInSeconds) * time.Second)
if err := client.ContainerStop(context.Background(), d.Id(), &timeout); err != nil {
return fmt.Errorf("Error stopping container %s: %s", d.Id(), err)
}
}
removeOpts := dc.RemoveContainerOptions{
ID: d.Id(),
removeOpts := types.ContainerRemoveOptions{
RemoveVolumes: true,
Force: true,
}
if err := client.RemoveContainer(removeOpts); err != nil {
if err := client.ContainerRemove(context.Background(), d.Id(), removeOpts); err != nil {
return fmt.Errorf("Error deleting container %s: %s", d.Id(), err)
}
@ -398,8 +394,8 @@ func mapTypeMapValsToStringSlice(typeMap map[string]interface{}) []string {
return mapped
}
func fetchDockerContainer(ID string, client *dc.Client) (*dc.APIContainers, error) {
apiContainers, err := client.ListContainers(dc.ListContainersOptions{All: true})
func fetchDockerContainer(ID string, client *client.Client) (*types.Container, error) {
apiContainers, err := client.ContainerList(context.Background(), types.ContainerListOptions{All: true})
if err != nil {
return nil, fmt.Errorf("Error fetching container information from Docker: %s\n", err)
@ -414,23 +410,23 @@ func fetchDockerContainer(ID string, client *dc.Client) (*dc.APIContainers, erro
return nil, nil
}
func portSetToDockerPorts(ports *schema.Set) (map[dc.Port]struct{}, map[dc.Port][]dc.PortBinding) {
retExposedPorts := map[dc.Port]struct{}{}
retPortBindings := map[dc.Port][]dc.PortBinding{}
func portSetToDockerPorts(ports *schema.Set) (map[nat.Port]struct{}, map[nat.Port][]nat.PortBinding) {
retExposedPorts := map[nat.Port]struct{}{}
retPortBindings := map[nat.Port][]nat.PortBinding{}
for _, portInt := range ports.List() {
port := portInt.(map[string]interface{})
internal := port["internal"].(int)
protocol := port["protocol"].(string)
exposedPort := dc.Port(strconv.Itoa(internal) + "/" + protocol)
exposedPort := nat.Port(strconv.Itoa(internal) + "/" + protocol)
retExposedPorts[exposedPort] = struct{}{}
external, extOk := port["external"].(int)
ip, ipOk := port["ip"].(string)
if extOk {
portBinding := dc.PortBinding{
portBinding := nat.PortBinding{
HostPort: strconv.Itoa(external),
}
if ipOk {
@ -443,12 +439,12 @@ func portSetToDockerPorts(ports *schema.Set) (map[dc.Port]struct{}, map[dc.Port]
return retExposedPorts, retPortBindings
}
func ulimitsToDockerUlimits(extraUlimits *schema.Set) []dc.ULimit {
retExtraUlimits := []dc.ULimit{}
func ulimitsToDockerUlimits(extraUlimits *schema.Set) []*units.Ulimit {
retExtraUlimits := []*units.Ulimit{}
for _, ulimitInt := range extraUlimits.List() {
ulimits := ulimitInt.(map[string]interface{})
u := dc.ULimit{
u := &units.Ulimit{
Name: ulimits["name"].(string),
Soft: int64(ulimits["soft"].(int)),
Hard: int64(ulimits["hard"].(int)),
@ -508,8 +504,8 @@ func volumeSetToDockerVolumes(volumes *schema.Set) (map[string]struct{}, []strin
return retVolumeMap, retHostConfigBinds, retVolumeFromContainers, nil
}
func deviceSetToDockerDevices(devices *schema.Set) []dc.Device {
retDevices := []dc.Device{}
func deviceSetToDockerDevices(devices *schema.Set) []container.DeviceMapping {
retDevices := []container.DeviceMapping{}
for _, deviceInt := range devices.List() {
deviceMap := deviceInt.(map[string]interface{})
hostPath := deviceMap["host_path"].(string)
@ -524,7 +520,7 @@ func deviceSetToDockerDevices(devices *schema.Set) []dc.Device {
permissions = "rwm"
}
device := dc.Device{
device := container.DeviceMapping{
PathOnHost: hostPath,
PathInContainer: containerPath,
CgroupPermissions: permissions,

View file

@ -4,18 +4,18 @@ import (
"archive/tar"
"bytes"
"fmt"
"os"
"strconv"
"strings"
"testing"
dc "github.com/fsouza/go-dockerclient"
"context"
"github.com/docker/docker/api/types"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccDockerContainer_basic(t *testing.T) {
var c dc.Container
var c types.ContainerJSON
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
@ -54,7 +54,7 @@ func TestAccDockerContainerPath_validation(t *testing.T) {
}
func TestAccDockerContainer_volume(t *testing.T) {
var c dc.Container
var c types.ContainerJSON
testCheck := func(*terraform.State) error {
if len(c.Mounts) != 1 {
@ -96,7 +96,7 @@ func TestAccDockerContainer_volume(t *testing.T) {
}
func TestAccDockerContainer_customized(t *testing.T) {
var c dc.Container
var c types.ContainerJSON
testCheck := func(*terraform.State) error {
if len(c.Config.Entrypoint) < 3 ||
@ -257,24 +257,18 @@ func TestAccDockerContainer_customized(t *testing.T) {
}
func TestAccDockerContainer_upload(t *testing.T) {
var c dc.Container
var c types.ContainerJSON
testCheck := func(*terraform.State) error {
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
buf := new(bytes.Buffer)
opts := dc.DownloadFromContainerOptions{
OutputStream: buf,
Path: "/terraform/test.txt",
}
if err := client.DownloadFromContainer(c.ID, opts); err != nil {
srcPath := "/terraform/test.txt"
r, _, err := client.CopyFromContainer(context.Background(), c.ID, srcPath)
if err != nil {
return fmt.Errorf("Unable to download a file from container: %s", err)
}
r := bytes.NewReader(buf.Bytes())
tr := tar.NewReader(r)
if header, err := tr.Next(); err != nil {
return fmt.Errorf("Unable to read content of tar archive: %s", err)
} else {
@ -311,43 +305,32 @@ func TestAccDockerContainer_upload(t *testing.T) {
}
func TestAccDockerContainer_device(t *testing.T) {
var c dc.Container
var c types.ContainerJSON
testCheck := func(*terraform.State) error {
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
createExecOpts := dc.CreateExecOptions{
Cmd: []string{"dd", "if=/dev/zero_test", "of=/tmp/test.txt", "count=10", "bs=1"},
Container: c.ID,
createExecOpts := types.ExecConfig{
Cmd: []string{"dd", "if=/dev/zero_test", "of=/tmp/test.txt", "count=10", "bs=1"},
}
exec, err := client.CreateExec(createExecOpts)
exec, err := client.ContainerExecCreate(context.Background(), c.ID, createExecOpts)
if err != nil {
return fmt.Errorf("Unable to create a exec instance on container: %s", err)
}
startExecOpts := dc.StartExecOptions{
OutputStream: os.Stdout,
ErrorStream: os.Stdout,
}
if err := client.StartExec(exec.ID, startExecOpts); err != nil {
startExecOpts := types.ExecStartCheck{}
if err := client.ContainerExecStart(context.Background(), exec.ID, startExecOpts); err != nil {
return fmt.Errorf("Unable to run exec a instance on container: %s", err)
}
buf := new(bytes.Buffer)
downloadFileOpts := dc.DownloadFromContainerOptions{
OutputStream: buf,
Path: "/tmp/test.txt",
}
if err := client.DownloadFromContainer(c.ID, downloadFileOpts); err != nil {
srcPath := "/tmp/test.txt"
out, _, err := client.CopyFromContainer(context.Background(), c.ID, srcPath)
if err != nil {
return fmt.Errorf("Unable to download a file from container: %s", err)
}
r := bytes.NewReader(buf.Bytes())
tr := tar.NewReader(r)
tr := tar.NewReader(out)
if _, err := tr.Next(); err != nil {
return fmt.Errorf("Unable to read content of tar archive: %s", err)
}
@ -383,7 +366,7 @@ func TestAccDockerContainer_device(t *testing.T) {
})
}
func testAccContainerRunning(n string, container *dc.Container) resource.TestCheckFunc {
func testAccContainerRunning(n string, container *types.ContainerJSON) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
@ -395,18 +378,18 @@ func testAccContainerRunning(n string, container *dc.Container) resource.TestChe
}
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
containers, err := client.ListContainers(dc.ListContainersOptions{})
containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{})
if err != nil {
return err
}
for _, c := range containers {
if c.ID == rs.Primary.ID {
inspected, err := client.InspectContainer(c.ID)
inspected, err := client.ContainerInspect(context.Background(), c.ID)
if err != nil {
return fmt.Errorf("Container could not be inspected: %s", err)
}
*container = *inspected
*container = inspected
return nil
}
}

View file

@ -1,10 +1,16 @@
package docker
import (
"context"
"fmt"
"log"
"strings"
dc "github.com/fsouza/go-dockerclient"
"bytes"
"encoding/base64"
"encoding/json"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/hashicorp/terraform/helper/schema"
)
@ -18,7 +24,7 @@ func resourceDockerImageCreate(d *schema.ResourceData, meta interface{}) error {
d.SetId(apiImage.ID + d.Get("name").(string))
d.Set("latest", apiImage.ID)
return nil
return resourceDockerImageRead(d, meta)
}
func resourceDockerImageRead(d *schema.ResourceData, meta interface{}) error {
@ -49,7 +55,7 @@ func resourceDockerImageUpdate(d *schema.ResourceData, meta interface{}) error {
d.Set("latest", apiImage.ID)
return nil
return resourceDockerImageRead(d, meta)
}
func resourceDockerImageDelete(d *schema.ResourceData, meta interface{}) error {
@ -62,7 +68,7 @@ func resourceDockerImageDelete(d *schema.ResourceData, meta interface{}) error {
return nil
}
func searchLocalImages(data Data, imageName string) *dc.APIImages {
func searchLocalImages(data Data, imageName string) *types.ImageSummary {
if apiImage, ok := data.DockerImages[imageName]; ok {
return apiImage
}
@ -73,7 +79,7 @@ func searchLocalImages(data Data, imageName string) *dc.APIImages {
return nil
}
func removeImage(d *schema.ResourceData, client *dc.Client) error {
func removeImage(d *schema.ResourceData, client *client.Client) error {
var data Data
if keepLocally := d.Get("keep_locally").(bool); keepLocally {
@ -92,23 +98,24 @@ func removeImage(d *schema.ResourceData, client *dc.Client) error {
foundImage := searchLocalImages(data, imageName)
if foundImage != nil {
err := client.RemoveImage(foundImage.ID)
imageDeleteResponseItems, err := client.ImageRemove(context.Background(), foundImage.ID, types.ImageRemoveOptions{})
if err != nil {
return err
}
log.Printf("[INFO] Deleted image items: %v", imageDeleteResponseItems)
}
return nil
}
func fetchLocalImages(data *Data, client *dc.Client) error {
images, err := client.ListImages(dc.ListImagesOptions{All: false})
func fetchLocalImages(data *Data, client *client.Client) error {
images, err := client.ImageList(context.Background(), types.ImageListOptions{All: false})
if err != nil {
return fmt.Errorf("Unable to list Docker images: %s", err)
}
if data.DockerImages == nil {
data.DockerImages = make(map[string]*dc.APIImages)
data.DockerImages = make(map[string]*types.ImageSummary)
}
// Docker uses different nomenclatures in different places...sometimes a short
@ -125,11 +132,11 @@ func fetchLocalImages(data *Data, client *dc.Client) error {
return nil
}
func pullImage(data *Data, client *dc.Client, authConfig *dc.AuthConfigurations, image string) error {
func pullImage(data *Data, client *client.Client, authConfig *AuthConfigs, image string) error {
pullOpts := parseImageOptions(image)
// If a registry was specified in the image name, try to find auth for it
auth := dc.AuthConfiguration{}
auth := types.AuthConfig{}
if pullOpts.Registry != "" {
if authConfig, ok := authConfig.Configs[normalizeRegistryAddress(pullOpts.Registry)]; ok {
auth = authConfig
@ -141,16 +148,39 @@ func pullImage(data *Data, client *dc.Client, authConfig *dc.AuthConfigurations,
}
}
if err := client.PullImage(pullOpts, auth); err != nil {
return fmt.Errorf("Error pulling image %s: %s\n", image, err)
encodedJSON, err := json.Marshal(auth)
if err != nil {
return fmt.Errorf("error creating auth config: %s", err)
}
out, err := client.ImagePull(context.Background(), image, types.ImagePullOptions{
RegistryAuth: base64.URLEncoding.EncodeToString(encodedJSON),
})
if err != nil {
return fmt.Errorf("error pulling image %s: %s", image, err)
}
defer out.Close()
buf := new(bytes.Buffer)
buf.ReadFrom(out)
s := buf.String()
log.Printf("[DEBUG] pulled image %v: %v", image, s)
return fetchLocalImages(data, client)
}
func parseImageOptions(image string) dc.PullImageOptions {
pullOpts := dc.PullImageOptions{}
type internalPullImageOptions struct {
Repository string `qs:"fromImage"`
Tag string
// Only required for Docker Engine 1.9 or 1.10 w/ Remote API < 1.21
// and Docker Engine < 1.9
// This parameter was removed in Docker Engine 1.11
Registry string
}
func parseImageOptions(image string) internalPullImageOptions {
pullOpts := internalPullImageOptions{}
splitImageName := strings.Split(image, ":")
switch len(splitImageName) {
@ -195,11 +225,11 @@ func parseImageOptions(image string) dc.PullImageOptions {
return pullOpts
}
func findImage(d *schema.ResourceData, client *dc.Client, authConfig *dc.AuthConfigurations) (*dc.APIImages, error) {
func findImage(d *schema.ResourceData, client *client.Client, authConfig *AuthConfigs) (*types.ImageSummary, error) {
var data Data
if err := fetchLocalImages(&data, client); err != nil {
return nil, err
}
//if err := fetchLocalImages(&data, client); err != nil {
// return nil, err
//} Is done in pullImage
imageName := d.Get("name").(string)
if imageName == "" {

View file

@ -1,12 +1,12 @@
package docker
import (
"context"
"fmt"
"os"
"regexp"
"testing"
dc "github.com/fsouza/go-dockerclient"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
@ -56,7 +56,7 @@ func TestAccDockerImage_destroy(t *testing.T) {
}
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
_, err := client.InspectImage(rs.Primary.Attributes["latest"])
_, _, err := client.ImageInspectWithRaw(context.Background(), rs.Primary.Attributes["latest"])
if err != nil {
return err
}
@ -132,11 +132,9 @@ func testAccDockerImageDestroy(s *terraform.State) error {
}
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
_, err := client.InspectImage(rs.Primary.Attributes["latest"])
_, _, err := client.ImageInspectWithRaw(context.Background(), rs.Primary.Attributes["latest"])
if err == nil {
return fmt.Errorf("Image still exists")
} else if err != dc.ErrNoSuchImage {
return err
}
}
return nil

View file

@ -3,16 +3,20 @@ package docker
import (
"fmt"
dc "github.com/fsouza/go-dockerclient"
"context"
"encoding/json"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"log"
"time"
)
func resourceDockerNetworkCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ProviderConfig).DockerClient
createOpts := dc.CreateNetworkOptions{
Name: d.Get("name").(string),
}
createOpts := types.NetworkCreate{}
if v, ok := d.GetOk("check_duplicate"); ok {
createOpts.CheckDuplicate = v.(bool)
}
@ -20,13 +24,13 @@ func resourceDockerNetworkCreate(d *schema.ResourceData, meta interface{}) error
createOpts.Driver = v.(string)
}
if v, ok := d.GetOk("options"); ok {
createOpts.Options = v.(map[string]interface{})
createOpts.Options = mapTypeMapValsToString(v.(map[string]interface{}))
}
if v, ok := d.GetOk("internal"); ok {
createOpts.Internal = v.(bool)
}
ipamOpts := &dc.IPAMOptions{}
ipamOpts := &network.IPAM{}
ipamOptsSet := false
if v, ok := d.GetOk("ipam_driver"); ok {
ipamOpts.Driver = v.(string)
@ -41,46 +45,34 @@ func resourceDockerNetworkCreate(d *schema.ResourceData, meta interface{}) error
createOpts.IPAM = ipamOpts
}
var err error
var retNetwork *dc.Network
if retNetwork, err = client.CreateNetwork(createOpts); err != nil {
retNetwork := types.NetworkCreateResponse{}
retNetwork, err := client.NetworkCreate(context.Background(), d.Get("name").(string), createOpts)
if err != nil {
return fmt.Errorf("Unable to create network: %s", err)
}
if retNetwork == nil {
return fmt.Errorf("Returned network is nil")
}
d.SetId(retNetwork.ID)
d.Set("name", retNetwork.Name)
d.Set("scope", retNetwork.Scope)
d.Set("driver", retNetwork.Driver)
d.Set("options", retNetwork.Options)
// The 'internal' property is not send back when create network
d.Set("internal", createOpts.Internal)
return nil
return resourceDockerNetworkRead(d, meta)
}
func resourceDockerNetworkRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ProviderConfig).DockerClient
log.Printf("[INFO] Waiting for network: '%s' to expose all fields: max '%v seconds'", d.Id(), 30)
var err error
var retNetwork *dc.Network
if retNetwork, err = client.NetworkInfo(d.Id()); err != nil {
if _, ok := err.(*dc.NoSuchNetwork); !ok {
return fmt.Errorf("Unable to inspect network: %s", err)
}
}
if retNetwork == nil {
d.SetId("")
return nil
stateConf := &resource.StateChangeConf{
Pending: []string{"pending"},
Target: []string{"all_fields", "removed"},
Refresh: resourceDockerNetworkReadRefreshFunc(d, meta),
Timeout: 30 * time.Second,
MinTimeout: 5 * time.Second,
Delay: 2 * time.Second,
}
d.Set("scope", retNetwork.Scope)
d.Set("driver", retNetwork.Driver)
d.Set("options", retNetwork.Options)
d.Set("internal", retNetwork.Internal)
// Wait, catching any errors
_, err := stateConf.WaitForState()
if err != nil {
return err
}
return nil
}
@ -88,23 +80,21 @@ func resourceDockerNetworkRead(d *schema.ResourceData, meta interface{}) error {
func resourceDockerNetworkDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ProviderConfig).DockerClient
if err := client.RemoveNetwork(d.Id()); err != nil {
if _, ok := err.(*dc.NoSuchNetwork); !ok {
return fmt.Errorf("Error deleting network %s: %s", d.Id(), err)
}
if err := client.NetworkRemove(context.Background(), d.Id()); err != nil {
return fmt.Errorf("Error deleting network %s: %s", d.Id(), err)
}
d.SetId("")
return nil
}
func ipamConfigSetToIpamConfigs(ipamConfigSet *schema.Set) []dc.IPAMConfig {
ipamConfigs := make([]dc.IPAMConfig, ipamConfigSet.Len())
func ipamConfigSetToIpamConfigs(ipamConfigSet *schema.Set) []network.IPAMConfig {
ipamConfigs := make([]network.IPAMConfig, ipamConfigSet.Len())
for i, ipamConfigInt := range ipamConfigSet.List() {
ipamConfigRaw := ipamConfigInt.(map[string]interface{})
ipamConfig := dc.IPAMConfig{}
ipamConfig := network.IPAMConfig{}
ipamConfig.Subnet = ipamConfigRaw["subnet"].(string)
ipamConfig.IPRange = ipamConfigRaw["ip_range"].(string)
ipamConfig.Gateway = ipamConfigRaw["gateway"].(string)
@ -120,3 +110,38 @@ func ipamConfigSetToIpamConfigs(ipamConfigSet *schema.Set) []dc.IPAMConfig {
return ipamConfigs
}
func resourceDockerNetworkReadRefreshFunc(
d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
client := meta.(*ProviderConfig).DockerClient
networkID := d.Id()
retNetwork, _, err := client.NetworkInspectWithRaw(context.Background(), networkID, types.NetworkInspectOptions{})
if err != nil {
log.Printf("[WARN] Network (%s) not found, removing from state", networkID)
d.SetId("")
return networkID, "removed", err
}
jsonObj, _ := json.MarshalIndent(retNetwork, "", "\t")
log.Printf("[DEBUG] Docker network inspect: %s", jsonObj)
d.Set("internal", retNetwork.Internal)
d.Set("driver", retNetwork.Driver)
d.Set("scope", retNetwork.Scope)
if retNetwork.Scope == "overlay" {
if retNetwork.Options != nil && len(retNetwork.Options) != 0 {
d.Set("options", retNetwork.Options)
} else {
log.Printf("[DEBUG] options: %v not exposed", retNetwork.Options)
return networkID, "pending", nil
}
} else {
d.Set("options", retNetwork.Options)
}
log.Println("[DEBUG] all network fields exposed")
return networkID, "all_fields", nil
}
}

View file

@ -4,13 +4,14 @@ import (
"fmt"
"testing"
dc "github.com/fsouza/go-dockerclient"
"context"
"github.com/docker/docker/api/types"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccDockerNetwork_basic(t *testing.T) {
var n dc.Network
var n types.NetworkResource
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -26,7 +27,7 @@ func TestAccDockerNetwork_basic(t *testing.T) {
})
}
func testAccNetwork(n string, network *dc.Network) resource.TestCheckFunc {
func testAccNetwork(n string, network *types.NetworkResource) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
@ -38,18 +39,18 @@ func testAccNetwork(n string, network *dc.Network) resource.TestCheckFunc {
}
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
networks, err := client.ListNetworks()
networks, err := client.NetworkList(context.Background(), types.NetworkListOptions{})
if err != nil {
return err
}
for _, n := range networks {
if n.ID == rs.Primary.ID {
inspected, err := client.NetworkInfo(n.ID)
inspected, err := client.NetworkInspect(context.Background(), n.ID, types.NetworkInspectOptions{})
if err != nil {
return fmt.Errorf("Network could not be obtained: %s", err)
}
*network = *inspected
*network = inspected
return nil
}
}
@ -65,7 +66,7 @@ resource "docker_network" "foo" {
`
func TestAccDockerNetwork_internal(t *testing.T) {
var n dc.Network
var n types.NetworkResource
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -82,7 +83,7 @@ func TestAccDockerNetwork_internal(t *testing.T) {
})
}
func testAccNetworkInternal(network *dc.Network, internal bool) resource.TestCheckFunc {
func testAccNetworkInternal(network *types.NetworkResource, internal bool) resource.TestCheckFunc {
return func(s *terraform.State) error {
if network.Internal != internal {
return fmt.Errorf("Bad value for attribute 'internal': %t", network.Internal)

View file

@ -4,8 +4,8 @@ import (
"encoding/base64"
"log"
"context"
"github.com/docker/docker/api/types/swarm"
dc "github.com/fsouza/go-dockerclient"
"github.com/hashicorp/terraform/helper/schema"
)
@ -39,16 +39,14 @@ func resourceDockerSecretCreate(d *schema.ResourceData, meta interface{}) error
client := meta.(*ProviderConfig).DockerClient
data, _ := base64.StdEncoding.DecodeString(d.Get("data").(string))
createSecretOpts := dc.CreateSecretOptions{
SecretSpec: swarm.SecretSpec{
Annotations: swarm.Annotations{
Name: d.Get("name").(string),
},
Data: data,
secretSpec := swarm.SecretSpec{
Annotations: swarm.Annotations{
Name: d.Get("name").(string),
},
Data: data,
}
secret, err := client.CreateSecret(createSecretOpts)
secret, err := client.SecretCreate(context.Background(), secretSpec)
if err != nil {
return err
}
@ -60,15 +58,12 @@ func resourceDockerSecretCreate(d *schema.ResourceData, meta interface{}) error
func resourceDockerSecretRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ProviderConfig).DockerClient
secret, err := client.InspectSecret(d.Id())
secret, _, err := client.SecretInspectWithRaw(context.Background(), d.Id())
if err != nil {
if _, ok := err.(*dc.NoSuchSecret); ok {
log.Printf("[WARN] Secret (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}
return err
log.Printf("[WARN] Secret (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}
d.SetId(secret.ID)
return nil
@ -76,9 +71,7 @@ func resourceDockerSecretRead(d *schema.ResourceData, meta interface{}) error {
func resourceDockerSecretDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ProviderConfig).DockerClient
err := client.RemoveSecret(dc.RemoveSecretOptions{
ID: d.Id(),
})
err := client.SecretRemove(context.Background(), d.Id())
if err != nil {
return err

View file

@ -4,6 +4,7 @@ import (
"fmt"
"testing"
"context"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
@ -29,7 +30,7 @@ func TestAccDockerSecret_basic(t *testing.T) {
},
})
}
func TestAccDockerSecret_basicUpdateble(t *testing.T) {
func TestAccDockerSecret_basicUpdatable(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
@ -82,9 +83,9 @@ func testCheckDockerSecretDestroy(s *terraform.State) error {
}
id := rs.Primary.Attributes["id"]
secret, err := client.InspectSecret(id)
_, _, err := client.SecretInspectWithRaw(context.Background(), id)
if err == nil || secret != nil {
if err == nil {
return fmt.Errorf("Secret with id '%s' still exists", id)
}
return nil

View file

@ -10,10 +10,13 @@ import (
"strings"
"time"
"encoding/base64"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/swarm"
dc "github.com/fsouza/go-dockerclient"
"github.com/docker/docker/client"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
@ -53,17 +56,20 @@ func resourceDockerServiceCreate(d *schema.ResourceData, meta interface{}) error
return err
}
createOpts := dc.CreateServiceOptions{
ServiceSpec: serviceSpec,
}
serviceOptions := types.ServiceCreateOptions{}
auth := types.AuthConfig{}
if v, ok := d.GetOk("auth"); ok {
createOpts.Auth = authToServiceAuth(v.(map[string]interface{}))
auth = authToServiceAuth(v.(map[string]interface{}))
} else {
createOpts.Auth = fromRegistryAuth(d.Get("task_spec.0.container_spec.0.image").(string), meta.(*ProviderConfig).AuthConfigs.Configs)
auth = fromRegistryAuth(d.Get("task_spec.0.container_spec.0.image").(string), meta.(*ProviderConfig).AuthConfigs.Configs)
}
encodedJSON, err := json.Marshal(auth)
if err != nil {
return fmt.Errorf("error creating auth config: %s", err)
}
serviceOptions.EncodedRegistryAuth = base64.URLEncoding.EncodeToString(encodedJSON)
service, err := client.CreateService(createOpts)
service, err := client.ServiceCreate(context.Background(), serviceSpec, serviceOptions)
if err != nil {
return err
}
@ -108,7 +114,7 @@ func resourceDockerServiceRead(d *schema.ResourceData, meta interface{}) error {
d.SetId("")
return nil
}
service, err := client.InspectService(apiService.ID)
service, _, err := client.ServiceInspectWithRaw(context.Background(), apiService.ID, types.ServiceInspectOptions{})
if err != nil {
return fmt.Errorf("Error inspecting service %s: %s", apiService.ID, err)
}
@ -142,7 +148,7 @@ func resourceDockerServiceRead(d *schema.ResourceData, meta interface{}) error {
func resourceDockerServiceUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ProviderConfig).DockerClient
service, err := client.InspectService(d.Id())
service, _, err := client.ServiceInspectWithRaw(context.Background(), d.Id(), types.ServiceInspectOptions{})
if err != nil {
return err
}
@ -152,20 +158,26 @@ func resourceDockerServiceUpdate(d *schema.ResourceData, meta interface{}) error
return err
}
updateOpts := dc.UpdateServiceOptions{
ServiceSpec: serviceSpec,
Version: service.Version.Index,
}
updateOptions := types.ServiceUpdateOptions{}
auth := types.AuthConfig{}
if v, ok := d.GetOk("auth"); ok {
updateOpts.Auth = authToServiceAuth(v.(map[string]interface{}))
auth = authToServiceAuth(v.(map[string]interface{}))
} else {
updateOpts.Auth = fromRegistryAuth(d.Get("task_spec.0.container_spec.0.image").(string), meta.(*ProviderConfig).AuthConfigs.Configs)
auth = fromRegistryAuth(d.Get("task_spec.0.container_spec.0.image").(string), meta.(*ProviderConfig).AuthConfigs.Configs)
}
encodedJSON, err := json.Marshal(auth)
if err != nil {
return fmt.Errorf("error creating auth config: %s", err)
}
updateOptions.EncodedRegistryAuth = base64.URLEncoding.EncodeToString(encodedJSON)
if err = client.UpdateService(d.Id(), updateOpts); err != nil {
updateResponse, err := client.ServiceUpdate(context.Background(), d.Id(), service.Version, serviceSpec, updateOptions)
if err != nil {
return err
}
if len(updateResponse.Warnings) > 0 {
log.Printf("[INFO] Warninig while updating Service '%s': %v", service.ID, updateResponse.Warnings)
}
if v, ok := d.GetOk("converge_config"); ok {
convergeConfig := createConvergeConfig(v.([]interface{}))
@ -209,8 +221,8 @@ func resourceDockerServiceDelete(d *schema.ResourceData, meta interface{}) error
// Helpers
/////////////////
// fetchDockerService fetches a service by its name or id
func fetchDockerService(ID string, name string, client *dc.Client) (*swarm.Service, error) {
apiServices, err := client.ListServices(dc.ListServicesOptions{})
func fetchDockerService(ID string, name string, client *client.Client) (*swarm.Service, error) {
apiServices, err := client.ServiceList(context.Background(), types.ServiceListOptions{})
if err != nil {
return nil, fmt.Errorf("Error fetching service information from Docker: %s", err)
@ -226,39 +238,34 @@ func fetchDockerService(ID string, name string, client *dc.Client) (*swarm.Servi
}
// deleteService deletes the service with the given id
func deleteService(serviceID string, d *schema.ResourceData, client *dc.Client) error {
func deleteService(serviceID string, d *schema.ResourceData, client *client.Client) error {
// get containerIDs of the running service because they do not exist after the service is deleted
serviceContainerIds := make([]string, 0)
if _, ok := d.GetOk("task_spec.0.container_spec.0.stop_grace_period"); ok {
filter := make(map[string][]string)
filter["service"] = []string{d.Get("name").(string)}
tasks, err := client.ListTasks(dc.ListTasksOptions{
Filters: filter,
filters := filters.NewArgs()
filters.Add("service", d.Get("name").(string))
tasks, err := client.TaskList(context.Background(), types.TaskListOptions{
Filters: filters,
})
if err != nil {
return err
}
for _, t := range tasks {
task, _ := client.InspectTask(t.ID)
log.Printf("[INFO] Found container ['%s'] for destroying: '%s'", task.Status.State, task.Status.ContainerStatus.ContainerID)
if strings.TrimSpace(task.Status.ContainerStatus.ContainerID) != "" && task.Status.State != swarm.TaskStateShutdown {
serviceContainerIds = append(serviceContainerIds, task.Status.ContainerStatus.ContainerID)
task, _, _ := client.TaskInspectWithRaw(context.Background(), t.ID)
containerID := ""
if task.Status.ContainerStatus != nil {
containerID = task.Status.ContainerStatus.ContainerID
}
log.Printf("[INFO] Found container ['%s'] for destroying: '%s'", task.Status.State, containerID)
if strings.TrimSpace(containerID) != "" && task.Status.State != swarm.TaskStateShutdown {
serviceContainerIds = append(serviceContainerIds, containerID)
}
}
}
// delete the service
log.Printf("[INFO] Deleting service: '%s'", serviceID)
removeOpts := dc.RemoveServiceOptions{
ID: serviceID,
}
if err := client.RemoveService(removeOpts); err != nil {
if _, ok := err.(*dc.NoSuchService); ok {
log.Printf("[WARN] Service (%s) not found, removing from state", serviceID)
d.SetId("")
return nil
}
if err := client.ServiceRemove(context.Background(), serviceID); err != nil {
return fmt.Errorf("Error deleting service %s: %s", serviceID, err)
}
@ -269,17 +276,16 @@ func deleteService(serviceID string, d *schema.ResourceData, client *dc.Client)
log.Printf("[INFO] Waiting for container: '%s' to exit: max %v", containerID, destroyGraceSeconds)
ctx, cancel := context.WithTimeout(context.Background(), destroyGraceSeconds)
defer cancel()
exitCode, _ := client.WaitContainerWithContext(containerID, ctx)
exitCode, _ := client.ContainerWait(ctx, containerID, container.WaitConditionRemoved)
log.Printf("[INFO] Container exited with code [%v]: '%s'", exitCode, containerID)
removeOpts := dc.RemoveContainerOptions{
ID: containerID,
removeOpts := types.ContainerRemoveOptions{
RemoveVolumes: true,
Force: true,
}
log.Printf("[INFO] Removing container: '%s'", containerID)
if err := client.RemoveContainer(removeOpts); err != nil {
if err := client.ContainerRemove(context.Background(), containerID, removeOpts); err != nil {
if !(strings.Contains(err.Error(), "No such container") || strings.Contains(err.Error(), "is already in progress")) {
return fmt.Errorf("Error deleting container %s: %s", containerID, err)
}
@ -321,18 +327,17 @@ func resourceDockerServiceCreateRefreshFunc(
updater = &replicatedConsoleLogUpdater{}
}
filter := make(map[string][]string)
filter["service"] = []string{serviceID}
filter["desired-state"] = []string{"running"}
filters := filters.NewArgs()
filters.Add("service", serviceID)
filters.Add("desired-state", "running")
getUpToDateTasks := func() ([]swarm.Task, error) {
return client.ListTasks(dc.ListTasksOptions{
Filters: filter,
Context: ctx,
return client.TaskList(ctx, types.TaskListOptions{
Filters: filters,
})
}
var service *swarm.Service
service, err := client.InspectService(serviceID)
service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
if err != nil {
return nil, "", err
}
@ -347,7 +352,7 @@ func resourceDockerServiceCreateRefreshFunc(
return nil, "", err
}
serviceCreateStatus, err := updater.update(service, tasks, activeNodes, false)
serviceCreateStatus, err := updater.update(&service, tasks, activeNodes, false)
if err != nil {
return nil, "", err
}
@ -377,18 +382,17 @@ func resourceDockerServiceUpdateRefreshFunc(
}
rollback = false
filter := make(map[string][]string)
filter["service"] = []string{serviceID}
filter["desired-state"] = []string{"running"}
filters := filters.NewArgs()
filters.Add("service", serviceID)
filters.Add("desired-state", "running")
getUpToDateTasks := func() ([]swarm.Task, error) {
return client.ListTasks(dc.ListTasksOptions{
Filters: filter,
Context: ctx,
return client.TaskList(ctx, types.TaskListOptions{
Filters: filters,
})
}
var service *swarm.Service
service, err := client.InspectService(serviceID)
service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
if err != nil {
return nil, "", err
}
@ -421,7 +425,7 @@ func resourceDockerServiceUpdateRefreshFunc(
return nil, "", err
}
isUpdateCompleted, err := updater.update(service, tasks, activeNodes, rollback)
isUpdateCompleted, err := updater.update(&service, tasks, activeNodes, rollback)
if err != nil {
return nil, "", err
}
@ -438,8 +442,8 @@ func resourceDockerServiceUpdateRefreshFunc(
}
// getActiveNodes gets the actives nodes withon a swarm
func getActiveNodes(ctx context.Context, client *dc.Client) (map[string]struct{}, error) {
nodes, err := client.ListNodes(dc.ListNodesOptions{Context: ctx})
func getActiveNodes(ctx context.Context, client *client.Client) (map[string]struct{}, error) {
nodes, err := client.NodeList(ctx, types.NodeListOptions{})
if err != nil {
return nil, err
}
@ -1265,20 +1269,20 @@ func createConvergeConfig(config []interface{}) *convergeConfig {
}
// authToServiceAuth maps the auth to AuthConfiguration
func authToServiceAuth(auth map[string]interface{}) dc.AuthConfiguration {
func authToServiceAuth(auth map[string]interface{}) types.AuthConfig {
if auth["username"] != nil && len(auth["username"].(string)) > 0 && auth["password"] != nil && len(auth["password"].(string)) > 0 {
return dc.AuthConfiguration{
return types.AuthConfig{
Username: auth["username"].(string),
Password: auth["password"].(string),
ServerAddress: auth["server_address"].(string),
}
}
return dc.AuthConfiguration{}
return types.AuthConfig{}
}
// fromRegistryAuth extract the desired AuthConfiguration for the given image
func fromRegistryAuth(image string, configs map[string]dc.AuthConfiguration) dc.AuthConfiguration {
func fromRegistryAuth(image string, configs map[string]types.AuthConfig) types.AuthConfig {
// Remove normalized prefixes to simlify substring
image = strings.Replace(strings.Replace(image, "http://", "", 1), "https://", "", 1)
// Get the registry with optional port
@ -1291,7 +1295,7 @@ func fromRegistryAuth(image string, configs map[string]dc.AuthConfiguration) dc.
}
}
return dc.AuthConfiguration{}
return types.AuthConfig{}
}
// stringSetToPlacementPrefs maps a string set to PlacementPreference

View file

@ -6,7 +6,9 @@ import (
"regexp"
"testing"
dc "github.com/fsouza/go-dockerclient"
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
@ -16,8 +18,8 @@ import (
// ----------------------------------------
func TestDockerSecretFromRegistryAuth_basic(t *testing.T) {
authConfigs := make(map[string]dc.AuthConfiguration)
authConfigs["https://repo.my-company.com:8787"] = dc.AuthConfiguration{
authConfigs := make(map[string]types.AuthConfig)
authConfigs["https://repo.my-company.com:8787"] = types.AuthConfig{
Username: "myuser",
Password: "mypass",
Email: "",
@ -32,14 +34,14 @@ func TestDockerSecretFromRegistryAuth_basic(t *testing.T) {
}
func TestDockerSecretFromRegistryAuth_multiple(t *testing.T) {
authConfigs := make(map[string]dc.AuthConfiguration)
authConfigs["https://repo.my-company.com:8787"] = dc.AuthConfiguration{
authConfigs := make(map[string]types.AuthConfig)
authConfigs["https://repo.my-company.com:8787"] = types.AuthConfig{
Username: "myuser",
Password: "mypass",
Email: "",
ServerAddress: "repo.my-company.com:8787",
}
authConfigs["https://nexus.my-fancy-company.com"] = dc.AuthConfiguration{
authConfigs["https://nexus.my-fancy-company.com"] = types.AuthConfig{
Username: "myuser33",
Password: "mypass123",
Email: "test@example.com",
@ -98,6 +100,7 @@ func TestAccDockerService_minimal(t *testing.T) {
},
})
}
func TestAccDockerService_full(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -113,7 +116,7 @@ func TestAccDockerService_full(t *testing.T) {
name = "tftest-full-myconfig"
data = "ewogICJwcmVmaXgiOiAiMTIzIgp9"
}
resource "docker_secret" "service_secret" {
name = "tftest-mysecret"
data = "ewogICJrZXkiOiAiUVdFUlRZIgp9"
@ -126,27 +129,27 @@ func TestAccDockerService_full(t *testing.T) {
resource "docker_service" "foo" {
name = "tftest-service-basic"
task_spec {
container_spec {
image = "127.0.0.1:15000/tftest-service:v1"
labels {
foo = "bar"
}
command = ["ls"]
args = ["-las"]
hostname = "my-fancy-service"
env {
MYFOO = "BAR"
}
dir = "/root"
user = "root"
groups = ["docker", "foogroup"]
privileges {
se_linux_context {
disable = true
@ -156,9 +159,9 @@ func TestAccDockerService_full(t *testing.T) {
level = "level-label"
}
}
read_only = true
mounts = [
{
target = "/mount/test"
@ -178,28 +181,28 @@ func TestAccDockerService_full(t *testing.T) {
}
},
]
stop_signal = "SIGTERM"
stop_grace_period = "10s"
healthcheck {
test = ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval = "5s"
timeout = "2s"
retries = 4
}
hosts {
host = "testhost"
ip = "10.0.1.0"
}
dns_config {
nameservers = ["8.8.8.8"]
search = ["example.org"]
options = ["timeout:3"]
}
secrets = [
{
secret_id = "${docker_secret.service_secret.id}"
@ -207,7 +210,7 @@ func TestAccDockerService_full(t *testing.T) {
file_name = "/secrets.json"
},
]
configs = [
{
config_id = "${docker_config.service_config.id}"
@ -216,51 +219,51 @@ func TestAccDockerService_full(t *testing.T) {
},
]
}
resources {
limits {
nano_cpus = 1000000
memory_bytes = 536870912
}
}
restart_policy {
condition = "on-failure"
delay = "3s"
max_attempts = 4
window = "10s"
}
placement {
constraints = [
"node.role==manager",
]
prefs = [
"spread=node.role.manager",
]
}
force_update = 0
runtime = "container"
networks = ["${docker_network.test_network.id}"]
log_driver {
name = "json-file"
options {
max-size = "10m"
max-file = "3"
}
}
}
mode {
replicated {
replicas = 2
}
}
update_config {
parallelism = 2
delay = "10s"
@ -269,7 +272,7 @@ func TestAccDockerService_full(t *testing.T) {
max_failure_ratio = "0.1"
order = "start-first"
}
rollback_config {
parallelism = 2
delay = "5ms"
@ -278,10 +281,10 @@ func TestAccDockerService_full(t *testing.T) {
max_failure_ratio = "0.9"
order = "stop-first"
}
endpoint_spec {
mode = "vip"
ports {
name = "random"
protocol = "tcp"
@ -291,7 +294,7 @@ func TestAccDockerService_full(t *testing.T) {
}
}
}
`,
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_service.foo", "id", serviceIDRegex),
@ -338,12 +341,12 @@ func TestAccDockerService_full(t *testing.T) {
resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.secrets.#", "1"),
resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.resources.0.limits.0.nano_cpus", "1000000"),
resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.resources.0.limits.0.memory_bytes", "536870912"),
// resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.restart_policy.condition", "on-failure"),
// resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.restart_policy.delay", "3s"),
// resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.restart_policy.max_attempts", "4"),
// resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.restart_policy.window", "10s"),
// resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.placement.0.constraints.4248571116", "node.role==manager"),
// resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.placement.0.prefs.1751004438", "spread=node.role.manager"),
resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.restart_policy.condition", "on-failure"),
resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.restart_policy.delay", "3s"),
resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.restart_policy.max_attempts", "4"),
resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.restart_policy.window", "10s"),
resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.placement.0.constraints.4248571116", "node.role==manager"),
resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.placement.0.prefs.1751004438", "spread=node.role.manager"),
resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.force_update", "0"),
resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.networks.#", "1"),
resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.log_driver.0.name", "json-file"),
@ -503,6 +506,7 @@ func TestAccDockerService_GlobalAndReplicated(t *testing.T) {
},
})
}
func TestAccDockerService_GlobalWithConvergeConfig(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -692,7 +696,7 @@ func TestAccDockerService_updateConfigReplicasImageAndHealthIncreaseAndDecreaseR
task_spec {
container_spec {
image = "127.0.0.1:15000/tftest-service:v1"
configs = [
{
config_id = "${docker_config.service_config.id}"
@ -700,7 +704,7 @@ func TestAccDockerService_updateConfigReplicasImageAndHealthIncreaseAndDecreaseR
file_name = "/configs.json"
},
]
healthcheck {
test = ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval = "1s"
@ -708,7 +712,7 @@ func TestAccDockerService_updateConfigReplicasImageAndHealthIncreaseAndDecreaseR
start_period = "0s"
retries = 2
}
stop_grace_period = "10s"
}
}
@ -822,7 +826,7 @@ func TestAccDockerService_updateConfigReplicasImageAndHealthIncreaseAndDecreaseR
target_port = "8080"
published_port = "8082"
}
]
]
}
}
`,
@ -914,7 +918,7 @@ func TestAccDockerService_updateConfigReplicasImageAndHealthIncreaseAndDecreaseR
target_port = "8080"
published_port = "8082"
}
]
]
}
}
`,
@ -961,7 +965,7 @@ func TestAccDockerService_nonExistingPrivateImageConverge(t *testing.T) {
container_spec = {
image = "127.0.0.1:15000/idonoexist:latest"
}
}
}
mode {
replicated {
@ -983,6 +987,7 @@ func TestAccDockerService_nonExistingPrivateImageConverge(t *testing.T) {
},
})
}
func TestAccDockerService_nonExistingPublicImageConverge(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -996,7 +1001,7 @@ func TestAccDockerService_nonExistingPublicImageConverge(t *testing.T) {
container_spec = {
image = "stovogel/blablabla:part5"
}
}
}
mode {
replicated {
@ -1070,6 +1075,7 @@ func TestAccDockerService_basicConvergeAndStopGracefully(t *testing.T) {
},
})
}
func TestAccDockerService_updateFailsAndRollbackConverge(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -1082,7 +1088,7 @@ func TestAccDockerService_updateFailsAndRollbackConverge(t *testing.T) {
task_spec {
container_spec {
image = "127.0.0.1:15000/tftest-service:v1"
healthcheck {
test = ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval = "5s"
@ -1212,6 +1218,7 @@ func TestAccDockerService_updateFailsAndRollbackConverge(t *testing.T) {
}
func TestAccDockerService_updateNetworksConverge(t *testing.T) {
// t.Skip("Skipped because response from daemon is not always consistent")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
@ -1242,8 +1249,7 @@ func TestAccDockerService_updateNetworksConverge(t *testing.T) {
replicas = 2
}
}
endpoint_spec {
mode = "vip"
@ -1253,8 +1259,8 @@ func TestAccDockerService_updateNetworksConverge(t *testing.T) {
}
converge_config {
delay = "7s"
timeout = "3m"
delay = "5s"
timeout = "60s"
}
}
@ -1303,8 +1309,8 @@ func TestAccDockerService_updateNetworksConverge(t *testing.T) {
}
converge_config {
delay = "7s"
timeout = "3m"
delay = "5s"
timeout = "60s"
}
}
`,
@ -1356,8 +1362,8 @@ func TestAccDockerService_updateNetworksConverge(t *testing.T) {
}
converge_config {
delay = "7s"
timeout = "3m"
delay = "5s"
timeout = "60s"
}
}
`,
@ -1372,6 +1378,7 @@ func TestAccDockerService_updateNetworksConverge(t *testing.T) {
},
})
}
func TestAccDockerService_updateMountsConverge(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -1383,10 +1390,6 @@ func TestAccDockerService_updateMountsConverge(t *testing.T) {
name = "tftest-volume"
}
resource "docker_volume" "foo2" {
name = "tftest-volume2"
}
resource "docker_service" "foo" {
name = "tftest-service-up-mounts"
task_spec {
@ -1415,10 +1418,9 @@ func TestAccDockerService_updateMountsConverge(t *testing.T) {
}
}
converge_config {
delay = "7s"
timeout = "3m"
delay = "5s"
timeout = "60s"
}
}
`,
@ -1482,8 +1484,8 @@ func TestAccDockerService_updateMountsConverge(t *testing.T) {
}
converge_config {
delay = "7s"
timeout = "3m"
delay = "5s"
timeout = "60s"
}
}
`,
@ -1498,6 +1500,7 @@ func TestAccDockerService_updateMountsConverge(t *testing.T) {
},
})
}
func TestAccDockerService_updateHostsConverge(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -1526,7 +1529,6 @@ func TestAccDockerService_updateHostsConverge(t *testing.T) {
}
}
converge_config {
delay = "7s"
timeout = "3m"
@ -1622,6 +1624,7 @@ func TestAccDockerService_updateHostsConverge(t *testing.T) {
},
})
}
func TestAccDockerService_updateLoggingConverge(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -1639,7 +1642,7 @@ func TestAccDockerService_updateLoggingConverge(t *testing.T) {
log_driver {
name = "json-file"
options {
max-size = "10m"
max-file = "3"
@ -1681,7 +1684,7 @@ func TestAccDockerService_updateLoggingConverge(t *testing.T) {
}
log_driver {
name = "json-file"
options {
max-size = "15m"
max-file = "5"
@ -1760,7 +1763,7 @@ func TestAccDockerService_updateHealthcheckConverge(t *testing.T) {
container_spec {
image = "127.0.0.1:15000/tftest-service:v1"
stop_grace_period = "10s"
healthcheck {
test = ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval = "1s"
@ -1775,7 +1778,7 @@ func TestAccDockerService_updateHealthcheckConverge(t *testing.T) {
replicas = 2
}
}
update_config {
parallelism = 1
delay = "1s"
@ -1843,7 +1846,7 @@ func TestAccDockerService_updateHealthcheckConverge(t *testing.T) {
replicas = 2
}
}
update_config {
parallelism = 1
delay = "1s"
@ -1859,7 +1862,7 @@ func TestAccDockerService_updateHealthcheckConverge(t *testing.T) {
published_port = "8080"
}
}
converge_config {
delay = "7s"
timeout = "3m"
@ -1922,7 +1925,7 @@ func TestAccDockerService_updateIncreaseReplicasConverge(t *testing.T) {
replicas = 1
}
}
update_config {
parallelism = 1
delay = "1s"
@ -1977,7 +1980,7 @@ func TestAccDockerService_updateIncreaseReplicasConverge(t *testing.T) {
container_spec {
image = "127.0.0.1:15000/tftest-service:v1"
stop_grace_period = "10s"
healthcheck {
test = ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval = "1s"
@ -1992,7 +1995,7 @@ func TestAccDockerService_updateIncreaseReplicasConverge(t *testing.T) {
replicas = 3
}
}
update_config {
parallelism = 1
delay = "1s"
@ -2008,7 +2011,7 @@ func TestAccDockerService_updateIncreaseReplicasConverge(t *testing.T) {
published_port = "8080"
}
}
converge_config {
delay = "7s"
timeout = "3m"
@ -2042,6 +2045,7 @@ func TestAccDockerService_updateIncreaseReplicasConverge(t *testing.T) {
},
})
}
func TestAccDockerService_updateDecreaseReplicasConverge(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -2055,7 +2059,7 @@ func TestAccDockerService_updateDecreaseReplicasConverge(t *testing.T) {
container_spec {
image = "127.0.0.1:15000/tftest-service:v1"
stop_grace_period = "10s"
healthcheck {
test = ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval = "1s"
@ -2070,7 +2074,7 @@ func TestAccDockerService_updateDecreaseReplicasConverge(t *testing.T) {
replicas = 5
}
}
update_config {
parallelism = 1
delay = "1s"
@ -2139,7 +2143,7 @@ func TestAccDockerService_updateDecreaseReplicasConverge(t *testing.T) {
replicas = 1
}
}
update_config {
parallelism = 1
delay = "1s"
@ -2155,7 +2159,7 @@ func TestAccDockerService_updateDecreaseReplicasConverge(t *testing.T) {
published_port = "8080"
}
}
converge_config {
delay = "7s"
timeout = "3m"
@ -2381,7 +2385,7 @@ func TestAccDockerService_updateConfigConverge(t *testing.T) {
replicas = 2
}
}
update_config {
parallelism = 1
delay = "1s"
@ -2397,7 +2401,7 @@ func TestAccDockerService_updateConfigConverge(t *testing.T) {
published_port = "8080"
}
}
converge_config {
delay = "7s"
timeout = "30s"
@ -2467,7 +2471,7 @@ func TestAccDockerService_updateConfigConverge(t *testing.T) {
replicas = 2
}
}
update_config {
parallelism = 1
delay = "1s"
@ -2483,7 +2487,7 @@ func TestAccDockerService_updateConfigConverge(t *testing.T) {
published_port = "8080"
}
}
converge_config {
delay = "7s"
timeout = "30s"
@ -2516,6 +2520,7 @@ func TestAccDockerService_updateConfigConverge(t *testing.T) {
},
})
}
func TestAccDockerService_updateConfigAndSecretConverge(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -2578,7 +2583,7 @@ func TestAccDockerService_updateConfigAndSecretConverge(t *testing.T) {
replicas = 2
}
}
update_config {
parallelism = 1
delay = "1s"
@ -2595,7 +2600,6 @@ func TestAccDockerService_updateConfigAndSecretConverge(t *testing.T) {
}
}
converge_config {
delay = "7s"
timeout = "3m"
@ -2684,7 +2688,7 @@ func TestAccDockerService_updateConfigAndSecretConverge(t *testing.T) {
replicas = 2
}
}
update_config {
parallelism = 1
delay = "1s"
@ -2701,7 +2705,6 @@ func TestAccDockerService_updateConfigAndSecretConverge(t *testing.T) {
}
}
converge_config {
delay = "7s"
timeout = "3m"
@ -2736,6 +2739,7 @@ func TestAccDockerService_updateConfigAndSecretConverge(t *testing.T) {
},
})
}
func TestAccDockerService_updatePortConverge(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -2852,7 +2856,7 @@ func TestAccDockerService_updatePortConverge(t *testing.T) {
target_port = "8080"
published_port = "8082"
}
]
]
}
converge_config {
@ -2889,6 +2893,7 @@ func TestAccDockerService_updatePortConverge(t *testing.T) {
},
})
}
func TestAccDockerService_updateConfigReplicasImageAndHealthConverge(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -3038,7 +3043,7 @@ func TestAccDockerService_updateConfigReplicasImageAndHealthConverge(t *testing.
target_port = "8080"
published_port = "8082"
}
]
]
}
converge_config {
@ -3075,6 +3080,7 @@ func TestAccDockerService_updateConfigReplicasImageAndHealthConverge(t *testing.
},
})
}
func TestAccDockerService_updateConfigAndDecreaseReplicasConverge(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -3119,7 +3125,7 @@ func TestAccDockerService_updateConfigAndDecreaseReplicasConverge(t *testing.T)
replicas = 5
}
}
update_config {
parallelism = 1
delay = "1s"
@ -3204,7 +3210,7 @@ func TestAccDockerService_updateConfigAndDecreaseReplicasConverge(t *testing.T)
replicas = 1
}
}
update_config {
parallelism = 1
delay = "1s"
@ -3253,6 +3259,7 @@ func TestAccDockerService_updateConfigAndDecreaseReplicasConverge(t *testing.T)
},
})
}
func TestAccDockerService_updateConfigReplicasImageAndHealthIncreaseAndDecreaseReplicasConverge(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -3402,7 +3409,7 @@ func TestAccDockerService_updateConfigReplicasImageAndHealthIncreaseAndDecreaseR
target_port = "8080"
published_port = "8082"
}
]
]
}
converge_config {
@ -3495,7 +3502,7 @@ func TestAccDockerService_updateConfigReplicasImageAndHealthIncreaseAndDecreaseR
target_port = "8080"
published_port = "8082"
}
]
]
}
converge_config {
@ -3579,10 +3586,10 @@ func TestAccDockerService_privateConverge(t *testing.T) {
func isServiceRemoved(serviceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
filter := make(map[string][]string)
filter["name"] = []string{serviceName}
services, err := client.ListServices(dc.ListServicesOptions{
Filters: filter,
filters := filters.NewArgs()
filters.Add("name", serviceName)
services, err := client.ServiceList(context.Background(), types.ServiceListOptions{
Filters: filters,
})
if err != nil {
return fmt.Errorf("Error listing service for name %s: %v", serviceName, err)

View file

@ -1,13 +1,15 @@
package docker
import (
"context"
"fmt"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/volume"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"log"
"strings"
"time"
dc "github.com/fsouza/go-dockerclient"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceDockerVolume() *schema.Resource {
@ -44,8 +46,10 @@ func resourceDockerVolume() *schema.Resource {
func resourceDockerVolumeCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ProviderConfig).DockerClient
ctx := context.Background()
createOpts := volume.VolumeCreateBody{}
createOpts := dc.CreateVolumeOptions{}
if v, ok := d.GetOk("name"); ok {
createOpts.Name = v.(string)
}
@ -57,34 +61,32 @@ func resourceDockerVolumeCreate(d *schema.ResourceData, meta interface{}) error
}
var err error
var retVolume *dc.Volume
if retVolume, err = client.CreateVolume(createOpts); err != nil {
var retVolume types.Volume
retVolume, err = client.VolumeCreate(ctx, createOpts)
if err != nil {
return fmt.Errorf("Unable to create volume: %s", err)
}
if retVolume == nil {
return fmt.Errorf("Returned volume is nil")
}
d.SetId(retVolume.Name)
d.Set("name", retVolume.Name)
d.Set("driver", retVolume.Driver)
d.Set("mountpoint", retVolume.Mountpoint)
return nil
return resourceDockerVolumeRead(d, meta)
}
func resourceDockerVolumeRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ProviderConfig).DockerClient
ctx := context.Background()
var err error
var retVolume *dc.Volume
if retVolume, err = client.InspectVolume(d.Id()); err != nil && err != dc.ErrNoSuchVolume {
var retVolume types.Volume
retVolume, err = client.VolumeInspect(ctx, d.Id())
if err != nil {
return fmt.Errorf("Unable to inspect volume: %s", err)
}
if retVolume == nil {
d.SetId("")
return nil
}
d.Set("name", retVolume.Name)
d.Set("driver", retVolume.Driver)
@ -94,35 +96,42 @@ func resourceDockerVolumeRead(d *schema.ResourceData, meta interface{}) error {
}
func resourceDockerVolumeDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ProviderConfig).DockerClient
log.Printf("[INFO] Waiting for volume: '%s' to get removed: max '%v seconds'", d.Id(), 30)
// TODO catch error if removal is already in progress + fix with statefunc
if err := client.RemoveVolume(d.Id()); err != nil && err != dc.ErrNoSuchVolume {
if err == dc.ErrVolumeInUse {
loops := 20
sleepTime := 1000 * time.Millisecond
for i := loops; i > 0; i-- {
if err = client.RemoveVolume(d.Id()); err != nil {
if err == dc.ErrVolumeInUse {
log.Printf("[INFO] Volume remove loop: %d of %d due to error: %s", loops-i+1, loops, err)
time.Sleep(sleepTime)
continue
}
if err == dc.ErrNoSuchVolume {
log.Printf("[INFO] Volume successfully removed")
d.SetId("")
return nil
}
if !strings.Contains(err.Error(), "is already in progress") {
// if it's not in use any more (so it's deleted successfully) and another error occurred
return fmt.Errorf("Error deleting volume %s: %s", d.Id(), err)
}
}
}
return fmt.Errorf("Error deleting volume %s: %s after %d tries", d.Id(), err, loops)
}
stateConf := &resource.StateChangeConf{
Pending: []string{"in_use"},
Target: []string{"removed"},
Refresh: resourceDockerVolumeRemoveRefreshFunc(d.Id(), meta),
Timeout: 30 * time.Second,
MinTimeout: 5 * time.Second,
Delay: 2 * time.Second,
}
// Wait, catching any errors
_, err := stateConf.WaitForState()
if err != nil {
return err
}
d.SetId("")
return nil
}
func resourceDockerVolumeRemoveRefreshFunc(
volumeID string, meta interface{}) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
client := meta.(*ProviderConfig).DockerClient
forceDelete := true
if err := client.VolumeRemove(context.Background(), volumeID, forceDelete); err != nil {
if strings.Contains(err.Error(), "volume is in use") { // store.IsInUse(err)
log.Printf("[INFO] Volume with id '%v' is still in use", volumeID)
return volumeID, "in_use", nil
}
log.Printf("[INFO] Removing volume with id '%v' caused an error: %v", volumeID, err)
return nil, "", err
}
log.Printf("[INFO] Removing volume with id '%v' got removed", volumeID)
return volumeID, "removed", nil
}
}

View file

@ -1,16 +1,16 @@
package docker
import (
"context"
"fmt"
"testing"
dc "github.com/fsouza/go-dockerclient"
"github.com/docker/docker/api/types"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"testing"
)
func TestAccDockerVolume_basic(t *testing.T) {
var v dc.Volume
var v types.Volume
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
@ -28,7 +28,7 @@ func TestAccDockerVolume_basic(t *testing.T) {
})
}
func checkDockerVolume(n string, volume *dc.Volume) resource.TestCheckFunc {
func checkDockerVolume(n string, volume *types.Volume) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
@ -39,24 +39,16 @@ func checkDockerVolume(n string, volume *dc.Volume) resource.TestCheckFunc {
return fmt.Errorf("No ID is set")
}
ctx := context.Background()
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
volumes, err := client.ListVolumes(dc.ListVolumesOptions{})
v, err := client.VolumeInspect(ctx, rs.Primary.ID)
if err != nil {
return err
}
for _, v := range volumes {
if v.Name == rs.Primary.ID {
inspected, err := client.InspectVolume(v.Name)
if err != nil {
return fmt.Errorf("Volume could not be inspected: %s", err)
}
*volume = *inspected
return nil
}
}
*volume = v
return fmt.Errorf("Volume not found: %s", rs.Primary.ID)
return nil
}
}

View file

@ -122,3 +122,13 @@ func validateStringIsBase64Encoded() schema.SchemaValidateFunc {
return
}
}
func validateDockerContainerPath(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[a-zA-Z]:\\|^/`).MatchString(value) {
errors = append(errors, fmt.Errorf("%q must be an absolute path", k))
}
return
}

30
scripts/compile.sh Executable file
View file

@ -0,0 +1,30 @@
#!/bin/bash
set -e
# Prerequisites
if ! command -v gox > /dev/null; then
go get -u github.com/mitchellh/gox
fi
# setup environment
PROVIDER_NAME="docker"
TARGET_DIR="$(pwd)/results"
XC_ARCH=${XC_ARCH:-"386 amd64 arm"}
XC_OS=${XC_OS:=linux darwin windows freebsd openbsd solaris}
XC_EXCLUDE_OSARCH="!darwin/arm !darwin/386 !solaris/amd64"
LD_FLAGS="-s -w"
export CGO_ENABLED=0
rm -rf "${TARGET_DIR}"
mkdir -p "${TARGET_DIR}"
# Compile
gox \
-os="${XC_OS}" \
-arch="${XC_ARCH}" \
-osarch="${XC_EXCLUDE_OSARCH}" \
-ldflags "${LD_FLAGS}" \
-output "$TARGET_DIR/{{.OS}}_{{.Arch}}/terraform-provider-${PROVIDER_NAME}_v0.0.0_x4" \
-verbose \
-rebuild \
.

View file

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

View file

@ -1,189 +0,0 @@
# This is the official list of go-dockerclient authors for copyright purposes.
Abhishek Chanda
Adam Bell-Hanssen
Adnan Khan
Adrien Kohlbecker
Aldrin Leal
Alex Dadgar
Alfonso Acosta
André Carvalho
Andreas Jaekle
Andrew Snodgrass
Andrews Medina
Andrey Sibiryov
Andy Goldstein
Anirudh Aithal
Antonio Murdaca
Artem Sidorenko
Arthur Rodrigues
Ben Marini
Ben McCann
Ben Parees
Benno van den Berg
Bradley Cicenas
Brendan Fosberry
Brian Lalor
Brian P. Hamachek
Brian Palmer
Bryan Boreham
Burke Libbey
Carlos Diaz-Padron
Carson A
Cássio Botaro
Cesar Wong
Cezar Sa Espinola
Changping Chen
Cheah Chu Yeow
cheneydeng
Chris Bednarski
Chris Stavropoulos
Christian Stewart
Christophe Mourette
Clint Armstrong
CMGS
Colin Hebert
Craig Jellick
Damien Lespiau
Damon Wang
Dan Williams
Daniel, Dao Quang Minh
Daniel Garcia
Daniel Hiltgen
Daniel Tsui
Darren Shepherd
Dave Choi
David Huie
Dawn Chen
Denis Makogon
Derek Petersen
Dinesh Subhraveti
Drew Wells
Ed
Elias G. Schneevoigt
Erez Horev
Eric Anderson
Eric J. Holmes
Eric Mountain
Erwin van Eyk
Ethan Mosbaugh
Ewout Prangsma
Fabio Rehm
Fatih Arslan
Felipe Oliveira
Flavia Missi
Florent Aide
Francisco Souza
Frank Groeneveld
George Moura
Grégoire Delattre
Guilherme Rezende
Guillermo Álvarez Fernández
Harry Zhang
He Simei
Isaac Schnitzer
Ivan Mikushin
James Bardin
James Nugent
Jamie Snell
Januar Wayong
Jari Kolehmainen
Jason Wilder
Jawher Moussa
Jean-Baptiste Dalido
Jeff Mitchell
Jeffrey Hulten
Jen Andre
Jérôme Laurens
Jim Minter
Johan Euphrosine
Johannes Scheuermann
John Hughes
Jorge Marey
Julian Einwag
Kamil Domanski
Karan Misra
Ken Herner
Kevin Lin
Kevin Xu
Kim, Hirokuni
Kostas Lekkas
Kyle Allan
Liron Levin
Lior Yankovich
Liu Peng
Lorenz Leutgeb
Lucas Clemente
Lucas Weiblen
Lyon Hill
Mantas Matelis
Manuel Vogel
Marguerite des Trois Maisons
Mariusz Borsa
Martin Sweeney
Máximo Cuadros Ortiz
Michael Schmatz
Michal Fojtik
Mike Dillon
Mrunal Patel
Nate Jones
Nguyen Sy Thanh Son
Nicholas Van Wiggeren
Nick Ethier
niko83
Omeid Matten
Orivej Desh
Paul Bellamy
Paul Morie
Paul Weil
Peter Edge
Peter Jihoon Kim
Peter Teich
Phil Lu
Philippe Lafoucrière
Radek Simko
Rafe Colton
Raphaël Pinson
Reed Allman
RJ Catalano
Rob Miller
Robbert Klarenbeek
Robert Williamson
Roman Khlystik
Russell Haering
Salvador Gironès
Sam Rijs
Sami Wagiaalla
Samuel Archambault
Samuel Karp
Sebastian Borza
Seth Jennings
Shane Xie
Silas Sewell
Simon Eskildsen
Simon Menke
Skolos
Soulou
Sridhar Ratnakumar
Steven Jack
Summer Mousa
Sunjin Lee
Sunny
Swaroop Ramachandra
Tarsis Azevedo
Tim Schindler
Timothy St. Clair
Tobi Knaup
Tom Wilkie
Tonic
ttyh061
upccup
Victor Marmol
Vincenzo Prignano
Vlad Alexandru Ionescu
Weitao Zhou
Wiliam Souza
Ye Yin
Yosuke Otosu
Yu, Zou
Yuriy Bogdanov

View file

@ -1,6 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
You can find the Docker license at the following link:
https://raw.githubusercontent.com/docker/docker/master/LICENSE

View file

@ -1,24 +0,0 @@
[[constraint]]
name = "github.com/Microsoft/go-winio"
version = "v0.4.5"
[[constraint]]
name = "github.com/docker/docker"
branch = "master"
[[constraint]]
name = "github.com/docker/go-units"
version = "v0.3.2"
[[constraint]]
name = "github.com/google/go-cmp"
version = "v0.2.0"
[[constraint]]
name = "github.com/gorilla/mux"
version = "v1.5.0"
[[override]]
name = "github.com/Nvveen/Gotty"
source = "https://github.com/ijc25/Gotty.git"
revision = "a8b993ba6abdb0e0c12b0125c603323a71c7790c"

View file

@ -1,22 +0,0 @@
Copyright (c) 2013-2018, go-dockerclient authors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,34 +0,0 @@
.PHONY: \
all \
lint \
vet \
fmtcheck \
pretest \
test \
integration
all: test
lint:
@ go get -v golang.org/x/lint/golint
[ -z "$$(golint . | grep -v 'type name will be used as docker.DockerInfo' | grep -v 'context.Context should be the first' | tee /dev/stderr)" ]
vet:
go vet ./...
fmtcheck:
[ -z "$$(gofmt -s -d *.go ./testing | tee /dev/stderr)" ]
testdeps:
go get -u github.com/golang/dep/cmd/dep
dep ensure -v
pretest: testdeps lint vet fmtcheck
gotest:
go test -race ./...
test: pretest gotest
integration:
go test -tags docker_integration -run TestIntegration -v

View file

@ -1,133 +0,0 @@
# go-dockerclient
[![Travis Build Status](https://travis-ci.org/fsouza/go-dockerclient.svg?branch=master)](https://travis-ci.org/fsouza/go-dockerclient)
[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/4m374pti06ubg2l7?svg=true)](https://ci.appveyor.com/project/fsouza/go-dockerclient)
[![GoDoc](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](https://godoc.org/github.com/fsouza/go-dockerclient)
This package presents a client for the Docker remote API. It also provides
support for the extensions in the [Swarm API](https://docs.docker.com/swarm/swarm-api/).
This package also provides support for docker's network API, which is a simple
passthrough to the libnetwork remote API. Note that docker's network API is
only available in docker 1.8 and above, and only enabled in docker if
DOCKER_EXPERIMENTAL is defined during the docker build process.
For more details, check the [remote API
documentation](http://docs.docker.com/engine/reference/api/docker_remote_api/).
## Example
```go
package main
import (
"fmt"
"github.com/fsouza/go-dockerclient"
)
func main() {
endpoint := "unix:///var/run/docker.sock"
client, err := docker.NewClient(endpoint)
if err != nil {
panic(err)
}
imgs, err := client.ListImages(docker.ListImagesOptions{All: false})
if err != nil {
panic(err)
}
for _, img := range imgs {
fmt.Println("ID: ", img.ID)
fmt.Println("RepoTags: ", img.RepoTags)
fmt.Println("Created: ", img.Created)
fmt.Println("Size: ", img.Size)
fmt.Println("VirtualSize: ", img.VirtualSize)
fmt.Println("ParentId: ", img.ParentID)
}
}
```
## Using with TLS
In order to instantiate the client for a TLS-enabled daemon, you should use
NewTLSClient, passing the endpoint and path for key and certificates as
parameters.
```go
package main
import (
"fmt"
"github.com/fsouza/go-dockerclient"
)
func main() {
endpoint := "tcp://[ip]:[port]"
path := os.Getenv("DOCKER_CERT_PATH")
ca := fmt.Sprintf("%s/ca.pem", path)
cert := fmt.Sprintf("%s/cert.pem", path)
key := fmt.Sprintf("%s/key.pem", path)
client, _ := docker.NewTLSClient(endpoint, cert, key, ca)
// use client
}
```
If using [docker-machine](https://docs.docker.com/machine/), or another
application that exports environment variables `DOCKER_HOST`,
`DOCKER_TLS_VERIFY`, `DOCKER_CERT_PATH`, you can use NewClientFromEnv.
```go
package main
import (
"fmt"
"github.com/fsouza/go-dockerclient"
)
func main() {
client, _ := docker.NewClientFromEnv()
// use client
}
```
See the documentation for more details.
## Developing
All development commands can be seen in the [Makefile](Makefile).
Commited code must pass:
* [golint](https://github.com/golang/lint) (with some exceptions, see the Makefile).
* [go vet](https://golang.org/cmd/vet/)
* [gofmt](https://golang.org/cmd/gofmt)
* [go test](https://golang.org/cmd/go/#hdr-Test_packages)
Running `make test` will check all of these. If your editor does not
automatically call ``gofmt -s``, `make fmt` will format all go files in this
repository.
## Vendoring
go-dockerclient uses [dep](https://github.com/golang/dep/) for vendoring. If
you're using dep, you should be able to pick go-dockerclient releases and get
the proper dependencies.
With other vendoring tools, users might need to specify go-dockerclient's
dependencies manually.
## Using with Docker 1.9 and Go 1.4
There's a tag for using go-dockerclient with Docker 1.9 (which requires
compiling go-dockerclient with Go 1.4), the tag name is ``docker-1.9/go-1.4``.
The instructions below can be used to get a version of go-dockerclient that compiles with Go 1.4:
```
% git clone -b docker-1.9/go-1.4 https://github.com/fsouza/go-dockerclient.git $GOPATH/src/github.com/fsouza/go-dockerclient
% git clone -b v1.9.1 https://github.com/docker/docker.git $GOPATH/src/github.com/docker/docker
% go get github.com/fsouza/go-dockerclient
```

View file

@ -1,21 +0,0 @@
version: '{build}'
platform: x64
clone_depth: 2
clone_folder: c:\gopath\src\github.com\fsouza\go-dockerclient
environment:
GOPATH: c:\gopath
matrix:
- GOVERSION: 1.9.4
- GOVERSION: 1.10
install:
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
- rmdir c:\go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.zip
- 7z x go%GOVERSION%.windows-amd64.zip -y -oC:\ > NUL
build_script:
- go get -u github.com/golang/dep/cmd/dep
- dep ensure -v
test_script:
- for /f "" %%G in ('go list ./... ^| find /i /v "/vendor/"') do ( go test %%G & IF ERRORLEVEL == 1 EXIT 1)
matrix:
fast_finish: true

View file

@ -1,185 +0,0 @@
// Copyright 2015 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"strings"
)
// ErrCannotParseDockercfg is the error returned by NewAuthConfigurations when the dockercfg cannot be parsed.
var ErrCannotParseDockercfg = errors.New("Failed to read authentication from dockercfg")
// AuthConfiguration represents authentication options to use in the PushImage
// method. It represents the authentication in the Docker index server.
type AuthConfiguration struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Email string `json:"email,omitempty"`
ServerAddress string `json:"serveraddress,omitempty"`
}
// AuthConfigurations represents authentication options to use for the
// PushImage method accommodating the new X-Registry-Config header
type AuthConfigurations struct {
Configs map[string]AuthConfiguration `json:"configs"`
}
// AuthConfigurations119 is used to serialize a set of AuthConfigurations
// for Docker API >= 1.19.
type AuthConfigurations119 map[string]AuthConfiguration
// dockerConfig represents a registry authentation configuration from the
// .dockercfg file.
type dockerConfig struct {
Auth string `json:"auth"`
Email string `json:"email"`
}
// NewAuthConfigurationsFromFile returns AuthConfigurations from a path containing JSON
// in the same format as the .dockercfg file.
func NewAuthConfigurationsFromFile(path string) (*AuthConfigurations, error) {
r, err := os.Open(path)
if err != nil {
return nil, err
}
return NewAuthConfigurations(r)
}
func cfgPaths(dockerConfigEnv string, homeEnv string) []string {
var paths []string
if dockerConfigEnv != "" {
paths = append(paths, path.Join(dockerConfigEnv, "config.json"))
}
if homeEnv != "" {
paths = append(paths, path.Join(homeEnv, ".docker", "config.json"))
paths = append(paths, path.Join(homeEnv, ".dockercfg"))
}
return paths
}
// NewAuthConfigurationsFromDockerCfg returns AuthConfigurations from
// system config files. The following files are checked in the order listed:
// - $DOCKER_CONFIG/config.json if DOCKER_CONFIG set in the environment,
// - $HOME/.docker/config.json
// - $HOME/.dockercfg
func NewAuthConfigurationsFromDockerCfg() (*AuthConfigurations, error) {
err := fmt.Errorf("No docker configuration found")
var auths *AuthConfigurations
pathsToTry := cfgPaths(os.Getenv("DOCKER_CONFIG"), os.Getenv("HOME"))
for _, path := range pathsToTry {
auths, err = NewAuthConfigurationsFromFile(path)
if err == nil {
return auths, nil
}
}
return auths, err
}
// NewAuthConfigurations returns AuthConfigurations from a JSON encoded string in the
// same format as the .dockercfg file.
func NewAuthConfigurations(r io.Reader) (*AuthConfigurations, error) {
var auth *AuthConfigurations
confs, err := parseDockerConfig(r)
if err != nil {
return nil, err
}
auth, err = authConfigs(confs)
if err != nil {
return nil, err
}
return auth, nil
}
func parseDockerConfig(r io.Reader) (map[string]dockerConfig, error) {
buf := new(bytes.Buffer)
buf.ReadFrom(r)
byteData := buf.Bytes()
confsWrapper := struct {
Auths map[string]dockerConfig `json:"auths"`
}{}
if err := json.Unmarshal(byteData, &confsWrapper); err == nil {
if len(confsWrapper.Auths) > 0 {
return confsWrapper.Auths, nil
}
}
var confs map[string]dockerConfig
if err := json.Unmarshal(byteData, &confs); err != nil {
return nil, err
}
return confs, nil
}
// authConfigs converts a dockerConfigs map to a AuthConfigurations object.
func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) {
c := &AuthConfigurations{
Configs: make(map[string]AuthConfiguration),
}
for reg, conf := range confs {
if conf.Auth == "" {
continue
}
data, err := base64.StdEncoding.DecodeString(conf.Auth)
if err != nil {
return nil, err
}
userpass := strings.SplitN(string(data), ":", 2)
if len(userpass) != 2 {
return nil, ErrCannotParseDockercfg
}
c.Configs[reg] = AuthConfiguration{
Email: conf.Email,
Username: userpass[0],
Password: userpass[1],
ServerAddress: reg,
}
}
return c, nil
}
// AuthStatus returns the authentication status for Docker API versions >= 1.23.
type AuthStatus struct {
Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"`
IdentityToken string `json:"IdentityToken,omitempty" yaml:"IdentityToken,omitempty" toml:"IdentityToken,omitempty"`
}
// AuthCheck validates the given credentials. It returns nil if successful.
//
// For Docker API versions >= 1.23, the AuthStatus struct will be populated, otherwise it will be empty.`
//
// See https://goo.gl/6nsZkH for more details.
func (c *Client) AuthCheck(conf *AuthConfiguration) (AuthStatus, error) {
var authStatus AuthStatus
if conf == nil {
return authStatus, errors.New("conf is nil")
}
resp, err := c.do("POST", "/auth", doOptions{data: conf})
if err != nil {
return authStatus, err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return authStatus, err
}
if len(data) == 0 {
return authStatus, nil
}
if err := json.Unmarshal(data, &authStatus); err != nil {
return authStatus, err
}
return authStatus, nil
}

View file

@ -1,43 +0,0 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import "fmt"
// ChangeType is a type for constants indicating the type of change
// in a container
type ChangeType int
const (
// ChangeModify is the ChangeType for container modifications
ChangeModify ChangeType = iota
// ChangeAdd is the ChangeType for additions to a container
ChangeAdd
// ChangeDelete is the ChangeType for deletions from a container
ChangeDelete
)
// Change represents a change in a container.
//
// See https://goo.gl/Wo0JJp for more details.
type Change struct {
Path string
Kind ChangeType
}
func (change *Change) String() string {
var kind string
switch change.Kind {
case ChangeModify:
kind = "C"
case ChangeAdd:
kind = "A"
case ChangeDelete:
kind = "D"
}
return fmt.Sprintf("%s %s", kind, change.Path)
}

File diff suppressed because it is too large Load diff

View file

@ -1,32 +0,0 @@
// Copyright 2016 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !windows
package docker
import (
"context"
"net"
"net/http"
)
// initializeNativeClient initializes the native Unix domain socket client on
// Unix-style operating systems
func (c *Client) initializeNativeClient(trFunc func() *http.Transport) {
if c.endpointURL.Scheme != unixProtocol {
return
}
sockPath := c.endpointURL.Path
tr := trFunc()
tr.Dial = func(network, addr string) (net.Conn, error) {
return c.Dialer.Dial(unixProtocol, sockPath)
}
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return c.Dialer.Dial(unixProtocol, sockPath)
}
c.HTTPClient.Transport = tr
}

View file

@ -1,45 +0,0 @@
// Copyright 2016 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package docker
import (
"context"
"net"
"net/http"
"time"
"github.com/Microsoft/go-winio"
)
const namedPipeConnectTimeout = 2 * time.Second
type pipeDialer struct {
dialFunc func(network, addr string) (net.Conn, error)
}
func (p pipeDialer) Dial(network, address string) (net.Conn, error) {
return p.dialFunc(network, address)
}
// initializeNativeClient initializes the native Named Pipe client for Windows
func (c *Client) initializeNativeClient(trFunc func() *http.Transport) {
if c.endpointURL.Scheme != namedPipeProtocol {
return
}
namedPipePath := c.endpointURL.Path
dialFunc := func(network, addr string) (net.Conn, error) {
timeout := namedPipeConnectTimeout
return winio.DialPipe(namedPipePath, &timeout)
}
tr := trFunc()
tr.Dial = dialFunc
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialFunc(network, addr)
}
c.Dialer = &pipeDialer{dialFunc}
c.HTTPClient.Transport = tr
}

File diff suppressed because it is too large Load diff

View file

@ -1,26 +0,0 @@
// Copyright 2017 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"encoding/json"
"github.com/docker/docker/api/types/registry"
)
// InspectDistribution returns image digest and platform information by contacting the registry
func (c *Client) InspectDistribution(name string) (*registry.DistributionInspect, error) {
path := "/distribution/" + name + "/json"
resp, err := c.do("GET", path, doOptions{})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var distributionInspect registry.DistributionInspect
if err := json.NewDecoder(resp.Body).Decode(&distributionInspect); err != nil {
return nil, err
}
return &distributionInspect, nil
}

View file

@ -1,172 +0,0 @@
// Copyright 2014 Docker authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the DOCKER-LICENSE file.
package docker
import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
)
// Env represents a list of key-pair represented in the form KEY=VALUE.
type Env []string
// Get returns the string value of the given key.
func (env *Env) Get(key string) (value string) {
return env.Map()[key]
}
// Exists checks whether the given key is defined in the internal Env
// representation.
func (env *Env) Exists(key string) bool {
_, exists := env.Map()[key]
return exists
}
// GetBool returns a boolean representation of the given key. The key is false
// whenever its value if 0, no, false, none or an empty string. Any other value
// will be interpreted as true.
func (env *Env) GetBool(key string) (value bool) {
s := strings.ToLower(strings.Trim(env.Get(key), " \t"))
if s == "" || s == "0" || s == "no" || s == "false" || s == "none" {
return false
}
return true
}
// SetBool defines a boolean value to the given key.
func (env *Env) SetBool(key string, value bool) {
if value {
env.Set(key, "1")
} else {
env.Set(key, "0")
}
}
// GetInt returns the value of the provided key, converted to int.
//
// It the value cannot be represented as an integer, it returns -1.
func (env *Env) GetInt(key string) int {
return int(env.GetInt64(key))
}
// SetInt defines an integer value to the given key.
func (env *Env) SetInt(key string, value int) {
env.Set(key, strconv.Itoa(value))
}
// GetInt64 returns the value of the provided key, converted to int64.
//
// It the value cannot be represented as an integer, it returns -1.
func (env *Env) GetInt64(key string) int64 {
s := strings.Trim(env.Get(key), " \t")
val, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return -1
}
return val
}
// SetInt64 defines an integer (64-bit wide) value to the given key.
func (env *Env) SetInt64(key string, value int64) {
env.Set(key, strconv.FormatInt(value, 10))
}
// GetJSON unmarshals the value of the provided key in the provided iface.
//
// iface is a value that can be provided to the json.Unmarshal function.
func (env *Env) GetJSON(key string, iface interface{}) error {
sval := env.Get(key)
if sval == "" {
return nil
}
return json.Unmarshal([]byte(sval), iface)
}
// SetJSON marshals the given value to JSON format and stores it using the
// provided key.
func (env *Env) SetJSON(key string, value interface{}) error {
sval, err := json.Marshal(value)
if err != nil {
return err
}
env.Set(key, string(sval))
return nil
}
// GetList returns a list of strings matching the provided key. It handles the
// list as a JSON representation of a list of strings.
//
// If the given key matches to a single string, it will return a list
// containing only the value that matches the key.
func (env *Env) GetList(key string) []string {
sval := env.Get(key)
if sval == "" {
return nil
}
var l []string
if err := json.Unmarshal([]byte(sval), &l); err != nil {
l = append(l, sval)
}
return l
}
// SetList stores the given list in the provided key, after serializing it to
// JSON format.
func (env *Env) SetList(key string, value []string) error {
return env.SetJSON(key, value)
}
// Set defines the value of a key to the given string.
func (env *Env) Set(key, value string) {
*env = append(*env, key+"="+value)
}
// Decode decodes `src` as a json dictionary, and adds each decoded key-value
// pair to the environment.
//
// If `src` cannot be decoded as a json dictionary, an error is returned.
func (env *Env) Decode(src io.Reader) error {
m := make(map[string]interface{})
if err := json.NewDecoder(src).Decode(&m); err != nil {
return err
}
for k, v := range m {
env.SetAuto(k, v)
}
return nil
}
// SetAuto will try to define the Set* method to call based on the given value.
func (env *Env) SetAuto(key string, value interface{}) {
if fval, ok := value.(float64); ok {
env.SetInt64(key, int64(fval))
} else if sval, ok := value.(string); ok {
env.Set(key, sval)
} else if val, err := json.Marshal(value); err == nil {
env.Set(key, string(val))
} else {
env.Set(key, fmt.Sprintf("%v", value))
}
}
// Map returns the map representation of the env.
func (env *Env) Map() map[string]string {
if len(*env) == 0 {
return nil
}
m := make(map[string]string)
for _, kv := range *env {
parts := strings.SplitN(kv, "=", 2)
if len(parts) == 1 {
m[parts[0]] = ""
} else {
m[parts[0]] = parts[1]
}
}
return m
}

View file

@ -1,395 +0,0 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"encoding/json"
"errors"
"fmt"
"io"
"math"
"net"
"net/http"
"net/http/httputil"
"sync"
"sync/atomic"
"time"
)
// APIEvents represents events coming from the Docker API
// The fields in the Docker API changed in API version 1.22, and
// events for more than images and containers are now fired off.
// To maintain forward and backward compatibility, go-dockerclient
// replicates the event in both the new and old format as faithfully as possible.
//
// For events that only exist in 1.22 in later, `Status` is filled in as
// `"Type:Action"` instead of just `Action` to allow for older clients to
// differentiate and not break if they rely on the pre-1.22 Status types.
//
// The transformEvent method can be consulted for more information about how
// events are translated from new/old API formats
type APIEvents struct {
// New API Fields in 1.22
Action string `json:"action,omitempty"`
Type string `json:"type,omitempty"`
Actor APIActor `json:"actor,omitempty"`
// Old API fields for < 1.22
Status string `json:"status,omitempty"`
ID string `json:"id,omitempty"`
From string `json:"from,omitempty"`
// Fields in both
Time int64 `json:"time,omitempty"`
TimeNano int64 `json:"timeNano,omitempty"`
}
// APIActor represents an actor that accomplishes something for an event
type APIActor struct {
ID string `json:"id,omitempty"`
Attributes map[string]string `json:"attributes,omitempty"`
}
type eventMonitoringState struct {
// `sync/atomic` expects the first word in an allocated struct to be 64-bit
// aligned on both ARM and x86-32. See https://goo.gl/zW7dgq for more details.
lastSeen int64
sync.RWMutex
sync.WaitGroup
enabled bool
C chan *APIEvents
errC chan error
listeners []chan<- *APIEvents
}
const (
maxMonitorConnRetries = 5
retryInitialWaitTime = 10.
)
var (
// ErrNoListeners is the error returned when no listeners are available
// to receive an event.
ErrNoListeners = errors.New("no listeners present to receive event")
// ErrListenerAlreadyExists is the error returned when the listerner already
// exists.
ErrListenerAlreadyExists = errors.New("listener already exists for docker events")
// ErrTLSNotSupported is the error returned when the client does not support
// TLS (this applies to the Windows named pipe client).
ErrTLSNotSupported = errors.New("tls not supported by this client")
// EOFEvent is sent when the event listener receives an EOF error.
EOFEvent = &APIEvents{
Type: "EOF",
Status: "EOF",
}
)
// AddEventListener adds a new listener to container events in the Docker API.
//
// The parameter is a channel through which events will be sent.
func (c *Client) AddEventListener(listener chan<- *APIEvents) error {
var err error
if !c.eventMonitor.isEnabled() {
err = c.eventMonitor.enableEventMonitoring(c)
if err != nil {
return err
}
}
return c.eventMonitor.addListener(listener)
}
// RemoveEventListener removes a listener from the monitor.
func (c *Client) RemoveEventListener(listener chan *APIEvents) error {
err := c.eventMonitor.removeListener(listener)
if err != nil {
return err
}
if c.eventMonitor.listernersCount() == 0 {
c.eventMonitor.disableEventMonitoring()
}
return nil
}
func (eventState *eventMonitoringState) addListener(listener chan<- *APIEvents) error {
eventState.Lock()
defer eventState.Unlock()
if listenerExists(listener, &eventState.listeners) {
return ErrListenerAlreadyExists
}
eventState.Add(1)
eventState.listeners = append(eventState.listeners, listener)
return nil
}
func (eventState *eventMonitoringState) removeListener(listener chan<- *APIEvents) error {
eventState.Lock()
defer eventState.Unlock()
if listenerExists(listener, &eventState.listeners) {
var newListeners []chan<- *APIEvents
for _, l := range eventState.listeners {
if l != listener {
newListeners = append(newListeners, l)
}
}
eventState.listeners = newListeners
eventState.Add(-1)
}
return nil
}
func (eventState *eventMonitoringState) closeListeners() {
for _, l := range eventState.listeners {
close(l)
eventState.Add(-1)
}
eventState.listeners = nil
}
func (eventState *eventMonitoringState) listernersCount() int {
eventState.RLock()
defer eventState.RUnlock()
return len(eventState.listeners)
}
func listenerExists(a chan<- *APIEvents, list *[]chan<- *APIEvents) bool {
for _, b := range *list {
if b == a {
return true
}
}
return false
}
func (eventState *eventMonitoringState) enableEventMonitoring(c *Client) error {
eventState.Lock()
defer eventState.Unlock()
if !eventState.enabled {
eventState.enabled = true
atomic.StoreInt64(&eventState.lastSeen, 0)
eventState.C = make(chan *APIEvents, 100)
eventState.errC = make(chan error, 1)
go eventState.monitorEvents(c)
}
return nil
}
func (eventState *eventMonitoringState) disableEventMonitoring() error {
eventState.Lock()
defer eventState.Unlock()
eventState.closeListeners()
eventState.Wait()
if eventState.enabled {
eventState.enabled = false
close(eventState.C)
close(eventState.errC)
}
return nil
}
func (eventState *eventMonitoringState) monitorEvents(c *Client) {
var err error
for eventState.noListeners() {
time.Sleep(10 * time.Millisecond)
}
if err = eventState.connectWithRetry(c); err != nil {
// terminate if connect failed
eventState.disableEventMonitoring()
return
}
for eventState.isEnabled() {
timeout := time.After(100 * time.Millisecond)
select {
case ev, ok := <-eventState.C:
if !ok {
return
}
if ev == EOFEvent {
eventState.disableEventMonitoring()
return
}
eventState.updateLastSeen(ev)
eventState.sendEvent(ev)
case err = <-eventState.errC:
if err == ErrNoListeners {
eventState.disableEventMonitoring()
return
} else if err != nil {
defer func() { go eventState.monitorEvents(c) }()
return
}
case <-timeout:
continue
}
}
}
func (eventState *eventMonitoringState) connectWithRetry(c *Client) error {
var retries int
eventState.RLock()
eventChan := eventState.C
errChan := eventState.errC
eventState.RUnlock()
err := c.eventHijack(atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan)
for ; err != nil && retries < maxMonitorConnRetries; retries++ {
waitTime := int64(retryInitialWaitTime * math.Pow(2, float64(retries)))
time.Sleep(time.Duration(waitTime) * time.Millisecond)
eventState.RLock()
eventChan = eventState.C
errChan = eventState.errC
eventState.RUnlock()
err = c.eventHijack(atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan)
}
return err
}
func (eventState *eventMonitoringState) noListeners() bool {
eventState.RLock()
defer eventState.RUnlock()
return len(eventState.listeners) == 0
}
func (eventState *eventMonitoringState) isEnabled() bool {
eventState.RLock()
defer eventState.RUnlock()
return eventState.enabled
}
func (eventState *eventMonitoringState) sendEvent(event *APIEvents) {
eventState.RLock()
defer eventState.RUnlock()
eventState.Add(1)
defer eventState.Done()
if eventState.enabled {
if len(eventState.listeners) == 0 {
eventState.errC <- ErrNoListeners
return
}
for _, listener := range eventState.listeners {
select {
case listener <- event:
default:
}
}
}
}
func (eventState *eventMonitoringState) updateLastSeen(e *APIEvents) {
eventState.Lock()
defer eventState.Unlock()
if atomic.LoadInt64(&eventState.lastSeen) < e.Time {
atomic.StoreInt64(&eventState.lastSeen, e.Time)
}
}
func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan chan error) error {
uri := "/events"
if startTime != 0 {
uri += fmt.Sprintf("?since=%d", startTime)
}
protocol := c.endpointURL.Scheme
address := c.endpointURL.Path
if protocol != "unix" && protocol != "npipe" {
protocol = "tcp"
address = c.endpointURL.Host
}
var dial net.Conn
var err error
if c.TLSConfig == nil {
dial, err = c.Dialer.Dial(protocol, address)
} else {
netDialer, ok := c.Dialer.(*net.Dialer)
if !ok {
return ErrTLSNotSupported
}
dial, err = tlsDialWithDialer(netDialer, protocol, address, c.TLSConfig)
}
if err != nil {
return err
}
conn := httputil.NewClientConn(dial, nil)
req, err := http.NewRequest("GET", uri, nil)
if err != nil {
return err
}
res, err := conn.Do(req)
if err != nil {
return err
}
go func(res *http.Response, conn *httputil.ClientConn) {
defer conn.Close()
defer res.Body.Close()
decoder := json.NewDecoder(res.Body)
for {
var event APIEvents
if err = decoder.Decode(&event); err != nil {
if err == io.EOF || err == io.ErrUnexpectedEOF {
c.eventMonitor.RLock()
if c.eventMonitor.enabled && c.eventMonitor.C == eventChan {
// Signal that we're exiting.
eventChan <- EOFEvent
}
c.eventMonitor.RUnlock()
break
}
errChan <- err
}
if event.Time == 0 {
continue
}
transformEvent(&event)
c.eventMonitor.RLock()
if c.eventMonitor.enabled && c.eventMonitor.C == eventChan {
eventChan <- &event
}
c.eventMonitor.RUnlock()
}
}(res, conn)
return nil
}
// transformEvent takes an event and determines what version it is from
// then populates both versions of the event
func transformEvent(event *APIEvents) {
// if event version is <= 1.21 there will be no Action and no Type
if event.Action == "" && event.Type == "" {
event.Action = event.Status
event.Actor.ID = event.ID
event.Actor.Attributes = map[string]string{}
switch event.Status {
case "delete", "import", "pull", "push", "tag", "untag":
event.Type = "image"
default:
event.Type = "container"
if event.From != "" {
event.Actor.Attributes["image"] = event.From
}
}
} else {
if event.Status == "" {
if event.Type == "image" || event.Type == "container" {
event.Status = event.Action
} else {
// Because just the Status has been overloaded with different Types
// if an event is not for an image or a container, we prepend the type
// to avoid problems for people relying on actions being only for
// images and containers
event.Status = event.Type + ":" + event.Action
}
}
if event.ID == "" {
event.ID = event.Actor.ID
}
if event.From == "" {
event.From = event.Actor.Attributes["image"]
}
}
}

View file

@ -1,213 +0,0 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
)
// Exec is the type representing a `docker exec` instance and containing the
// instance ID
type Exec struct {
ID string `json:"Id,omitempty" yaml:"Id,omitempty"`
}
// CreateExecOptions specify parameters to the CreateExecContainer function.
//
// See https://goo.gl/60TeBP for more details
type CreateExecOptions struct {
AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty" toml:"AttachStdin,omitempty"`
AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty" toml:"AttachStdout,omitempty"`
AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty" toml:"AttachStderr,omitempty"`
Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"`
Env []string `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"`
Cmd []string `json:"Cmd,omitempty" yaml:"Cmd,omitempty" toml:"Cmd,omitempty"`
Container string `json:"Container,omitempty" yaml:"Container,omitempty" toml:"Container,omitempty"`
User string `json:"User,omitempty" yaml:"User,omitempty" toml:"User,omitempty"`
Context context.Context `json:"-"`
Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty" toml:"Privileged,omitempty"`
}
// CreateExec sets up an exec instance in a running container `id`, returning the exec
// instance, or an error in case of failure.
//
// See https://goo.gl/60TeBP for more details
func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) {
if len(opts.Env) > 0 && c.serverAPIVersion.LessThan(apiVersion125) {
return nil, errors.New("exec configuration Env is only supported in API#1.25 and above")
}
path := fmt.Sprintf("/containers/%s/exec", opts.Container)
resp, err := c.do("POST", path, doOptions{data: opts, context: opts.Context})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: opts.Container}
}
return nil, err
}
defer resp.Body.Close()
var exec Exec
if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil {
return nil, err
}
return &exec, nil
}
// StartExecOptions specify parameters to the StartExecContainer function.
//
// See https://goo.gl/1EeDWi for more details
type StartExecOptions struct {
InputStream io.Reader `qs:"-"`
OutputStream io.Writer `qs:"-"`
ErrorStream io.Writer `qs:"-"`
Detach bool `json:"Detach,omitempty" yaml:"Detach,omitempty" toml:"Detach,omitempty"`
Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"`
// Use raw terminal? Usually true when the container contains a TTY.
RawTerminal bool `qs:"-"`
// If set, after a successful connect, a sentinel will be sent and then the
// client will block on receive before continuing.
//
// It must be an unbuffered channel. Using a buffered channel can lead
// to unexpected behavior.
Success chan struct{} `json:"-"`
Context context.Context `json:"-"`
}
// StartExec starts a previously set up exec instance id. If opts.Detach is
// true, it returns after starting the exec command. Otherwise, it sets up an
// interactive session with the exec command.
//
// See https://goo.gl/1EeDWi for more details
func (c *Client) StartExec(id string, opts StartExecOptions) error {
cw, err := c.StartExecNonBlocking(id, opts)
if err != nil {
return err
}
if cw != nil {
return cw.Wait()
}
return nil
}
// StartExecNonBlocking starts a previously set up exec instance id. If opts.Detach is
// true, it returns after starting the exec command. Otherwise, it sets up an
// interactive session with the exec command.
//
// See https://goo.gl/1EeDWi for more details
func (c *Client) StartExecNonBlocking(id string, opts StartExecOptions) (CloseWaiter, error) {
if id == "" {
return nil, &NoSuchExec{ID: id}
}
path := fmt.Sprintf("/exec/%s/start", id)
if opts.Detach {
resp, err := c.do("POST", path, doOptions{data: opts, context: opts.Context})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, &NoSuchExec{ID: id}
}
return nil, err
}
defer resp.Body.Close()
return nil, nil
}
return c.hijack("POST", path, hijackOptions{
success: opts.Success,
setRawTerminal: opts.RawTerminal,
in: opts.InputStream,
stdout: opts.OutputStream,
stderr: opts.ErrorStream,
data: opts,
})
}
// ResizeExecTTY resizes the tty session used by the exec command id. This API
// is valid only if Tty was specified as part of creating and starting the exec
// command.
//
// See https://goo.gl/Mo5bxx for more details
func (c *Client) ResizeExecTTY(id string, height, width int) error {
params := make(url.Values)
params.Set("h", strconv.Itoa(height))
params.Set("w", strconv.Itoa(width))
path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode())
resp, err := c.do("POST", path, doOptions{})
if err != nil {
return err
}
resp.Body.Close()
return nil
}
// ExecProcessConfig is a type describing the command associated to a Exec
// instance. It's used in the ExecInspect type.
type ExecProcessConfig struct {
User string `json:"user,omitempty" yaml:"user,omitempty" toml:"user,omitempty"`
Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty" toml:"privileged,omitempty"`
Tty bool `json:"tty,omitempty" yaml:"tty,omitempty" toml:"tty,omitempty"`
EntryPoint string `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty" toml:"entrypoint,omitempty"`
Arguments []string `json:"arguments,omitempty" yaml:"arguments,omitempty" toml:"arguments,omitempty"`
}
// ExecInspect is a type with details about a exec instance, including the
// exit code if the command has finished running. It's returned by a api
// call to /exec/(id)/json
//
// See https://goo.gl/ctMUiW for more details
type ExecInspect struct {
ID string `json:"ID,omitempty" yaml:"ID,omitempty" toml:"ID,omitempty"`
ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty" toml:"ExitCode,omitempty"`
Running bool `json:"Running,omitempty" yaml:"Running,omitempty" toml:"Running,omitempty"`
OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty" toml:"OpenStdin,omitempty"`
OpenStderr bool `json:"OpenStderr,omitempty" yaml:"OpenStderr,omitempty" toml:"OpenStderr,omitempty"`
OpenStdout bool `json:"OpenStdout,omitempty" yaml:"OpenStdout,omitempty" toml:"OpenStdout,omitempty"`
ProcessConfig ExecProcessConfig `json:"ProcessConfig,omitempty" yaml:"ProcessConfig,omitempty" toml:"ProcessConfig,omitempty"`
ContainerID string `json:"ContainerID,omitempty" yaml:"ContainerID,omitempty" toml:"ContainerID,omitempty"`
DetachKeys string `json:"DetachKeys,omitempty" yaml:"DetachKeys,omitempty" toml:"DetachKeys,omitempty"`
CanRemove bool `json:"CanRemove,omitempty" yaml:"CanRemove,omitempty" toml:"CanRemove,omitempty"`
}
// InspectExec returns low-level information about the exec command id.
//
// See https://goo.gl/ctMUiW for more details
func (c *Client) InspectExec(id string) (*ExecInspect, error) {
path := fmt.Sprintf("/exec/%s/json", id)
resp, err := c.do("GET", path, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, &NoSuchExec{ID: id}
}
return nil, err
}
defer resp.Body.Close()
var exec ExecInspect
if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil {
return nil, err
}
return &exec, nil
}
// NoSuchExec is the error returned when a given exec instance does not exist.
type NoSuchExec struct {
ID string
}
func (err *NoSuchExec) Error() string {
return "No such exec instance: " + err.ID
}

View file

@ -1,719 +0,0 @@
// Copyright 2013 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"time"
)
// APIImages represent an image returned in the ListImages call.
type APIImages struct {
ID string `json:"Id" yaml:"Id" toml:"Id"`
RepoTags []string `json:"RepoTags,omitempty" yaml:"RepoTags,omitempty" toml:"RepoTags,omitempty"`
Created int64 `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"`
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty" toml:"Size,omitempty"`
VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty" toml:"VirtualSize,omitempty"`
ParentID string `json:"ParentId,omitempty" yaml:"ParentId,omitempty" toml:"ParentId,omitempty"`
RepoDigests []string `json:"RepoDigests,omitempty" yaml:"RepoDigests,omitempty" toml:"RepoDigests,omitempty"`
Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"`
}
// RootFS represents the underlying layers used by an image
type RootFS struct {
Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"`
Layers []string `json:"Layers,omitempty" yaml:"Layers,omitempty" toml:"Layers,omitempty"`
}
// Image is the type representing a docker image and its various properties
type Image struct {
ID string `json:"Id" yaml:"Id" toml:"Id"`
RepoTags []string `json:"RepoTags,omitempty" yaml:"RepoTags,omitempty" toml:"RepoTags,omitempty"`
Parent string `json:"Parent,omitempty" yaml:"Parent,omitempty" toml:"Parent,omitempty"`
Comment string `json:"Comment,omitempty" yaml:"Comment,omitempty" toml:"Comment,omitempty"`
Created time.Time `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"`
Container string `json:"Container,omitempty" yaml:"Container,omitempty" toml:"Container,omitempty"`
ContainerConfig Config `json:"ContainerConfig,omitempty" yaml:"ContainerConfig,omitempty" toml:"ContainerConfig,omitempty"`
DockerVersion string `json:"DockerVersion,omitempty" yaml:"DockerVersion,omitempty" toml:"DockerVersion,omitempty"`
Author string `json:"Author,omitempty" yaml:"Author,omitempty" toml:"Author,omitempty"`
Config *Config `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"`
Architecture string `json:"Architecture,omitempty" yaml:"Architecture,omitempty"`
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty" toml:"Size,omitempty"`
VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty" toml:"VirtualSize,omitempty"`
RepoDigests []string `json:"RepoDigests,omitempty" yaml:"RepoDigests,omitempty" toml:"RepoDigests,omitempty"`
RootFS *RootFS `json:"RootFS,omitempty" yaml:"RootFS,omitempty" toml:"RootFS,omitempty"`
OS string `json:"Os,omitempty" yaml:"Os,omitempty" toml:"Os,omitempty"`
}
// ImagePre012 serves the same purpose as the Image type except that it is for
// earlier versions of the Docker API (pre-012 to be specific)
type ImagePre012 struct {
ID string `json:"id"`
Parent string `json:"parent,omitempty"`
Comment string `json:"comment,omitempty"`
Created time.Time `json:"created"`
Container string `json:"container,omitempty"`
ContainerConfig Config `json:"container_config,omitempty"`
DockerVersion string `json:"docker_version,omitempty"`
Author string `json:"author,omitempty"`
Config *Config `json:"config,omitempty"`
Architecture string `json:"architecture,omitempty"`
Size int64 `json:"size,omitempty"`
}
var (
// ErrNoSuchImage is the error returned when the image does not exist.
ErrNoSuchImage = errors.New("no such image")
// ErrMissingRepo is the error returned when the remote repository is
// missing.
ErrMissingRepo = errors.New("missing remote repository e.g. 'github.com/user/repo'")
// ErrMissingOutputStream is the error returned when no output stream
// is provided to some calls, like BuildImage.
ErrMissingOutputStream = errors.New("missing output stream")
// ErrMultipleContexts is the error returned when both a ContextDir and
// InputStream are provided in BuildImageOptions
ErrMultipleContexts = errors.New("image build may not be provided BOTH context dir and input stream")
// ErrMustSpecifyNames is the error rreturned when the Names field on
// ExportImagesOptions is nil or empty
ErrMustSpecifyNames = errors.New("must specify at least one name to export")
)
// ListImagesOptions specify parameters to the ListImages function.
//
// See https://goo.gl/BVzauZ for more details.
type ListImagesOptions struct {
Filters map[string][]string
All bool
Digests bool
Filter string
Context context.Context
}
// ListImages returns the list of available images in the server.
//
// See https://goo.gl/BVzauZ for more details.
func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) {
path := "/images/json?" + queryString(opts)
resp, err := c.do("GET", path, doOptions{context: opts.Context})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var images []APIImages
if err := json.NewDecoder(resp.Body).Decode(&images); err != nil {
return nil, err
}
return images, nil
}
// ImageHistory represent a layer in an image's history returned by the
// ImageHistory call.
type ImageHistory struct {
ID string `json:"Id" yaml:"Id" toml:"Id"`
Tags []string `json:"Tags,omitempty" yaml:"Tags,omitempty" toml:"Tags,omitempty"`
Created int64 `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Tags,omitempty"`
CreatedBy string `json:"CreatedBy,omitempty" yaml:"CreatedBy,omitempty" toml:"CreatedBy,omitempty"`
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty" toml:"Size,omitempty"`
}
// ImageHistory returns the history of the image by its name or ID.
//
// See https://goo.gl/fYtxQa for more details.
func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
resp, err := c.do("GET", "/images/"+name+"/history", doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, ErrNoSuchImage
}
return nil, err
}
defer resp.Body.Close()
var history []ImageHistory
if err := json.NewDecoder(resp.Body).Decode(&history); err != nil {
return nil, err
}
return history, nil
}
// RemoveImage removes an image by its name or ID.
//
// See https://goo.gl/Vd2Pck for more details.
func (c *Client) RemoveImage(name string) error {
resp, err := c.do("DELETE", "/images/"+name, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return ErrNoSuchImage
}
return err
}
resp.Body.Close()
return nil
}
// RemoveImageOptions present the set of options available for removing an image
// from a registry.
//
// See https://goo.gl/Vd2Pck for more details.
type RemoveImageOptions struct {
Force bool `qs:"force"`
NoPrune bool `qs:"noprune"`
Context context.Context
}
// RemoveImageExtended removes an image by its name or ID.
// Extra params can be passed, see RemoveImageOptions
//
// See https://goo.gl/Vd2Pck for more details.
func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error {
uri := fmt.Sprintf("/images/%s?%s", name, queryString(&opts))
resp, err := c.do("DELETE", uri, doOptions{context: opts.Context})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return ErrNoSuchImage
}
return err
}
resp.Body.Close()
return nil
}
// InspectImage returns an image by its name or ID.
//
// See https://goo.gl/ncLTG8 for more details.
func (c *Client) InspectImage(name string) (*Image, error) {
resp, err := c.do("GET", "/images/"+name+"/json", doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, ErrNoSuchImage
}
return nil, err
}
defer resp.Body.Close()
var image Image
// if the caller elected to skip checking the server's version, assume it's the latest
if c.SkipServerVersionCheck || c.expectedAPIVersion.GreaterThanOrEqualTo(apiVersion112) {
if err := json.NewDecoder(resp.Body).Decode(&image); err != nil {
return nil, err
}
} else {
var imagePre012 ImagePre012
if err := json.NewDecoder(resp.Body).Decode(&imagePre012); err != nil {
return nil, err
}
image.ID = imagePre012.ID
image.Parent = imagePre012.Parent
image.Comment = imagePre012.Comment
image.Created = imagePre012.Created
image.Container = imagePre012.Container
image.ContainerConfig = imagePre012.ContainerConfig
image.DockerVersion = imagePre012.DockerVersion
image.Author = imagePre012.Author
image.Config = imagePre012.Config
image.Architecture = imagePre012.Architecture
image.Size = imagePre012.Size
}
return &image, nil
}
// PushImageOptions represents options to use in the PushImage method.
//
// See https://goo.gl/BZemGg for more details.
type PushImageOptions struct {
// Name of the image
Name string
// Tag of the image
Tag string
// Registry server to push the image
Registry string
OutputStream io.Writer `qs:"-"`
RawJSONStream bool `qs:"-"`
InactivityTimeout time.Duration `qs:"-"`
Context context.Context
}
// PushImage pushes an image to a remote registry, logging progress to w.
//
// An empty instance of AuthConfiguration may be used for unauthenticated
// pushes.
//
// See https://goo.gl/BZemGg for more details.
func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error {
if opts.Name == "" {
return ErrNoSuchImage
}
headers, err := headersWithAuth(auth)
if err != nil {
return err
}
name := opts.Name
opts.Name = ""
path := "/images/" + name + "/push?" + queryString(&opts)
return c.stream("POST", path, streamOptions{
setRawTerminal: true,
rawJSONStream: opts.RawJSONStream,
headers: headers,
stdout: opts.OutputStream,
inactivityTimeout: opts.InactivityTimeout,
context: opts.Context,
})
}
// PullImageOptions present the set of options available for pulling an image
// from a registry.
//
// See https://goo.gl/qkoSsn for more details.
type PullImageOptions struct {
Repository string `qs:"fromImage"`
Tag string
// Only required for Docker Engine 1.9 or 1.10 w/ Remote API < 1.21
// and Docker Engine < 1.9
// This parameter was removed in Docker Engine 1.11
Registry string
OutputStream io.Writer `qs:"-"`
RawJSONStream bool `qs:"-"`
InactivityTimeout time.Duration `qs:"-"`
Context context.Context
}
// PullImage pulls an image from a remote registry, logging progress to
// opts.OutputStream.
//
// See https://goo.gl/qkoSsn for more details.
func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error {
if opts.Repository == "" {
return ErrNoSuchImage
}
headers, err := headersWithAuth(auth)
if err != nil {
return err
}
if opts.Tag == "" && strings.Contains(opts.Repository, "@") {
parts := strings.SplitN(opts.Repository, "@", 2)
opts.Repository = parts[0]
opts.Tag = parts[1]
}
return c.createImage(queryString(&opts), headers, nil, opts.OutputStream, opts.RawJSONStream, opts.InactivityTimeout, opts.Context)
}
func (c *Client) createImage(qs string, headers map[string]string, in io.Reader, w io.Writer, rawJSONStream bool, timeout time.Duration, context context.Context) error {
path := "/images/create?" + qs
return c.stream("POST", path, streamOptions{
setRawTerminal: true,
headers: headers,
in: in,
stdout: w,
rawJSONStream: rawJSONStream,
inactivityTimeout: timeout,
context: context,
})
}
// LoadImageOptions represents the options for LoadImage Docker API Call
//
// See https://goo.gl/rEsBV3 for more details.
type LoadImageOptions struct {
InputStream io.Reader
OutputStream io.Writer
Context context.Context
}
// LoadImage imports a tarball docker image
//
// See https://goo.gl/rEsBV3 for more details.
func (c *Client) LoadImage(opts LoadImageOptions) error {
return c.stream("POST", "/images/load", streamOptions{
setRawTerminal: true,
in: opts.InputStream,
stdout: opts.OutputStream,
context: opts.Context,
})
}
// ExportImageOptions represent the options for ExportImage Docker API call.
//
// See https://goo.gl/AuySaA for more details.
type ExportImageOptions struct {
Name string
OutputStream io.Writer
InactivityTimeout time.Duration
Context context.Context
}
// ExportImage exports an image (as a tar file) into the stream.
//
// See https://goo.gl/AuySaA for more details.
func (c *Client) ExportImage(opts ExportImageOptions) error {
return c.stream("GET", fmt.Sprintf("/images/%s/get", opts.Name), streamOptions{
setRawTerminal: true,
stdout: opts.OutputStream,
inactivityTimeout: opts.InactivityTimeout,
context: opts.Context,
})
}
// ExportImagesOptions represent the options for ExportImages Docker API call
//
// See https://goo.gl/N9XlDn for more details.
type ExportImagesOptions struct {
Names []string
OutputStream io.Writer `qs:"-"`
InactivityTimeout time.Duration `qs:"-"`
Context context.Context
}
// ExportImages exports one or more images (as a tar file) into the stream
//
// See https://goo.gl/N9XlDn for more details.
func (c *Client) ExportImages(opts ExportImagesOptions) error {
if opts.Names == nil || len(opts.Names) == 0 {
return ErrMustSpecifyNames
}
return c.stream("GET", "/images/get?"+queryString(&opts), streamOptions{
setRawTerminal: true,
stdout: opts.OutputStream,
inactivityTimeout: opts.InactivityTimeout,
})
}
// ImportImageOptions present the set of informations available for importing
// an image from a source file or the stdin.
//
// See https://goo.gl/qkoSsn for more details.
type ImportImageOptions struct {
Repository string `qs:"repo"`
Source string `qs:"fromSrc"`
Tag string `qs:"tag"`
InputStream io.Reader `qs:"-"`
OutputStream io.Writer `qs:"-"`
RawJSONStream bool `qs:"-"`
InactivityTimeout time.Duration `qs:"-"`
Context context.Context
}
// ImportImage imports an image from a url, a file or stdin
//
// See https://goo.gl/qkoSsn for more details.
func (c *Client) ImportImage(opts ImportImageOptions) error {
if opts.Repository == "" {
return ErrNoSuchImage
}
if opts.Source != "-" {
opts.InputStream = nil
}
if opts.Source != "-" && !isURL(opts.Source) {
f, err := os.Open(opts.Source)
if err != nil {
return err
}
opts.InputStream = f
opts.Source = "-"
}
return c.createImage(queryString(&opts), nil, opts.InputStream, opts.OutputStream, opts.RawJSONStream, opts.InactivityTimeout, opts.Context)
}
// BuildImageOptions present the set of informations available for building an
// image from a tarfile with a Dockerfile in it.
//
// For more details about the Docker building process, see
// https://goo.gl/4nYHwV.
type BuildImageOptions struct {
Name string `qs:"t"`
Dockerfile string `qs:"dockerfile"`
NoCache bool `qs:"nocache"`
CacheFrom []string `qs:"-"`
SuppressOutput bool `qs:"q"`
Pull bool `qs:"pull"`
RmTmpContainer bool `qs:"rm"`
ForceRmTmpContainer bool `qs:"forcerm"`
RawJSONStream bool `qs:"-"`
Memory int64 `qs:"memory"`
Memswap int64 `qs:"memswap"`
CPUShares int64 `qs:"cpushares"`
CPUQuota int64 `qs:"cpuquota"`
CPUPeriod int64 `qs:"cpuperiod"`
CPUSetCPUs string `qs:"cpusetcpus"`
Labels map[string]string `qs:"labels"`
InputStream io.Reader `qs:"-"`
OutputStream io.Writer `qs:"-"`
Remote string `qs:"remote"`
Auth AuthConfiguration `qs:"-"` // for older docker X-Registry-Auth header
AuthConfigs AuthConfigurations `qs:"-"` // for newer docker X-Registry-Config header
ContextDir string `qs:"-"`
Ulimits []ULimit `qs:"-"`
BuildArgs []BuildArg `qs:"-"`
NetworkMode string `qs:"networkmode"`
InactivityTimeout time.Duration `qs:"-"`
CgroupParent string `qs:"cgroupparent"`
SecurityOpt []string `qs:"securityopt"`
Context context.Context
}
// BuildArg represents arguments that can be passed to the image when building
// it from a Dockerfile.
//
// For more details about the Docker building process, see
// https://goo.gl/4nYHwV.
type BuildArg struct {
Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
Value string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"`
}
// BuildImage builds an image from a tarball's url or a Dockerfile in the input
// stream.
//
// See https://goo.gl/4nYHwV for more details.
func (c *Client) BuildImage(opts BuildImageOptions) error {
if opts.OutputStream == nil {
return ErrMissingOutputStream
}
headers, err := headersWithAuth(opts.Auth, c.versionedAuthConfigs(opts.AuthConfigs))
if err != nil {
return err
}
if opts.Remote != "" && opts.Name == "" {
opts.Name = opts.Remote
}
if opts.InputStream != nil || opts.ContextDir != "" {
headers["Content-Type"] = "application/tar"
} else if opts.Remote == "" {
return ErrMissingRepo
}
if opts.ContextDir != "" {
if opts.InputStream != nil {
return ErrMultipleContexts
}
var err error
if opts.InputStream, err = createTarStream(opts.ContextDir, opts.Dockerfile); err != nil {
return err
}
}
qs := queryString(&opts)
if c.serverAPIVersion.GreaterThanOrEqualTo(apiVersion125) && len(opts.CacheFrom) > 0 {
if b, err := json.Marshal(opts.CacheFrom); err == nil {
item := url.Values(map[string][]string{})
item.Add("cachefrom", string(b))
qs = fmt.Sprintf("%s&%s", qs, item.Encode())
}
}
if len(opts.Ulimits) > 0 {
if b, err := json.Marshal(opts.Ulimits); err == nil {
item := url.Values(map[string][]string{})
item.Add("ulimits", string(b))
qs = fmt.Sprintf("%s&%s", qs, item.Encode())
}
}
if len(opts.BuildArgs) > 0 {
v := make(map[string]string)
for _, arg := range opts.BuildArgs {
v[arg.Name] = arg.Value
}
if b, err := json.Marshal(v); err == nil {
item := url.Values(map[string][]string{})
item.Add("buildargs", string(b))
qs = fmt.Sprintf("%s&%s", qs, item.Encode())
}
}
return c.stream("POST", fmt.Sprintf("/build?%s", qs), streamOptions{
setRawTerminal: true,
rawJSONStream: opts.RawJSONStream,
headers: headers,
in: opts.InputStream,
stdout: opts.OutputStream,
inactivityTimeout: opts.InactivityTimeout,
context: opts.Context,
})
}
func (c *Client) versionedAuthConfigs(authConfigs AuthConfigurations) interface{} {
if c.serverAPIVersion == nil {
c.checkAPIVersion()
}
if c.serverAPIVersion != nil && c.serverAPIVersion.GreaterThanOrEqualTo(apiVersion119) {
return AuthConfigurations119(authConfigs.Configs)
}
return authConfigs
}
// TagImageOptions present the set of options to tag an image.
//
// See https://goo.gl/prHrvo for more details.
type TagImageOptions struct {
Repo string
Tag string
Force bool
Context context.Context
}
// TagImage adds a tag to the image identified by the given name.
//
// See https://goo.gl/prHrvo for more details.
func (c *Client) TagImage(name string, opts TagImageOptions) error {
if name == "" {
return ErrNoSuchImage
}
resp, err := c.do("POST", "/images/"+name+"/tag?"+queryString(&opts), doOptions{
context: opts.Context,
})
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return ErrNoSuchImage
}
return err
}
func isURL(u string) bool {
p, err := url.Parse(u)
if err != nil {
return false
}
return p.Scheme == "http" || p.Scheme == "https"
}
func headersWithAuth(auths ...interface{}) (map[string]string, error) {
var headers = make(map[string]string)
for _, auth := range auths {
switch auth.(type) {
case AuthConfiguration:
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(auth); err != nil {
return nil, err
}
headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes())
case AuthConfigurations, AuthConfigurations119:
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(auth); err != nil {
return nil, err
}
headers["X-Registry-Config"] = base64.URLEncoding.EncodeToString(buf.Bytes())
}
}
return headers, nil
}
// APIImageSearch reflect the result of a search on the Docker Hub.
//
// See https://goo.gl/KLO9IZ for more details.
type APIImageSearch struct {
Description string `json:"description,omitempty" yaml:"description,omitempty" toml:"description,omitempty"`
IsOfficial bool `json:"is_official,omitempty" yaml:"is_official,omitempty" toml:"is_official,omitempty"`
IsAutomated bool `json:"is_automated,omitempty" yaml:"is_automated,omitempty" toml:"is_automated,omitempty"`
Name string `json:"name,omitempty" yaml:"name,omitempty" toml:"name,omitempty"`
StarCount int `json:"star_count,omitempty" yaml:"star_count,omitempty" toml:"star_count,omitempty"`
}
// SearchImages search the docker hub with a specific given term.
//
// See https://goo.gl/KLO9IZ for more details.
func (c *Client) SearchImages(term string) ([]APIImageSearch, error) {
resp, err := c.do("GET", "/images/search?term="+term, doOptions{})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var searchResult []APIImageSearch
if err := json.NewDecoder(resp.Body).Decode(&searchResult); err != nil {
return nil, err
}
return searchResult, nil
}
// SearchImagesEx search the docker hub with a specific given term and authentication.
//
// See https://goo.gl/KLO9IZ for more details.
func (c *Client) SearchImagesEx(term string, auth AuthConfiguration) ([]APIImageSearch, error) {
headers, err := headersWithAuth(auth)
if err != nil {
return nil, err
}
resp, err := c.do("GET", "/images/search?term="+term, doOptions{
headers: headers,
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var searchResult []APIImageSearch
if err := json.NewDecoder(resp.Body).Decode(&searchResult); err != nil {
return nil, err
}
return searchResult, nil
}
// PruneImagesOptions specify parameters to the PruneImages function.
//
// See https://goo.gl/qfZlbZ for more details.
type PruneImagesOptions struct {
Filters map[string][]string
Context context.Context
}
// PruneImagesResults specify results from the PruneImages function.
//
// See https://goo.gl/qfZlbZ for more details.
type PruneImagesResults struct {
ImagesDeleted []struct{ Untagged, Deleted string }
SpaceReclaimed int64
}
// PruneImages deletes images which are unused.
//
// See https://goo.gl/qfZlbZ for more details.
func (c *Client) PruneImages(opts PruneImagesOptions) (*PruneImagesResults, error) {
path := "/images/prune?" + queryString(opts)
resp, err := c.do("POST", path, doOptions{context: opts.Context})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var results PruneImagesResults
if err := json.NewDecoder(resp.Body).Decode(&results); err != nil {
return nil, err
}
return &results, nil
}

View file

@ -1,182 +0,0 @@
// Copyright 2013 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"encoding/json"
"net"
"strings"
"github.com/docker/docker/api/types/swarm"
)
// Version returns version information about the docker server.
//
// See https://goo.gl/mU7yje for more details.
func (c *Client) Version() (*Env, error) {
resp, err := c.do("GET", "/version", doOptions{})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var env Env
if err := env.Decode(resp.Body); err != nil {
return nil, err
}
return &env, nil
}
// DockerInfo contains information about the Docker server
//
// See https://goo.gl/bHUoz9 for more details.
type DockerInfo struct {
ID string
Containers int
ContainersRunning int
ContainersPaused int
ContainersStopped int
Images int
Driver string
DriverStatus [][2]string
SystemStatus [][2]string
Plugins PluginsInfo
MemoryLimit bool
SwapLimit bool
KernelMemory bool
CPUCfsPeriod bool `json:"CpuCfsPeriod"`
CPUCfsQuota bool `json:"CpuCfsQuota"`
CPUShares bool
CPUSet bool
IPv4Forwarding bool
BridgeNfIptables bool
BridgeNfIP6tables bool `json:"BridgeNfIp6tables"`
Debug bool
OomKillDisable bool
ExperimentalBuild bool
NFd int
NGoroutines int
SystemTime string
ExecutionDriver string
LoggingDriver string
CgroupDriver string
NEventsListener int
KernelVersion string
OperatingSystem string
OSType string
Architecture string
IndexServerAddress string
RegistryConfig *ServiceConfig
SecurityOptions []string
NCPU int
MemTotal int64
DockerRootDir string
HTTPProxy string `json:"HttpProxy"`
HTTPSProxy string `json:"HttpsProxy"`
NoProxy string
Name string
Labels []string
ServerVersion string
ClusterStore string
ClusterAdvertise string
Isolation string
InitBinary string
DefaultRuntime string
LiveRestoreEnabled bool
Swarm swarm.Info
}
// PluginsInfo is a struct with the plugins registered with the docker daemon
//
// for more information, see: https://goo.gl/bHUoz9
type PluginsInfo struct {
// List of Volume plugins registered
Volume []string
// List of Network plugins registered
Network []string
// List of Authorization plugins registered
Authorization []string
}
// ServiceConfig stores daemon registry services configuration.
//
// for more information, see: https://goo.gl/7iFFDz
type ServiceConfig struct {
InsecureRegistryCIDRs []*NetIPNet
IndexConfigs map[string]*IndexInfo
Mirrors []string
}
// NetIPNet is the net.IPNet type, which can be marshalled and
// unmarshalled to JSON.
//
// for more information, see: https://goo.gl/7iFFDz
type NetIPNet net.IPNet
// MarshalJSON returns the JSON representation of the IPNet.
//
func (ipnet *NetIPNet) MarshalJSON() ([]byte, error) {
return json.Marshal((*net.IPNet)(ipnet).String())
}
// UnmarshalJSON sets the IPNet from a byte array of JSON.
//
func (ipnet *NetIPNet) UnmarshalJSON(b []byte) (err error) {
var ipnetStr string
if err = json.Unmarshal(b, &ipnetStr); err == nil {
var cidr *net.IPNet
if _, cidr, err = net.ParseCIDR(ipnetStr); err == nil {
*ipnet = NetIPNet(*cidr)
}
}
return
}
// IndexInfo contains information about a registry.
//
// for more information, see: https://goo.gl/7iFFDz
type IndexInfo struct {
Name string
Mirrors []string
Secure bool
Official bool
}
// Info returns system-wide information about the Docker server.
//
// See https://goo.gl/ElTHi2 for more details.
func (c *Client) Info() (*DockerInfo, error) {
resp, err := c.do("GET", "/info", doOptions{})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var info DockerInfo
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
return nil, err
}
return &info, nil
}
// ParseRepositoryTag gets the name of the repository and returns it splitted
// in two parts: the repository and the tag. It ignores the digest when it is
// present.
//
// Some examples:
//
// localhost.localdomain:5000/samalba/hipache:latest -> localhost.localdomain:5000/samalba/hipache, latest
// localhost.localdomain:5000/samalba/hipache -> localhost.localdomain:5000/samalba/hipache, ""
// busybox:latest@sha256:4a731fb46adc5cefe3ae374a8b6020fc1b6ad667a279647766e9a3cd89f6fa92 -> busybox, latest
func ParseRepositoryTag(repoTag string) (repository string, tag string) {
parts := strings.SplitN(repoTag, "@", 2)
repoTag = parts[0]
n := strings.LastIndex(repoTag, ":")
if n < 0 {
return repoTag, ""
}
if tag := repoTag[n+1:]; !strings.Contains(tag, "/") {
return repoTag[:n], tag
}
return repoTag, ""
}

View file

@ -1,321 +0,0 @@
// Copyright 2015 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
)
// ErrNetworkAlreadyExists is the error returned by CreateNetwork when the
// network already exists.
var ErrNetworkAlreadyExists = errors.New("network already exists")
// Network represents a network.
//
// See https://goo.gl/6GugX3 for more details.
type Network struct {
Name string
ID string `json:"Id"`
Scope string
Driver string
IPAM IPAMOptions
Containers map[string]Endpoint
Options map[string]string
Internal bool
EnableIPv6 bool `json:"EnableIPv6"`
Labels map[string]string
}
// Endpoint contains network resources allocated and used for a container in a network
//
// See https://goo.gl/6GugX3 for more details.
type Endpoint struct {
Name string
ID string `json:"EndpointID"`
MacAddress string
IPv4Address string
IPv6Address string
}
// ListNetworks returns all networks.
//
// See https://goo.gl/6GugX3 for more details.
func (c *Client) ListNetworks() ([]Network, error) {
resp, err := c.do("GET", "/networks", doOptions{})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var networks []Network
if err := json.NewDecoder(resp.Body).Decode(&networks); err != nil {
return nil, err
}
return networks, nil
}
// NetworkFilterOpts is an aggregation of key=value that Docker
// uses to filter networks
type NetworkFilterOpts map[string]map[string]bool
// FilteredListNetworks returns all networks with the filters applied
//
// See goo.gl/zd2mx4 for more details.
func (c *Client) FilteredListNetworks(opts NetworkFilterOpts) ([]Network, error) {
params, err := json.Marshal(opts)
if err != nil {
return nil, err
}
path := "/networks?filters=" + string(params)
resp, err := c.do("GET", path, doOptions{})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var networks []Network
if err := json.NewDecoder(resp.Body).Decode(&networks); err != nil {
return nil, err
}
return networks, nil
}
// NetworkInfo returns information about a network by its ID.
//
// See https://goo.gl/6GugX3 for more details.
func (c *Client) NetworkInfo(id string) (*Network, error) {
path := "/networks/" + id
resp, err := c.do("GET", path, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, &NoSuchNetwork{ID: id}
}
return nil, err
}
defer resp.Body.Close()
var network Network
if err := json.NewDecoder(resp.Body).Decode(&network); err != nil {
return nil, err
}
return &network, nil
}
// CreateNetworkOptions specify parameters to the CreateNetwork function and
// (for now) is the expected body of the "create network" http request message
//
// See https://goo.gl/6GugX3 for more details.
type CreateNetworkOptions struct {
Name string `json:"Name" yaml:"Name" toml:"Name"`
Driver string `json:"Driver" yaml:"Driver" toml:"Driver"`
IPAM *IPAMOptions `json:"IPAM,omitempty" yaml:"IPAM" toml:"IPAM"`
Options map[string]interface{} `json:"Options" yaml:"Options" toml:"Options"`
Labels map[string]string `json:"Labels" yaml:"Labels" toml:"Labels"`
CheckDuplicate bool `json:"CheckDuplicate" yaml:"CheckDuplicate" toml:"CheckDuplicate"`
Internal bool `json:"Internal" yaml:"Internal" toml:"Internal"`
EnableIPv6 bool `json:"EnableIPv6" yaml:"EnableIPv6" toml:"EnableIPv6"`
Context context.Context `json:"-"`
}
// IPAMOptions controls IP Address Management when creating a network
//
// See https://goo.gl/T8kRVH for more details.
type IPAMOptions struct {
Driver string `json:"Driver" yaml:"Driver" toml:"Driver"`
Config []IPAMConfig `json:"Config" yaml:"Config" toml:"Config"`
Options map[string]string `json:"Options" yaml:"Options" toml:"Options"`
}
// IPAMConfig represents IPAM configurations
//
// See https://goo.gl/T8kRVH for more details.
type IPAMConfig struct {
Subnet string `json:",omitempty"`
IPRange string `json:",omitempty"`
Gateway string `json:",omitempty"`
AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"`
}
// CreateNetwork creates a new network, returning the network instance,
// or an error in case of failure.
//
// See https://goo.gl/6GugX3 for more details.
func (c *Client) CreateNetwork(opts CreateNetworkOptions) (*Network, error) {
resp, err := c.do(
"POST",
"/networks/create",
doOptions{
data: opts,
context: opts.Context,
},
)
if err != nil {
return nil, err
}
defer resp.Body.Close()
type createNetworkResponse struct {
ID string
}
var (
network Network
cnr createNetworkResponse
)
if err := json.NewDecoder(resp.Body).Decode(&cnr); err != nil {
return nil, err
}
network.Name = opts.Name
network.ID = cnr.ID
network.Driver = opts.Driver
return &network, nil
}
// RemoveNetwork removes a network or returns an error in case of failure.
//
// See https://goo.gl/6GugX3 for more details.
func (c *Client) RemoveNetwork(id string) error {
resp, err := c.do("DELETE", "/networks/"+id, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return &NoSuchNetwork{ID: id}
}
return err
}
resp.Body.Close()
return nil
}
// NetworkConnectionOptions specify parameters to the ConnectNetwork and
// DisconnectNetwork function.
//
// See https://goo.gl/RV7BJU for more details.
type NetworkConnectionOptions struct {
Container string
// EndpointConfig is only applicable to the ConnectNetwork call
EndpointConfig *EndpointConfig `json:"EndpointConfig,omitempty"`
// Force is only applicable to the DisconnectNetwork call
Force bool
Context context.Context `json:"-"`
}
// EndpointConfig stores network endpoint details
//
// See https://goo.gl/RV7BJU for more details.
type EndpointConfig struct {
IPAMConfig *EndpointIPAMConfig `json:"IPAMConfig,omitempty" yaml:"IPAMConfig,omitempty" toml:"IPAMConfig,omitempty"`
Links []string `json:"Links,omitempty" yaml:"Links,omitempty" toml:"Links,omitempty"`
Aliases []string `json:"Aliases,omitempty" yaml:"Aliases,omitempty" toml:"Aliases,omitempty"`
NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty" toml:"NetworkID,omitempty"`
EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty" toml:"EndpointID,omitempty"`
Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty" toml:"Gateway,omitempty"`
IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty" toml:"IPAddress,omitempty"`
IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty" toml:"IPPrefixLen,omitempty"`
IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty" toml:"IPv6Gateway,omitempty"`
GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty" toml:"GlobalIPv6Address,omitempty"`
GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty" toml:"GlobalIPv6PrefixLen,omitempty"`
MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"`
}
// EndpointIPAMConfig represents IPAM configurations for an
// endpoint
//
// See https://goo.gl/RV7BJU for more details.
type EndpointIPAMConfig struct {
IPv4Address string `json:",omitempty"`
IPv6Address string `json:",omitempty"`
}
// ConnectNetwork adds a container to a network or returns an error in case of
// failure.
//
// See https://goo.gl/6GugX3 for more details.
func (c *Client) ConnectNetwork(id string, opts NetworkConnectionOptions) error {
resp, err := c.do("POST", "/networks/"+id+"/connect", doOptions{
data: opts,
context: opts.Context,
})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return &NoSuchNetworkOrContainer{NetworkID: id, ContainerID: opts.Container}
}
return err
}
resp.Body.Close()
return nil
}
// DisconnectNetwork removes a container from a network or returns an error in
// case of failure.
//
// See https://goo.gl/6GugX3 for more details.
func (c *Client) DisconnectNetwork(id string, opts NetworkConnectionOptions) error {
resp, err := c.do("POST", "/networks/"+id+"/disconnect", doOptions{data: opts})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return &NoSuchNetworkOrContainer{NetworkID: id, ContainerID: opts.Container}
}
return err
}
resp.Body.Close()
return nil
}
// PruneNetworksOptions specify parameters to the PruneNetworks function.
//
// See https://goo.gl/kX0S9h for more details.
type PruneNetworksOptions struct {
Filters map[string][]string
Context context.Context
}
// PruneNetworksResults specify results from the PruneNetworks function.
//
// See https://goo.gl/kX0S9h for more details.
type PruneNetworksResults struct {
NetworksDeleted []string
}
// PruneNetworks deletes networks which are unused.
//
// See https://goo.gl/kX0S9h for more details.
func (c *Client) PruneNetworks(opts PruneNetworksOptions) (*PruneNetworksResults, error) {
path := "/networks/prune?" + queryString(opts)
resp, err := c.do("POST", path, doOptions{context: opts.Context})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var results PruneNetworksResults
if err := json.NewDecoder(resp.Body).Decode(&results); err != nil {
return nil, err
}
return &results, nil
}
// NoSuchNetwork is the error returned when a given network does not exist.
type NoSuchNetwork struct {
ID string
}
func (err *NoSuchNetwork) Error() string {
return fmt.Sprintf("No such network: %s", err.ID)
}
// NoSuchNetworkOrContainer is the error returned when a given network or
// container does not exist.
type NoSuchNetworkOrContainer struct {
NetworkID string
ContainerID string
}
func (err *NoSuchNetworkOrContainer) Error() string {
return fmt.Sprintf("No such network (%s) or container (%s)", err.NetworkID, err.ContainerID)
}

View file

@ -1,391 +0,0 @@
// Copyright 2018 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
)
// PluginPrivilege represents a privilege for a plugin.
type PluginPrivilege struct {
Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"`
Value []string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"`
}
// InstallPluginOptions specify parameters to the InstallPlugins function.
//
// See https://goo.gl/C4t7Tz for more details.
type InstallPluginOptions struct {
Remote string
Name string
Plugins []PluginPrivilege `qs:"-"`
Auth AuthConfiguration
Context context.Context
}
// InstallPlugins installs a plugin or returns an error in case of failure.
//
// See https://goo.gl/C4t7Tz for more details.
func (c *Client) InstallPlugins(opts InstallPluginOptions) error {
path := "/plugins/pull?" + queryString(opts)
resp, err := c.do("POST", path, doOptions{
data: opts.Plugins,
context: opts.Context,
})
defer resp.Body.Close()
if err != nil {
return err
}
return nil
}
// PluginSettings stores plugin settings.
//
// See https://goo.gl/C4t7Tz for more details.
type PluginSettings struct {
Env []string `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"`
Args []string `json:"Args,omitempty" yaml:"Args,omitempty" toml:"Args,omitempty"`
Devices []string `json:"Devices,omitempty" yaml:"Devices,omitempty" toml:"Devices,omitempty"`
}
// PluginInterface stores plugin interface.
//
// See https://goo.gl/C4t7Tz for more details.
type PluginInterface struct {
Types []string `json:"Types,omitempty" yaml:"Types,omitempty" toml:"Types,omitempty"`
Socket string `json:"Socket,omitempty" yaml:"Socket,omitempty" toml:"Socket,omitempty"`
}
// PluginNetwork stores plugin network type.
//
// See https://goo.gl/C4t7Tz for more details.
type PluginNetwork struct {
Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"`
}
// PluginLinux stores plugin linux setting.
//
// See https://goo.gl/C4t7Tz for more details.
type PluginLinux struct {
Capabilities []string `json:"Capabilities,omitempty" yaml:"Capabilities,omitempty" toml:"Capabilities,omitempty"`
AllowAllDevices bool `json:"AllowAllDevices,omitempty" yaml:"AllowAllDevices,omitempty" toml:"AllowAllDevices,omitempty"`
Devices []PluginLinuxDevices `json:"Devices,omitempty" yaml:"Devices,omitempty" toml:"Devices,omitempty"`
}
// PluginLinuxDevices stores plugin linux device setting.
//
// See https://goo.gl/C4t7Tz for more details.
type PluginLinuxDevices struct {
Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
Description string `json:"Documentation,omitempty" yaml:"Documentation,omitempty" toml:"Documentation,omitempty"`
Settable []string `json:"Settable,omitempty" yaml:"Settable,omitempty" toml:"Settable,omitempty"`
Path string `json:"Path,omitempty" yaml:"Path,omitempty" toml:"Path,omitempty"`
}
// PluginEnv stores plugin environment.
//
// See https://goo.gl/C4t7Tz for more details.
type PluginEnv struct {
Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"`
Settable []string `json:"Settable,omitempty" yaml:"Settable,omitempty" toml:"Settable,omitempty"`
Value string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"`
}
// PluginArgs stores plugin arguments.
//
// See https://goo.gl/C4t7Tz for more details.
type PluginArgs struct {
Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"`
Settable []string `json:"Settable,omitempty" yaml:"Settable,omitempty" toml:"Settable,omitempty"`
Value []string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"`
}
// PluginUser stores plugin user.
//
// See https://goo.gl/C4t7Tz for more details.
type PluginUser struct {
UID int32 `json:"UID,omitempty" yaml:"UID,omitempty" toml:"UID,omitempty"`
GID int32 `json:"GID,omitempty" yaml:"GID,omitempty" toml:"GID,omitempty"`
}
// PluginConfig stores plugin config.
//
// See https://goo.gl/C4t7Tz for more details.
type PluginConfig struct {
Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"`
Documentation string
Interface PluginInterface `json:"Interface,omitempty" yaml:"Interface,omitempty" toml:"Interface,omitempty"`
Entrypoint []string `json:"Entrypoint,omitempty" yaml:"Entrypoint,omitempty" toml:"Entrypoint,omitempty"`
WorkDir string `json:"WorkDir,omitempty" yaml:"WorkDir,omitempty" toml:"WorkDir,omitempty"`
User PluginUser `json:"User,omitempty" yaml:"User,omitempty" toml:"User,omitempty"`
Network PluginNetwork `json:"Network,omitempty" yaml:"Network,omitempty" toml:"Network,omitempty"`
Linux PluginLinux `json:"Linux,omitempty" yaml:"Linux,omitempty" toml:"Linux,omitempty"`
PropagatedMount string `json:"PropagatedMount,omitempty" yaml:"PropagatedMount,omitempty" toml:"PropagatedMount,omitempty"`
Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"`
Env []PluginEnv `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"`
Args PluginArgs `json:"Args,omitempty" yaml:"Args,omitempty" toml:"Args,omitempty"`
}
// PluginDetail specify results from the ListPlugins function.
//
// See https://goo.gl/C4t7Tz for more details.
type PluginDetail struct {
ID string `json:"Id,omitempty" yaml:"Id,omitempty" toml:"Id,omitempty"`
Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
Tag string `json:"Tag,omitempty" yaml:"Tag,omitempty" toml:"Tag,omitempty"`
Active bool `json:"Active,omitempty" yaml:"Active,omitempty" toml:"Active,omitempty"`
Settings PluginSettings `json:"Settings,omitempty" yaml:"Settings,omitempty" toml:"Settings,omitempty"`
Config PluginConfig `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"`
}
// ListPlugins returns pluginDetails or an error.
//
// See https://goo.gl/C4t7Tz for more details.
func (c *Client) ListPlugins(ctx context.Context) ([]PluginDetail, error) {
resp, err := c.do("GET", "/plugins", doOptions{
context: ctx,
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
pluginDetails := make([]PluginDetail, 0)
if err := json.NewDecoder(resp.Body).Decode(&pluginDetails); err != nil {
return nil, err
}
return pluginDetails, nil
}
// GetPluginPrivileges returns pulginPrivileges or an error.
//
// See https://goo.gl/C4t7Tz for more details.
func (c *Client) GetPluginPrivileges(name string, ctx context.Context) ([]PluginPrivilege, error) {
resp, err := c.do("GET", "/plugins/privileges?remote="+name, doOptions{
context: ctx,
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var pluginPrivileges []PluginPrivilege
if err := json.NewDecoder(resp.Body).Decode(&pluginPrivileges); err != nil {
return nil, err
}
return pluginPrivileges, nil
}
// InspectPlugins returns a pluginDetail or an error.
//
// See https://goo.gl/C4t7Tz for more details.
func (c *Client) InspectPlugins(name string, ctx context.Context) (*PluginDetail, error) {
resp, err := c.do("GET", "/plugins/"+name+"/json", doOptions{
context: ctx,
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, &NoSuchPlugin{ID: name}
}
return nil, err
}
resp.Body.Close()
var pluginDetail PluginDetail
if err := json.NewDecoder(resp.Body).Decode(&pluginDetail); err != nil {
return nil, err
}
return &pluginDetail, nil
}
// RemovePluginOptions specify parameters to the RemovePlugin function.
//
// See https://goo.gl/C4t7Tz for more details.
type RemovePluginOptions struct {
// The Name of the plugin.
Name string `qs:"-"`
Force bool `qs:"force"`
Context context.Context
}
// RemovePlugin returns a PluginDetail or an error.
//
// See https://goo.gl/C4t7Tz for more details.
func (c *Client) RemovePlugin(opts RemovePluginOptions) (*PluginDetail, error) {
path := "/plugins/" + opts.Name + "?" + queryString(opts)
resp, err := c.do("DELETE", path, doOptions{context: opts.Context})
if err != nil {
return nil, err
}
defer resp.Body.Close()
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, &NoSuchPlugin{ID: opts.Name}
}
return nil, err
}
resp.Body.Close()
var pluginDetail PluginDetail
if err := json.NewDecoder(resp.Body).Decode(&pluginDetail); err != nil {
return nil, err
}
return &pluginDetail, nil
}
// EnablePluginOptions specify parameters to the EnablePlugin function.
//
// See https://goo.gl/C4t7Tz for more details.
type EnablePluginOptions struct {
// The Name of the plugin.
Name string `qs:"-"`
Timeout int64 `qs:"timeout"`
Context context.Context
}
// EnablePlugin enables plugin that opts point or returns an error.
//
// See https://goo.gl/C4t7Tz for more details.
func (c *Client) EnablePlugin(opts EnablePluginOptions) error {
path := "/plugins/" + opts.Name + "/enable?" + queryString(opts)
resp, err := c.do("POST", path, doOptions{context: opts.Context})
defer resp.Body.Close()
if err != nil {
return err
}
resp.Body.Close()
return nil
}
// DisablePluginOptions specify parameters to the DisablePlugin function.
//
// See https://goo.gl/C4t7Tz for more details.
type DisablePluginOptions struct {
// The Name of the plugin.
Name string `qs:"-"`
Context context.Context
}
// DisablePlugin disables plugin that opts point or returns an error.
//
// See https://goo.gl/C4t7Tz for more details.
func (c *Client) DisablePlugin(opts DisablePluginOptions) error {
path := "/plugins/" + opts.Name + "/disable"
resp, err := c.do("POST", path, doOptions{context: opts.Context})
defer resp.Body.Close()
if err != nil {
return err
}
resp.Body.Close()
return nil
}
// CreatePluginOptions specify parameters to the CreatePlugin function.
//
// See https://goo.gl/C4t7Tz for more details.
type CreatePluginOptions struct {
// The Name of the plugin.
Name string `qs:"name"`
// Path to tar containing plugin
Path string `qs:"-"`
Context context.Context
}
// CreatePlugin creates plugin that opts point or returns an error.
//
// See https://goo.gl/C4t7Tz for more details.
func (c *Client) CreatePlugin(opts CreatePluginOptions) (string, error) {
path := "/plugins/create?" + queryString(opts)
resp, err := c.do("POST", path, doOptions{
data: opts.Path,
context: opts.Context})
defer resp.Body.Close()
if err != nil {
return "", err
}
containerNameBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(containerNameBytes), nil
}
// PushPluginOptions specify parameters to PushPlugin function.
//
// See https://goo.gl/C4t7Tz for more details.
type PushPluginOptions struct {
// The Name of the plugin.
Name string
Context context.Context
}
// PushPlugin pushes plugin that opts point or returns an error.
//
// See https://goo.gl/C4t7Tz for more details.
func (c *Client) PushPlugin(opts PushPluginOptions) error {
path := "/plugins/" + opts.Name + "/push"
resp, err := c.do("POST", path, doOptions{context: opts.Context})
defer resp.Body.Close()
if err != nil {
return err
}
return nil
}
// ConfigurePluginOptions specify parameters to the ConfigurePlugin
//
// See https://goo.gl/C4t7Tz for more details.
type ConfigurePluginOptions struct {
// The Name of the plugin.
Name string `qs:"name"`
Envs []string
Context context.Context
}
// ConfigurePlugin configures plugin that opts point or returns an error.
//
// See https://goo.gl/C4t7Tz for more details.
func (c *Client) ConfigurePlugin(opts ConfigurePluginOptions) error {
path := "/plugins/" + opts.Name + "/set"
resp, err := c.do("POST", path, doOptions{
data: opts.Envs,
context: opts.Context,
})
defer resp.Body.Close()
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return &NoSuchPlugin{ID: opts.Name}
}
return err
}
return nil
}
// NoSuchPlugin is the error returned when a given plugin does not exist.
type NoSuchPlugin struct {
ID string
Err error
}
func (err *NoSuchPlugin) Error() string {
if err.Err != nil {
return err.Err.Error()
}
return "No such plugin: " + err.ID
}

View file

@ -1,49 +0,0 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
// Signal represents a signal that can be send to the container on
// KillContainer call.
type Signal int
// These values represent all signals available on Linux, where containers will
// be running.
const (
SIGABRT = Signal(0x6)
SIGALRM = Signal(0xe)
SIGBUS = Signal(0x7)
SIGCHLD = Signal(0x11)
SIGCLD = Signal(0x11)
SIGCONT = Signal(0x12)
SIGFPE = Signal(0x8)
SIGHUP = Signal(0x1)
SIGILL = Signal(0x4)
SIGINT = Signal(0x2)
SIGIO = Signal(0x1d)
SIGIOT = Signal(0x6)
SIGKILL = Signal(0x9)
SIGPIPE = Signal(0xd)
SIGPOLL = Signal(0x1d)
SIGPROF = Signal(0x1b)
SIGPWR = Signal(0x1e)
SIGQUIT = Signal(0x3)
SIGSEGV = Signal(0xb)
SIGSTKFLT = Signal(0x10)
SIGSTOP = Signal(0x13)
SIGSYS = Signal(0x1f)
SIGTERM = Signal(0xf)
SIGTRAP = Signal(0x5)
SIGTSTP = Signal(0x14)
SIGTTIN = Signal(0x15)
SIGTTOU = Signal(0x16)
SIGUNUSED = Signal(0x1f)
SIGURG = Signal(0x17)
SIGUSR1 = Signal(0xa)
SIGUSR2 = Signal(0xc)
SIGVTALRM = Signal(0x1a)
SIGWINCH = Signal(0x1c)
SIGXCPU = Signal(0x18)
SIGXFSZ = Signal(0x19)
)

View file

@ -1,156 +0,0 @@
// Copyright 2016 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/url"
"strconv"
"github.com/docker/docker/api/types/swarm"
)
var (
// ErrNodeAlreadyInSwarm is the error returned by InitSwarm and JoinSwarm
// when the node is already part of a Swarm.
ErrNodeAlreadyInSwarm = errors.New("node already in a Swarm")
// ErrNodeNotInSwarm is the error returned by LeaveSwarm and UpdateSwarm
// when the node is not part of a Swarm.
ErrNodeNotInSwarm = errors.New("node is not in a Swarm")
)
// InitSwarmOptions specify parameters to the InitSwarm function.
// See https://goo.gl/hzkgWu for more details.
type InitSwarmOptions struct {
swarm.InitRequest
Context context.Context
}
// InitSwarm initializes a new Swarm and returns the node ID.
// See https://goo.gl/ZWyG1M for more details.
func (c *Client) InitSwarm(opts InitSwarmOptions) (string, error) {
path := "/swarm/init"
resp, err := c.do("POST", path, doOptions{
data: opts.InitRequest,
forceJSON: true,
context: opts.Context,
})
if err != nil {
if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) {
return "", ErrNodeAlreadyInSwarm
}
return "", err
}
defer resp.Body.Close()
var response string
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return "", err
}
return response, nil
}
// JoinSwarmOptions specify parameters to the JoinSwarm function.
// See https://goo.gl/TdhJWU for more details.
type JoinSwarmOptions struct {
swarm.JoinRequest
Context context.Context
}
// JoinSwarm joins an existing Swarm.
// See https://goo.gl/N59IP1 for more details.
func (c *Client) JoinSwarm(opts JoinSwarmOptions) error {
path := "/swarm/join"
resp, err := c.do("POST", path, doOptions{
data: opts.JoinRequest,
forceJSON: true,
context: opts.Context,
})
if err != nil {
if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) {
return ErrNodeAlreadyInSwarm
}
}
resp.Body.Close()
return err
}
// LeaveSwarmOptions specify parameters to the LeaveSwarm function.
// See https://goo.gl/UWDlLg for more details.
type LeaveSwarmOptions struct {
Force bool
Context context.Context
}
// LeaveSwarm leaves a Swarm.
// See https://goo.gl/FTX1aD for more details.
func (c *Client) LeaveSwarm(opts LeaveSwarmOptions) error {
params := make(url.Values)
params.Set("force", strconv.FormatBool(opts.Force))
path := "/swarm/leave?" + params.Encode()
resp, err := c.do("POST", path, doOptions{
context: opts.Context,
})
if err != nil {
if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) {
return ErrNodeNotInSwarm
}
}
resp.Body.Close()
return err
}
// UpdateSwarmOptions specify parameters to the UpdateSwarm function.
// See https://goo.gl/vFbq36 for more details.
type UpdateSwarmOptions struct {
Version int
RotateWorkerToken bool
RotateManagerToken bool
Swarm swarm.Spec
Context context.Context
}
// UpdateSwarm updates a Swarm.
// See https://goo.gl/iJFnsw for more details.
func (c *Client) UpdateSwarm(opts UpdateSwarmOptions) error {
params := make(url.Values)
params.Set("version", strconv.Itoa(opts.Version))
params.Set("rotateWorkerToken", strconv.FormatBool(opts.RotateWorkerToken))
params.Set("rotateManagerToken", strconv.FormatBool(opts.RotateManagerToken))
path := "/swarm/update?" + params.Encode()
resp, err := c.do("POST", path, doOptions{
data: opts.Swarm,
forceJSON: true,
context: opts.Context,
})
if err != nil {
if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) {
return ErrNodeNotInSwarm
}
}
resp.Body.Close()
return err
}
// InspectSwarm inspects a Swarm.
// See https://goo.gl/MFwgX9 for more details.
func (c *Client) InspectSwarm(ctx context.Context) (swarm.Swarm, error) {
response := swarm.Swarm{}
resp, err := c.do("GET", "/swarm", doOptions{
context: ctx,
})
if err != nil {
if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) {
return response, ErrNodeNotInSwarm
}
return response, err
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
}

View file

@ -1,171 +0,0 @@
// Copyright 2017 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"context"
"encoding/json"
"net/http"
"net/url"
"strconv"
"github.com/docker/docker/api/types/swarm"
)
// NoSuchConfig is the error returned when a given config does not exist.
type NoSuchConfig struct {
ID string
Err error
}
func (err *NoSuchConfig) Error() string {
if err.Err != nil {
return err.Err.Error()
}
return "No such config: " + err.ID
}
// CreateConfigOptions specify parameters to the CreateConfig function.
//
// See https://goo.gl/KrVjHz for more details.
type CreateConfigOptions struct {
Auth AuthConfiguration `qs:"-"`
swarm.ConfigSpec
Context context.Context
}
// CreateConfig creates a new config, returning the config instance
// or an error in case of failure.
//
// See https://goo.gl/KrVjHz for more details.
func (c *Client) CreateConfig(opts CreateConfigOptions) (*swarm.Config, error) {
headers, err := headersWithAuth(opts.Auth)
if err != nil {
return nil, err
}
path := "/configs/create?" + queryString(opts)
resp, err := c.do("POST", path, doOptions{
headers: headers,
data: opts.ConfigSpec,
forceJSON: true,
context: opts.Context,
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var config swarm.Config
if err := json.NewDecoder(resp.Body).Decode(&config); err != nil {
return nil, err
}
return &config, nil
}
// RemoveConfigOptions encapsulates options to remove a config.
//
// See https://goo.gl/Tqrtya for more details.
type RemoveConfigOptions struct {
ID string `qs:"-"`
Context context.Context
}
// RemoveConfig removes a config, returning an error in case of failure.
//
// See https://goo.gl/Tqrtya for more details.
func (c *Client) RemoveConfig(opts RemoveConfigOptions) error {
path := "/configs/" + opts.ID
resp, err := c.do("DELETE", path, doOptions{context: opts.Context})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return &NoSuchConfig{ID: opts.ID}
}
return err
}
resp.Body.Close()
return nil
}
// UpdateConfigOptions specify parameters to the UpdateConfig function.
//
// See https://goo.gl/wu3MmS for more details.
type UpdateConfigOptions struct {
Auth AuthConfiguration `qs:"-"`
swarm.ConfigSpec
Context context.Context
Version uint64
}
// UpdateConfig updates the config at ID with the options
//
// Only label can be updated
// https://docs.docker.com/engine/api/v1.33/#operation/ConfigUpdate
// See https://goo.gl/wu3MmS for more details.
func (c *Client) UpdateConfig(id string, opts UpdateConfigOptions) error {
headers, err := headersWithAuth(opts.Auth)
if err != nil {
return err
}
params := make(url.Values)
params.Set("version", strconv.FormatUint(opts.Version, 10))
resp, err := c.do("POST", "/configs/"+id+"/update?"+params.Encode(), doOptions{
headers: headers,
data: opts.ConfigSpec,
forceJSON: true,
context: opts.Context,
})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return &NoSuchConfig{ID: id}
}
return err
}
defer resp.Body.Close()
return nil
}
// InspectConfig returns information about a config by its ID.
//
// See https://goo.gl/dHmr75 for more details.
func (c *Client) InspectConfig(id string) (*swarm.Config, error) {
path := "/configs/" + id
resp, err := c.do("GET", path, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, &NoSuchConfig{ID: id}
}
return nil, err
}
defer resp.Body.Close()
var config swarm.Config
if err := json.NewDecoder(resp.Body).Decode(&config); err != nil {
return nil, err
}
return &config, nil
}
// ListConfigsOptions specify parameters to the ListConfigs function.
//
// See https://goo.gl/DwvNMd for more details.
type ListConfigsOptions struct {
Filters map[string][]string
Context context.Context
}
// ListConfigs returns a slice of configs matching the given criteria.
//
// See https://goo.gl/DwvNMd for more details.
func (c *Client) ListConfigs(opts ListConfigsOptions) ([]swarm.Config, error) {
path := "/configs?" + queryString(opts)
resp, err := c.do("GET", path, doOptions{context: opts.Context})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var configs []swarm.Config
if err := json.NewDecoder(resp.Body).Decode(&configs); err != nil {
return nil, err
}
return configs, nil
}

View file

@ -1,130 +0,0 @@
// Copyright 2016 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"context"
"encoding/json"
"net/http"
"net/url"
"strconv"
"github.com/docker/docker/api/types/swarm"
)
// NoSuchNode is the error returned when a given node does not exist.
type NoSuchNode struct {
ID string
Err error
}
func (err *NoSuchNode) Error() string {
if err.Err != nil {
return err.Err.Error()
}
return "No such node: " + err.ID
}
// ListNodesOptions specify parameters to the ListNodes function.
//
// See http://goo.gl/3K4GwU for more details.
type ListNodesOptions struct {
Filters map[string][]string
Context context.Context
}
// ListNodes returns a slice of nodes matching the given criteria.
//
// See http://goo.gl/3K4GwU for more details.
func (c *Client) ListNodes(opts ListNodesOptions) ([]swarm.Node, error) {
path := "/nodes?" + queryString(opts)
resp, err := c.do("GET", path, doOptions{context: opts.Context})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var nodes []swarm.Node
if err := json.NewDecoder(resp.Body).Decode(&nodes); err != nil {
return nil, err
}
return nodes, nil
}
// InspectNode returns information about a node by its ID.
//
// See http://goo.gl/WjkTOk for more details.
func (c *Client) InspectNode(id string) (*swarm.Node, error) {
resp, err := c.do("GET", "/nodes/"+id, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, &NoSuchNode{ID: id}
}
return nil, err
}
defer resp.Body.Close()
var node swarm.Node
if err := json.NewDecoder(resp.Body).Decode(&node); err != nil {
return nil, err
}
return &node, nil
}
// UpdateNodeOptions specify parameters to the NodeUpdate function.
//
// See http://goo.gl/VPBFgA for more details.
type UpdateNodeOptions struct {
swarm.NodeSpec
Version uint64
Context context.Context
}
// UpdateNode updates a node.
//
// See http://goo.gl/VPBFgA for more details.
func (c *Client) UpdateNode(id string, opts UpdateNodeOptions) error {
params := make(url.Values)
params.Set("version", strconv.FormatUint(opts.Version, 10))
path := "/nodes/" + id + "/update?" + params.Encode()
resp, err := c.do("POST", path, doOptions{
context: opts.Context,
forceJSON: true,
data: opts.NodeSpec,
})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return &NoSuchNode{ID: id}
}
return err
}
resp.Body.Close()
return nil
}
// RemoveNodeOptions specify parameters to the RemoveNode function.
//
// See http://goo.gl/0SNvYg for more details.
type RemoveNodeOptions struct {
ID string
Force bool
Context context.Context
}
// RemoveNode removes a node.
//
// See http://goo.gl/0SNvYg for more details.
func (c *Client) RemoveNode(opts RemoveNodeOptions) error {
params := make(url.Values)
params.Set("force", strconv.FormatBool(opts.Force))
path := "/nodes/" + opts.ID + "?" + params.Encode()
resp, err := c.do("DELETE", path, doOptions{context: opts.Context})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return &NoSuchNode{ID: opts.ID}
}
return err
}
resp.Body.Close()
return nil
}

View file

@ -1,171 +0,0 @@
// Copyright 2016 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"context"
"encoding/json"
"net/http"
"net/url"
"strconv"
"github.com/docker/docker/api/types/swarm"
)
// NoSuchSecret is the error returned when a given secret does not exist.
type NoSuchSecret struct {
ID string
Err error
}
func (err *NoSuchSecret) Error() string {
if err.Err != nil {
return err.Err.Error()
}
return "No such secret: " + err.ID
}
// CreateSecretOptions specify parameters to the CreateSecret function.
//
// See https://goo.gl/KrVjHz for more details.
type CreateSecretOptions struct {
Auth AuthConfiguration `qs:"-"`
swarm.SecretSpec
Context context.Context
}
// CreateSecret creates a new secret, returning the secret instance
// or an error in case of failure.
//
// See https://goo.gl/KrVjHz for more details.
func (c *Client) CreateSecret(opts CreateSecretOptions) (*swarm.Secret, error) {
headers, err := headersWithAuth(opts.Auth)
if err != nil {
return nil, err
}
path := "/secrets/create?" + queryString(opts)
resp, err := c.do("POST", path, doOptions{
headers: headers,
data: opts.SecretSpec,
forceJSON: true,
context: opts.Context,
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var secret swarm.Secret
if err := json.NewDecoder(resp.Body).Decode(&secret); err != nil {
return nil, err
}
return &secret, nil
}
// RemoveSecretOptions encapsulates options to remove a secret.
//
// See https://goo.gl/Tqrtya for more details.
type RemoveSecretOptions struct {
ID string `qs:"-"`
Context context.Context
}
// RemoveSecret removes a secret, returning an error in case of failure.
//
// See https://goo.gl/Tqrtya for more details.
func (c *Client) RemoveSecret(opts RemoveSecretOptions) error {
path := "/secrets/" + opts.ID
resp, err := c.do("DELETE", path, doOptions{context: opts.Context})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return &NoSuchSecret{ID: opts.ID}
}
return err
}
resp.Body.Close()
return nil
}
// UpdateSecretOptions specify parameters to the UpdateSecret function.
//
// Only label can be updated
// See https://docs.docker.com/engine/api/v1.33/#operation/SecretUpdate
// See https://goo.gl/wu3MmS for more details.
type UpdateSecretOptions struct {
Auth AuthConfiguration `qs:"-"`
swarm.SecretSpec
Context context.Context
Version uint64
}
// UpdateSecret updates the secret at ID with the options
//
// See https://goo.gl/wu3MmS for more details.
func (c *Client) UpdateSecret(id string, opts UpdateSecretOptions) error {
headers, err := headersWithAuth(opts.Auth)
if err != nil {
return err
}
params := make(url.Values)
params.Set("version", strconv.FormatUint(opts.Version, 10))
resp, err := c.do("POST", "/secrets/"+id+"/update?"+params.Encode(), doOptions{
headers: headers,
data: opts.SecretSpec,
forceJSON: true,
context: opts.Context,
})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return &NoSuchSecret{ID: id}
}
return err
}
defer resp.Body.Close()
return nil
}
// InspectSecret returns information about a secret by its ID.
//
// See https://goo.gl/dHmr75 for more details.
func (c *Client) InspectSecret(id string) (*swarm.Secret, error) {
path := "/secrets/" + id
resp, err := c.do("GET", path, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, &NoSuchSecret{ID: id}
}
return nil, err
}
defer resp.Body.Close()
var secret swarm.Secret
if err := json.NewDecoder(resp.Body).Decode(&secret); err != nil {
return nil, err
}
return &secret, nil
}
// ListSecretsOptions specify parameters to the ListSecrets function.
//
// See https://goo.gl/DwvNMd for more details.
type ListSecretsOptions struct {
Filters map[string][]string
Context context.Context
}
// ListSecrets returns a slice of secrets matching the given criteria.
//
// See https://goo.gl/DwvNMd for more details.
func (c *Client) ListSecrets(opts ListSecretsOptions) ([]swarm.Secret, error) {
path := "/secrets?" + queryString(opts)
resp, err := c.do("GET", path, doOptions{context: opts.Context})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var secrets []swarm.Secret
if err := json.NewDecoder(resp.Body).Decode(&secrets); err != nil {
return nil, err
}
return secrets, nil
}

View file

@ -1,213 +0,0 @@
// Copyright 2016 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"context"
"encoding/json"
"io"
"net/http"
"time"
"github.com/docker/docker/api/types/swarm"
)
// NoSuchService is the error returned when a given service does not exist.
type NoSuchService struct {
ID string
Err error
}
func (err *NoSuchService) Error() string {
if err.Err != nil {
return err.Err.Error()
}
return "No such service: " + err.ID
}
// CreateServiceOptions specify parameters to the CreateService function.
//
// See https://goo.gl/KrVjHz for more details.
type CreateServiceOptions struct {
Auth AuthConfiguration `qs:"-"`
swarm.ServiceSpec
Context context.Context
}
// CreateService creates a new service, returning the service instance
// or an error in case of failure.
//
// See https://goo.gl/KrVjHz for more details.
func (c *Client) CreateService(opts CreateServiceOptions) (*swarm.Service, error) {
headers, err := headersWithAuth(opts.Auth)
if err != nil {
return nil, err
}
path := "/services/create?" + queryString(opts)
resp, err := c.do("POST", path, doOptions{
headers: headers,
data: opts.ServiceSpec,
forceJSON: true,
context: opts.Context,
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var service swarm.Service
if err := json.NewDecoder(resp.Body).Decode(&service); err != nil {
return nil, err
}
return &service, nil
}
// RemoveServiceOptions encapsulates options to remove a service.
//
// See https://goo.gl/Tqrtya for more details.
type RemoveServiceOptions struct {
ID string `qs:"-"`
Context context.Context
}
// RemoveService removes a service, returning an error in case of failure.
//
// See https://goo.gl/Tqrtya for more details.
func (c *Client) RemoveService(opts RemoveServiceOptions) error {
path := "/services/" + opts.ID
resp, err := c.do("DELETE", path, doOptions{context: opts.Context})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return &NoSuchService{ID: opts.ID}
}
return err
}
resp.Body.Close()
return nil
}
// UpdateServiceOptions specify parameters to the UpdateService function.
//
// See https://goo.gl/wu3MmS for more details.
type UpdateServiceOptions struct {
Auth AuthConfiguration `qs:"-"`
swarm.ServiceSpec `qs:"-"`
Context context.Context
Version uint64
Rollback string
}
// UpdateService updates the service at ID with the options
//
// See https://goo.gl/wu3MmS for more details.
func (c *Client) UpdateService(id string, opts UpdateServiceOptions) error {
headers, err := headersWithAuth(opts.Auth)
if err != nil {
return err
}
resp, err := c.do("POST", "/services/"+id+"/update?"+queryString(opts), doOptions{
headers: headers,
data: opts.ServiceSpec,
forceJSON: true,
context: opts.Context,
})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return &NoSuchService{ID: id}
}
return err
}
defer resp.Body.Close()
return nil
}
// InspectService returns information about a service by its ID.
//
// See https://goo.gl/dHmr75 for more details.
func (c *Client) InspectService(id string) (*swarm.Service, error) {
path := "/services/" + id
resp, err := c.do("GET", path, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, &NoSuchService{ID: id}
}
return nil, err
}
defer resp.Body.Close()
var service swarm.Service
if err := json.NewDecoder(resp.Body).Decode(&service); err != nil {
return nil, err
}
return &service, nil
}
// ListServicesOptions specify parameters to the ListServices function.
//
// See https://goo.gl/DwvNMd for more details.
type ListServicesOptions struct {
Filters map[string][]string
Context context.Context
}
// ListServices returns a slice of services matching the given criteria.
//
// See https://goo.gl/DwvNMd for more details.
func (c *Client) ListServices(opts ListServicesOptions) ([]swarm.Service, error) {
path := "/services?" + queryString(opts)
resp, err := c.do("GET", path, doOptions{context: opts.Context})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var services []swarm.Service
if err := json.NewDecoder(resp.Body).Decode(&services); err != nil {
return nil, err
}
return services, nil
}
// LogsServiceOptions represents the set of options used when getting logs from a
// service.
type LogsServiceOptions struct {
Context context.Context
Service string `qs:"-"`
OutputStream io.Writer `qs:"-"`
ErrorStream io.Writer `qs:"-"`
InactivityTimeout time.Duration `qs:"-"`
Tail string
// Use raw terminal? Usually true when the container contains a TTY.
RawTerminal bool `qs:"-"`
Since int64
Follow bool
Stdout bool
Stderr bool
Timestamps bool
Details bool
}
// GetServiceLogs gets stdout and stderr logs from the specified service.
//
// When LogsServiceOptions.RawTerminal is set to false, go-dockerclient will multiplex
// the streams and send the containers stdout to LogsServiceOptions.OutputStream, and
// stderr to LogsServiceOptions.ErrorStream.
//
// When LogsServiceOptions.RawTerminal is true, callers will get the raw stream on
// LogsServiceOptions.OutputStream.
func (c *Client) GetServiceLogs(opts LogsServiceOptions) error {
if opts.Service == "" {
return &NoSuchService{ID: opts.Service}
}
if opts.Tail == "" {
opts.Tail = "all"
}
path := "/services/" + opts.Service + "/logs?" + queryString(opts)
return c.stream("GET", path, streamOptions{
setRawTerminal: opts.RawTerminal,
stdout: opts.OutputStream,
stderr: opts.ErrorStream,
inactivityTimeout: opts.InactivityTimeout,
context: opts.Context,
})
}

View file

@ -1,70 +0,0 @@
// Copyright 2016 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"context"
"encoding/json"
"net/http"
"github.com/docker/docker/api/types/swarm"
)
// NoSuchTask is the error returned when a given task does not exist.
type NoSuchTask struct {
ID string
Err error
}
func (err *NoSuchTask) Error() string {
if err.Err != nil {
return err.Err.Error()
}
return "No such task: " + err.ID
}
// ListTasksOptions specify parameters to the ListTasks function.
//
// See http://goo.gl/rByLzw for more details.
type ListTasksOptions struct {
Filters map[string][]string
Context context.Context
}
// ListTasks returns a slice of tasks matching the given criteria.
//
// See http://goo.gl/rByLzw for more details.
func (c *Client) ListTasks(opts ListTasksOptions) ([]swarm.Task, error) {
path := "/tasks?" + queryString(opts)
resp, err := c.do("GET", path, doOptions{context: opts.Context})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var tasks []swarm.Task
if err := json.NewDecoder(resp.Body).Decode(&tasks); err != nil {
return nil, err
}
return tasks, nil
}
// InspectTask returns information about a task by its ID.
//
// See http://goo.gl/kyziuq for more details.
func (c *Client) InspectTask(id string) (*swarm.Task, error) {
resp, err := c.do("GET", "/tasks/"+id, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, &NoSuchTask{ID: id}
}
return nil, err
}
defer resp.Body.Close()
var task swarm.Task
if err := json.NewDecoder(resp.Body).Decode(&task); err != nil {
return nil, err
}
return &task, nil
}

View file

@ -1,122 +0,0 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/fileutils"
)
func createTarStream(srcPath, dockerfilePath string) (io.ReadCloser, error) {
srcPath, err := filepath.Abs(srcPath)
if err != nil {
return nil, err
}
excludes, err := parseDockerignore(srcPath)
if err != nil {
return nil, err
}
includes := []string{"."}
// If .dockerignore mentions .dockerignore or the Dockerfile
// then make sure we send both files over to the daemon
// because Dockerfile is, obviously, needed no matter what, and
// .dockerignore is needed to know if either one needs to be
// removed. The deamon will remove them for us, if needed, after it
// parses the Dockerfile.
//
// https://github.com/docker/docker/issues/8330
//
forceIncludeFiles := []string{".dockerignore", dockerfilePath}
for _, includeFile := range forceIncludeFiles {
if includeFile == "" {
continue
}
keepThem, err := fileutils.Matches(includeFile, excludes)
if err != nil {
return nil, fmt.Errorf("cannot match .dockerfile: '%s', error: %s", includeFile, err)
}
if keepThem {
includes = append(includes, includeFile)
}
}
if err := validateContextDirectory(srcPath, excludes); err != nil {
return nil, err
}
tarOpts := &archive.TarOptions{
ExcludePatterns: excludes,
IncludeFiles: includes,
Compression: archive.Uncompressed,
NoLchown: true,
}
return archive.TarWithOptions(srcPath, tarOpts)
}
// validateContextDirectory checks if all the contents of the directory
// can be read and returns an error if some files can't be read.
// Symlinks which point to non-existing files don't trigger an error
func validateContextDirectory(srcPath string, excludes []string) error {
return filepath.Walk(filepath.Join(srcPath, "."), func(filePath string, f os.FileInfo, err error) error {
// skip this directory/file if it's not in the path, it won't get added to the context
if relFilePath, relErr := filepath.Rel(srcPath, filePath); relErr != nil {
return relErr
} else if skip, matchErr := fileutils.Matches(relFilePath, excludes); matchErr != nil {
return matchErr
} else if skip {
if f.IsDir() {
return filepath.SkipDir
}
return nil
}
if err != nil {
if os.IsPermission(err) {
return fmt.Errorf("can't stat '%s'", filePath)
}
if os.IsNotExist(err) {
return nil
}
return err
}
// skip checking if symlinks point to non-existing files, such symlinks can be useful
// also skip named pipes, because they hanging on open
if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 {
return nil
}
if !f.IsDir() {
currentFile, err := os.Open(filePath)
if err != nil && os.IsPermission(err) {
return fmt.Errorf("no permission to read from '%s'", filePath)
}
currentFile.Close()
}
return nil
})
}
func parseDockerignore(root string) ([]string, error) {
var excludes []string
ignore, err := ioutil.ReadFile(path.Join(root, ".dockerignore"))
if err != nil && !os.IsNotExist(err) {
return excludes, fmt.Errorf("error reading .dockerignore: '%s'", err)
}
excludes = strings.Split(string(ignore), "\n")
return excludes, nil
}

View file

@ -1,118 +0,0 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// The content is borrowed from Docker's own source code to provide a simple
// tls based dialer
package docker
import (
"crypto/tls"
"errors"
"net"
"strings"
"time"
)
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 cwc, ok := c.rawConn.(interface {
CloseWrite() error
}); ok {
return cwc.CloseWrite()
}
return nil
}
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("")
})
}
rawConn, err := dialer.Dial(network, addr)
if err != nil {
return nil, err
}
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.
config = copyTLSConfig(config)
config.ServerName = hostname
}
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
}
// this exists to silent an error message in go vet
func copyTLSConfig(cfg *tls.Config) *tls.Config {
return &tls.Config{
Certificates: cfg.Certificates,
CipherSuites: cfg.CipherSuites,
ClientAuth: cfg.ClientAuth,
ClientCAs: cfg.ClientCAs,
ClientSessionCache: cfg.ClientSessionCache,
CurvePreferences: cfg.CurvePreferences,
InsecureSkipVerify: cfg.InsecureSkipVerify,
MaxVersion: cfg.MaxVersion,
MinVersion: cfg.MinVersion,
NameToCertificate: cfg.NameToCertificate,
NextProtos: cfg.NextProtos,
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
Rand: cfg.Rand,
RootCAs: cfg.RootCAs,
ServerName: cfg.ServerName,
SessionTicketKey: cfg.SessionTicketKey,
SessionTicketsDisabled: cfg.SessionTicketsDisabled,
}
}

View file

@ -1,190 +0,0 @@
// Copyright 2015 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"context"
"encoding/json"
"errors"
"net/http"
)
var (
// ErrNoSuchVolume is the error returned when the volume does not exist.
ErrNoSuchVolume = errors.New("no such volume")
// ErrVolumeInUse is the error returned when the volume requested to be removed is still in use.
ErrVolumeInUse = errors.New("volume in use and cannot be removed")
)
// Volume represents a volume.
//
// See https://goo.gl/3wgTsd for more details.
type Volume struct {
Name string `json:"Name" yaml:"Name" toml:"Name"`
Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty" toml:"Driver,omitempty"`
Mountpoint string `json:"Mountpoint,omitempty" yaml:"Mountpoint,omitempty" toml:"Mountpoint,omitempty"`
Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"`
Options map[string]string `json:"Options,omitempty" yaml:"Options,omitempty" toml:"Options,omitempty"`
}
// ListVolumesOptions specify parameters to the ListVolumes function.
//
// See https://goo.gl/3wgTsd for more details.
type ListVolumesOptions struct {
Filters map[string][]string
Context context.Context
}
// ListVolumes returns a list of available volumes in the server.
//
// See https://goo.gl/3wgTsd for more details.
func (c *Client) ListVolumes(opts ListVolumesOptions) ([]Volume, error) {
resp, err := c.do("GET", "/volumes?"+queryString(opts), doOptions{
context: opts.Context,
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
m := make(map[string]interface{})
if err = json.NewDecoder(resp.Body).Decode(&m); err != nil {
return nil, err
}
var volumes []Volume
volumesJSON, ok := m["Volumes"]
if !ok {
return volumes, nil
}
data, err := json.Marshal(volumesJSON)
if err != nil {
return nil, err
}
if err := json.Unmarshal(data, &volumes); err != nil {
return nil, err
}
return volumes, nil
}
// CreateVolumeOptions specify parameters to the CreateVolume function.
//
// See https://goo.gl/qEhmEC for more details.
type CreateVolumeOptions struct {
Name string
Driver string
DriverOpts map[string]string
Context context.Context `json:"-"`
Labels map[string]string
}
// CreateVolume creates a volume on the server.
//
// See https://goo.gl/qEhmEC for more details.
func (c *Client) CreateVolume(opts CreateVolumeOptions) (*Volume, error) {
resp, err := c.do("POST", "/volumes/create", doOptions{
data: opts,
context: opts.Context,
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var volume Volume
if err := json.NewDecoder(resp.Body).Decode(&volume); err != nil {
return nil, err
}
return &volume, nil
}
// InspectVolume returns a volume by its name.
//
// See https://goo.gl/GMjsMc for more details.
func (c *Client) InspectVolume(name string) (*Volume, error) {
resp, err := c.do("GET", "/volumes/"+name, doOptions{})
if err != nil {
if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound {
return nil, ErrNoSuchVolume
}
return nil, err
}
defer resp.Body.Close()
var volume Volume
if err := json.NewDecoder(resp.Body).Decode(&volume); err != nil {
return nil, err
}
return &volume, nil
}
// RemoveVolume removes a volume by its name.
//
// Deprecated: Use RemoveVolumeWithOptions instead.
func (c *Client) RemoveVolume(name string) error {
return c.RemoveVolumeWithOptions(RemoveVolumeOptions{Name: name})
}
// RemoveVolumeOptions specify parameters to the RemoveVolumeWithOptions
// function.
//
// See https://goo.gl/nvd6qj for more details.
type RemoveVolumeOptions struct {
Context context.Context
Name string `qs:"-"`
Force bool
}
// RemoveVolumeWithOptions removes a volume by its name and takes extra
// parameters.
//
// See https://goo.gl/nvd6qj for more details.
func (c *Client) RemoveVolumeWithOptions(opts RemoveVolumeOptions) error {
path := "/volumes/" + opts.Name
resp, err := c.do("DELETE", path+"?"+queryString(opts), doOptions{context: opts.Context})
if err != nil {
if e, ok := err.(*Error); ok {
if e.Status == http.StatusNotFound {
return ErrNoSuchVolume
}
if e.Status == http.StatusConflict {
return ErrVolumeInUse
}
}
return err
}
defer resp.Body.Close()
return nil
}
// PruneVolumesOptions specify parameters to the PruneVolumes function.
//
// See https://goo.gl/f9XDem for more details.
type PruneVolumesOptions struct {
Filters map[string][]string
Context context.Context
}
// PruneVolumesResults specify results from the PruneVolumes function.
//
// See https://goo.gl/f9XDem for more details.
type PruneVolumesResults struct {
VolumesDeleted []string
SpaceReclaimed int64
}
// PruneVolumes deletes volumes which are unused.
//
// See https://goo.gl/f9XDem for more details.
func (c *Client) PruneVolumes(opts PruneVolumesOptions) (*PruneVolumesResults, error) {
path := "/volumes/prune?" + queryString(opts)
resp, err := c.do("POST", path, doOptions{context: opts.Context})
if err != nil {
return nil, err
}
defer resp.Body.Close()
var results PruneVolumesResults
if err := json.NewDecoder(resp.Body).Decode(&results); err != nil {
return nil, err
}
return &results, nil
}

32
vendor/vendor.json vendored
View file

@ -351,6 +351,7 @@
"path": "github.com/docker/docker/api/types/swarm/runtime",
"revision": "65bd038fc5e47ed37d2702cbdd6ce484d320380b",
"revisionTime": "2018-05-30T01:28:07Z"
<<<<<<< HEAD
},
{
"checksumSHA1": "V8OZpLS8i1HfO4lAzUzEw2BgnfQ=",
@ -359,10 +360,21 @@
"revisionTime": "2018-05-30T01:28:07Z"
},
{
=======
},
{
"checksumSHA1": "V8OZpLS8i1HfO4lAzUzEw2BgnfQ=",
"path": "github.com/docker/docker/api/types/time",
"revision": "65bd038fc5e47ed37d2702cbdd6ce484d320380b",
"revisionTime": "2018-05-30T01:28:07Z"
},
{
>>>>>>> d96333876b7267be943761aa5586c6f65aed1533
"checksumSHA1": "MZsgRjJJ0D/gAsXfKiEys+op6dE=",
"path": "github.com/docker/docker/api/types/versions",
"revision": "65bd038fc5e47ed37d2702cbdd6ce484d320380b",
"revisionTime": "2018-05-30T01:28:07Z"
<<<<<<< HEAD
},
{
"checksumSHA1": "BjgxujsDKAzTeQR97H7ojww7BjM=",
@ -379,12 +391,29 @@
{
"checksumSHA1": "u6EOrZRfhdjr4up14b2JJ7MMMaY=",
"path": "github.com/docker/docker/opts",
=======
},
{
"checksumSHA1": "BjgxujsDKAzTeQR97H7ojww7BjM=",
"path": "github.com/docker/docker/api/types/volume",
"revision": "65bd038fc5e47ed37d2702cbdd6ce484d320380b",
"revisionTime": "2018-05-30T01:28:07Z"
},
{
"checksumSHA1": "6CnC2OQhh5uzaYoftbN+VKDMyyE=",
"path": "github.com/docker/docker/client",
>>>>>>> d96333876b7267be943761aa5586c6f65aed1533
"revision": "65bd038fc5e47ed37d2702cbdd6ce484d320380b",
"revisionTime": "2018-05-30T01:28:07Z"
},
{
<<<<<<< HEAD
"checksumSHA1": "XcUgUwnG9cMKIvwl8UGuUsXc0CA=",
"path": "github.com/docker/docker/pkg/archive",
=======
"checksumSHA1": "u6EOrZRfhdjr4up14b2JJ7MMMaY=",
"path": "github.com/docker/docker/opts",
>>>>>>> d96333876b7267be943761aa5586c6f65aed1533
"revision": "65bd038fc5e47ed37d2702cbdd6ce484d320380b",
"revisionTime": "2018-05-30T01:28:07Z"
},
@ -489,12 +518,15 @@
"path": "github.com/docker/libnetwork/ipamutils",
"revision": "a355c4bd0900178ebd6f435ee55d0ab4f712f506",
"revisionTime": "2018-05-29T16:43:54Z"
<<<<<<< HEAD
},
{
"checksumSHA1": "LxmlsftDto7ZuwfNmJRdKPah1t8=",
"path": "github.com/fsouza/go-dockerclient",
"revision": "bf3bc17bb026cbe5e9203374f9a1ce37e521a364",
"revisionTime": "2018-03-03T17:22:32Z"
=======
>>>>>>> d96333876b7267be943761aa5586c6f65aed1533
},
{
"checksumSHA1": "1K+xrZ1PBez190iGt5OnMtGdih4=",