diff --git a/README.md b/README.md index 72018b27..84adab57 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ If you wish to work on the provider, you'll first need [Go](http://www.golang.or To compile the provider, run `make build`. This will build the provider and put the provider binary in the `$GOPATH/bin` directory. ```sh -$ make bin +$ make build ... $ $GOPATH/bin/terraform-provider-$PROVIDER_NAME ... @@ -60,4 +60,25 @@ In order to run the full suite of Acceptance tests, run `make testacc`. ```sh $ make testacc +# e.g. run a single acceptance test: e.g. 'TestAccDockerRegistryImage_private' in 'data_source_docker_registry_image_test.go' +go test -v -timeout 30s github.com/terraform-providers/terraform-provider-docker/docker -run ^TestAccDockerRegistryImage_private$ ``` + +In order to extend the provider and test it with `terraform`, build the provider as mentioned above with +```sh +$ make build +``` + +Remove an explicit version of the provider you develop, because `terraform` will fetch +the locally built one in `$GOPATH/bin` +```hcl +provider "$PROVIDER_NAME" { + # version = "~> 0.1.2" + ... +} +``` + + +Don't forget to run `terraform init` each time you rebuild the provider. Check [here](https://www.youtube.com/watch?v=TMmovxyo5sY&t=30m14s) for a more detailed explanation. + +You can check the latest released version of a provider at https://releases.hashicorp.com/terraform-provider-$PROVIDER_NAME/. \ No newline at end of file diff --git a/docker/config.go b/docker/config.go index ad05d540..b73b5c2b 100644 --- a/docker/config.go +++ b/docker/config.go @@ -3,13 +3,14 @@ package docker import ( "fmt" "path/filepath" + "strings" dc "github.com/fsouza/go-dockerclient" ) -// Config is the structure that stores the configuration to talk to a +// DockerConfig is the structure that stores the configuration to talk to a // Docker API compatible host. -type Config struct { +type DockerConfig struct { Host string Ca string Cert string @@ -18,7 +19,7 @@ type Config struct { } // NewClient() returns a new Docker client. -func (c *Config) NewClient() (*dc.Client, error) { +func (c *DockerConfig) NewClient() (*dc.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") @@ -43,7 +44,22 @@ func (c *Config) NewClient() (*dc.Client, error) { return dc.NewClient(c.Host) } -// Data ia structure for holding data that we fetch from Docker. +// Data structure for holding data that we fetch from Docker. type Data struct { DockerImages map[string]*dc.APIImages } + +// ProviderConfig for the custom registry provider +type ProviderConfig struct { + DockerClient *dc.Client + AuthConfigs *dc.AuthConfigurations +} + +// The registry address can be referenced in various places (registry auth, docker config file, image name) +// with or without the http(s):// prefix; this function is used to standardize the inputs +func normalizeRegistryAddress(address string) string { + if !strings.HasPrefix(address, "https://") && !strings.HasPrefix(address, "http://") { + return "https://" + address + } + return address +} diff --git a/docker/data_source_docker_registry_image.go b/docker/data_source_docker_registry_image.go index 9898c8ac..754d9dac 100644 --- a/docker/data_source_docker_registry_image.go +++ b/docker/data_source_docker_registry_image.go @@ -31,6 +31,7 @@ func dataSourceDockerRegistryImage() *schema.Resource { func dataSourceDockerRegistryImageRead(d *schema.ResourceData, meta interface{}) error { pullOpts := parseImageOptions(d.Get("name").(string)) + authConfig := meta.(*ProviderConfig).AuthConfigs // Use the official Docker Hub if a registry isn't specified if pullOpts.Registry == "" { @@ -49,7 +50,15 @@ func dataSourceDockerRegistryImageRead(d *schema.ResourceData, meta interface{}) pullOpts.Tag = "latest" } - digest, err := getImageDigest(pullOpts.Registry, pullOpts.Repository, pullOpts.Tag, "", "") + username := "" + password := "" + + if auth, ok := authConfig.Configs[normalizeRegistryAddress(pullOpts.Registry)]; ok { + username = auth.Username + password = auth.Password + } + + digest, err := getImageDigest(pullOpts.Registry, pullOpts.Repository, pullOpts.Tag, username, password) if err != nil { return fmt.Errorf("Got error when attempting to fetch image version from registry: %s", err) @@ -74,6 +83,9 @@ func getImageDigest(registry, image, tag, username, password string) (string, er req.SetBasicAuth(username, password) } + // Set this header so that we get the v2 manifest back from the registry. + req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json") + resp, err := client.Do(req) if err != nil { diff --git a/docker/data_source_docker_registry_image_test.go b/docker/data_source_docker_registry_image_test.go index aa34b004..7695a816 100644 --- a/docker/data_source_docker_registry_image_test.go +++ b/docker/data_source_docker_registry_image_test.go @@ -1,6 +1,8 @@ package docker import ( + "fmt" + "os" "regexp" "testing" @@ -39,6 +41,23 @@ func TestAccDockerRegistryImage_private(t *testing.T) { }) } +func TestAccDockerRegistryImage_auth(t *testing.T) { + registry := os.Getenv("DOCKER_REGISTRY_ADDRESS") + image := os.Getenv("DOCKER_PRIVATE_IMAGE") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testAccDockerImageDataSourceAuthConfig, registry, image), + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr("data.docker_registry_image.foobar", "sha256_digest", registryDigestRegexp), + ), + }, + }, + }) +} + const testAccDockerImageDataSourceConfig = ` data "docker_registry_image" "foo" { name = "alpine:latest" @@ -50,3 +69,16 @@ data "docker_registry_image" "bar" { name = "gcr.io:443/google_containers/pause:0.8.0" } ` + +const testAccDockerImageDataSourceAuthConfig = ` +provider "docker" { + alias = "private" + registry_auth { + address = "%s" + } +} +data "docker_registry_image" "foobar" { + provider = "docker.private" + name = "%s" +} +` diff --git a/docker/provider.go b/docker/provider.go index 1da7ffbe..fab0fdbe 100644 --- a/docker/provider.go +++ b/docker/provider.go @@ -2,7 +2,11 @@ package docker import ( "fmt" + "os" + "os/user" + "strings" + dc "github.com/fsouza/go-dockerclient" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) @@ -42,6 +46,44 @@ func Provider() terraform.ResourceProvider { DefaultFunc: schema.EnvDefaultFunc("DOCKER_CERT_PATH", ""), Description: "Path to directory with Docker TLS config", }, + + "registry_auth": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Address of the registry", + }, + + "username": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"registry_auth.config_file"}, + DefaultFunc: schema.EnvDefaultFunc("DOCKER_REGISTRY_USER", ""), + Description: "Username for the registry", + }, + + "password": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"registry_auth.config_file"}, + DefaultFunc: schema.EnvDefaultFunc("DOCKER_REGISTRY_PASS", ""), + Description: "Password for the registry", + }, + + "config_file": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"registry_auth.username", "registry_auth.password"}, + DefaultFunc: schema.EnvDefaultFunc("DOCKER_CONFIG", "~/.docker/config.json"), + Description: "Path to docker json file for registry auth", + }, + }, + }, + }, }, ResourcesMap: map[string]*schema.Resource{ @@ -60,7 +102,7 @@ func Provider() terraform.ResourceProvider { } func providerConfigure(d *schema.ResourceData) (interface{}, error) { - config := Config{ + config := DockerConfig{ Host: d.Get("host").(string), Ca: d.Get("ca_material").(string), Cert: d.Get("cert_material").(string), @@ -78,5 +120,77 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { return nil, fmt.Errorf("Error pinging Docker server: %s", err) } - return client, nil + authConfigs := &dc.AuthConfigurations{} + + if v, ok := d.GetOk("registry_auth"); ok { + authConfigs, err = providerSetToRegistryAuth(v.(*schema.Set)) + + if err != nil { + return nil, fmt.Errorf("Error loading registry auth config: %s", err) + } + } + + providerConfig := ProviderConfig{ + DockerClient: client, + AuthConfigs: authConfigs, + } + + return &providerConfig, nil +} + +// 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), + } + + for _, authInt := range authSet.List() { + auth := authInt.(map[string]interface{}) + authConfig := dc.AuthConfiguration{} + authConfig.ServerAddress = normalizeRegistryAddress(auth["address"].(string)) + + // For each registry_auth block, generate an AuthConfiguration using either + // username/password or the given config file + if username, ok := auth["username"]; ok && username.(string) != "" { + authConfig.Username = auth["username"].(string) + authConfig.Password = auth["password"].(string) + } else if configFile, ok := auth["config_file"]; ok && configFile.(string) != "" { + filePath := configFile.(string) + if strings.HasPrefix(filePath, "~/") { + usr, err := user.Current() + if err != nil { + return nil, err + } + filePath = strings.Replace(filePath, "~", usr.HomeDir, 1) + } + + r, err := os.Open(filePath) + if err != nil { + return nil, fmt.Errorf("Error opening docker registry config file: %v", err) + } + + auths, err := dc.NewAuthConfigurations(r) + if err != nil { + return nil, fmt.Errorf("Error parsing docker registry config json: %v", err) + } + + foundRegistry := false + for registry, authFileConfig := range auths.Configs { + if authConfig.ServerAddress == normalizeRegistryAddress(registry) { + authConfig.Username = authFileConfig.Username + authConfig.Password = authFileConfig.Password + foundRegistry = true + } + } + + if !foundRegistry { + return nil, fmt.Errorf("Couldn't find registry config for '%s' in file: %s", + authConfig.ServerAddress, filePath) + } + } + + authConfigs.Configs[authConfig.ServerAddress] = authConfig + } + + return &authConfigs, nil } diff --git a/docker/provider_test.go b/docker/provider_test.go index d0910488..7d6396be 100644 --- a/docker/provider_test.go +++ b/docker/provider_test.go @@ -1,6 +1,7 @@ package docker import ( + "os" "os/exec" "testing" @@ -33,4 +34,17 @@ func testAccPreCheck(t *testing.T) { if err := cmd.Run(); err != nil { t.Fatalf("Docker must be available: %s", err) } + + if v := os.Getenv("DOCKER_REGISTRY_ADDRESS"); v == "" { + t.Fatalf("DOCKER_REGISTRY_ADDRESS must be set for acceptance tests") + } + if v := os.Getenv("DOCKER_REGISTRY_USER"); v == "" { + t.Fatalf("DOCKER_REGISTRY_USER must be set for acceptance tests") + } + if v := os.Getenv("DOCKER_REGISTRY_PASS"); v == "" { + t.Fatalf("DOCKER_REGISTRY_PASS must be set for acceptance tests") + } + if v := os.Getenv("DOCKER_PRIVATE_IMAGE"); v == "" { + t.Fatalf("DOCKER_PRIVATE_IMAGE must be set for acceptance tests") + } } diff --git a/docker/resource_docker_container_funcs.go b/docker/resource_docker_container_funcs.go index 4a494c5b..4c9aaf49 100644 --- a/docker/resource_docker_container_funcs.go +++ b/docker/resource_docker_container_funcs.go @@ -18,7 +18,7 @@ var ( func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) error { var err error - client := meta.(*dc.Client) + client := meta.(*ProviderConfig).DockerClient var data Data if err := fetchLocalImages(&data, client); err != nil { @@ -247,7 +247,7 @@ func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) err } func resourceDockerContainerRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*dc.Client) + client := meta.(*ProviderConfig).DockerClient apiContainer, err := fetchDockerContainer(d.Id(), client) if err != nil { @@ -314,7 +314,7 @@ func resourceDockerContainerUpdate(d *schema.ResourceData, meta interface{}) err } func resourceDockerContainerDelete(d *schema.ResourceData, meta interface{}) error { - client := meta.(*dc.Client) + client := meta.(*ProviderConfig).DockerClient // Stop the container before removing if destroy_grace_seconds is defined if d.Get("destroy_grace_seconds").(int) > 0 { diff --git a/docker/resource_docker_container_test.go b/docker/resource_docker_container_test.go index dcc6affa..f7746d97 100644 --- a/docker/resource_docker_container_test.go +++ b/docker/resource_docker_container_test.go @@ -229,7 +229,7 @@ func TestAccDockerContainer_upload(t *testing.T) { var c dc.Container testCheck := func(*terraform.State) error { - client := testAccProvider.Meta().(*dc.Client) + client := testAccProvider.Meta().(*ProviderConfig).DockerClient buf := new(bytes.Buffer) opts := dc.DownloadFromContainerOptions{ @@ -285,7 +285,7 @@ func testAccContainerRunning(n string, container *dc.Container) resource.TestChe return fmt.Errorf("No ID is set") } - client := testAccProvider.Meta().(*dc.Client) + client := testAccProvider.Meta().(*ProviderConfig).DockerClient containers, err := client.ListContainers(dc.ListContainersOptions{}) if err != nil { return err diff --git a/docker/resource_docker_image_funcs.go b/docker/resource_docker_image_funcs.go index 9c27b425..5bffbf13 100644 --- a/docker/resource_docker_image_funcs.go +++ b/docker/resource_docker_image_funcs.go @@ -9,8 +9,8 @@ import ( ) func resourceDockerImageCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*dc.Client) - apiImage, err := findImage(d, client) + client := meta.(*ProviderConfig).DockerClient + apiImage, err := findImage(d, client, meta.(*ProviderConfig).AuthConfigs) if err != nil { return fmt.Errorf("Unable to read Docker image into resource: %s", err) } @@ -22,7 +22,7 @@ func resourceDockerImageCreate(d *schema.ResourceData, meta interface{}) error { } func resourceDockerImageRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*dc.Client) + client := meta.(*ProviderConfig).DockerClient var data Data if err := fetchLocalImages(&data, client); err != nil { return fmt.Errorf("Error reading docker image list: %s", err) @@ -41,8 +41,8 @@ func resourceDockerImageRead(d *schema.ResourceData, meta interface{}) error { func resourceDockerImageUpdate(d *schema.ResourceData, meta interface{}) error { // We need to re-read in case switching parameters affects // the value of "latest" or others - client := meta.(*dc.Client) - apiImage, err := findImage(d, client) + client := meta.(*ProviderConfig).DockerClient + apiImage, err := findImage(d, client, meta.(*ProviderConfig).AuthConfigs) if err != nil { return fmt.Errorf("Unable to read Docker image into resource: %s", err) } @@ -53,7 +53,7 @@ func resourceDockerImageUpdate(d *schema.ResourceData, meta interface{}) error { } func resourceDockerImageDelete(d *schema.ResourceData, meta interface{}) error { - client := meta.(*dc.Client) + client := meta.(*ProviderConfig).DockerClient err := removeImage(d, client) if err != nil { return fmt.Errorf("Unable to remove Docker image: %s", err) @@ -125,12 +125,24 @@ func fetchLocalImages(data *Data, client *dc.Client) error { return nil } -func pullImage(data *Data, client *dc.Client, image string) error { +func pullImage(data *Data, client *dc.Client, authConfig *dc.AuthConfigurations, image string) error { // TODO: Test local registry handling. It should be working // based on the code that was ported over pullOpts := parseImageOptions(image) + + // If a registry was specified in the image name, try to find auth for it auth := dc.AuthConfiguration{} + if pullOpts.Registry != "" { + if authConfig, ok := authConfig.Configs[normalizeRegistryAddress(pullOpts.Registry)]; ok { + auth = authConfig + } + } else { + // Try to find an auth config for the public docker hub if a registry wasn't given + if authConfig, ok := authConfig.Configs["https://registry.hub.docker.com"]; ok { + auth = authConfig + } + } if err := client.PullImage(pullOpts, auth); err != nil { return fmt.Errorf("Error pulling image %s: %s\n", image, err) @@ -152,7 +164,7 @@ func parseImageOptions(image string) dc.PullImageOptions { pullOpts.Tag = splitImageName[2] pullOpts.Repository = pullOpts.Registry + "/" + strings.Join(splitPortRepo[1:], "/") - // It's either registry:port/username/repo, registry:port/repo, + // It's either registry:port/username/repo, registry:port/repo // or repo:tag with default registry case 2: splitPortRepo := strings.Split(splitImageName[1], "/") @@ -169,15 +181,24 @@ func parseImageOptions(image string) dc.PullImageOptions { pullOpts.Tag = "latest" } - // Plain username/repo or repo + // Registry/username/repo or plain username/repo or repo default: - pullOpts.Repository = image + splitRegistryRepo := strings.Split(image, "/") + switch len(splitRegistryRepo) { + // registry/username/repo + case 3: + pullOpts.Registry = splitRegistryRepo[0] + pullOpts.Repository = pullOpts.Registry + "/" + strings.Join(splitRegistryRepo[1:], "/") + // plain username/repo or repo + default: + pullOpts.Repository = image + } } return pullOpts } -func findImage(d *schema.ResourceData, client *dc.Client) (*dc.APIImages, error) { +func findImage(d *schema.ResourceData, client *dc.Client, authConfig *dc.AuthConfigurations) (*dc.APIImages, error) { var data Data if err := fetchLocalImages(&data, client); err != nil { return nil, err @@ -188,7 +209,7 @@ func findImage(d *schema.ResourceData, client *dc.Client) (*dc.APIImages, error) return nil, fmt.Errorf("Empty image name is not allowed") } - if err := pullImage(&data, client, imageName); err != nil { + if err := pullImage(&data, client, authConfig, imageName); err != nil { return nil, fmt.Errorf("Unable to pull image %s: %s", imageName, err) } diff --git a/docker/resource_docker_image_test.go b/docker/resource_docker_image_test.go index 4d75a617..faf28121 100644 --- a/docker/resource_docker_image_test.go +++ b/docker/resource_docker_image_test.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "os" "regexp" "testing" @@ -54,7 +55,7 @@ func TestAccDockerImage_destroy(t *testing.T) { continue } - client := testAccProvider.Meta().(*dc.Client) + client := testAccProvider.Meta().(*ProviderConfig).DockerClient _, err := client.InspectImage(rs.Primary.Attributes["latest"]) if err != nil { return err @@ -105,13 +106,32 @@ func TestAccDockerImage_data_pull_trigger(t *testing.T) { }) } +func TestAccDockerImage_data_private(t *testing.T) { + registry := os.Getenv("DOCKER_REGISTRY_ADDRESS") + image := os.Getenv("DOCKER_PRIVATE_IMAGE") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + PreventPostDestroyRefresh: true, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testAccDockerImageFromDataPrivateConfig, registry, image), + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr("docker_image.foo_private", "latest", contentDigestRegexp), + ), + }, + }, + }) +} + func testAccDockerImageDestroy(s *terraform.State) error { for _, rs := range s.RootModule().Resources { if rs.Type != "docker_image" { continue } - client := testAccProvider.Meta().(*dc.Client) + client := testAccProvider.Meta().(*ProviderConfig).DockerClient _, err := client.InspectImage(rs.Primary.Attributes["latest"]) if err == nil { return fmt.Errorf("Image still exists") @@ -160,3 +180,21 @@ resource "docker_image" "foobarbazoo" { pull_trigger = "${data.docker_registry_image.foobarbazoo.sha256_digest}" } ` + +const testAccDockerImageFromDataPrivateConfig = ` +provider "docker" { + alias = "private" + registry_auth { + address = "%s" + } +} +data "docker_registry_image" "foo_private" { + provider = "docker.private" + name = "%s" +} +resource "docker_image" "foo_private" { + provider = "docker.private" + name = "${data.docker_registry_image.foo_private.name}" + pull_triggers = ["${data.docker_registry_image.foo_private.sha256_digest}"] +} +` diff --git a/docker/resource_docker_network_funcs.go b/docker/resource_docker_network_funcs.go index f5ff172b..68d80232 100644 --- a/docker/resource_docker_network_funcs.go +++ b/docker/resource_docker_network_funcs.go @@ -8,7 +8,7 @@ import ( ) func resourceDockerNetworkCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*dc.Client) + client := meta.(*ProviderConfig).DockerClient createOpts := dc.CreateNetworkOptions{ Name: d.Get("name").(string), @@ -63,7 +63,7 @@ func resourceDockerNetworkCreate(d *schema.ResourceData, meta interface{}) error } func resourceDockerNetworkRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*dc.Client) + client := meta.(*ProviderConfig).DockerClient var err error var retNetwork *dc.Network @@ -86,7 +86,7 @@ func resourceDockerNetworkRead(d *schema.ResourceData, meta interface{}) error { } func resourceDockerNetworkDelete(d *schema.ResourceData, meta interface{}) error { - client := meta.(*dc.Client) + client := meta.(*ProviderConfig).DockerClient if err := client.RemoveNetwork(d.Id()); err != nil { if _, ok := err.(*dc.NoSuchNetwork); !ok { diff --git a/docker/resource_docker_network_test.go b/docker/resource_docker_network_test.go index 5fe7f8b3..f652997e 100644 --- a/docker/resource_docker_network_test.go +++ b/docker/resource_docker_network_test.go @@ -37,7 +37,7 @@ func testAccNetwork(n string, network *dc.Network) resource.TestCheckFunc { return fmt.Errorf("No ID is set") } - client := testAccProvider.Meta().(*dc.Client) + client := testAccProvider.Meta().(*ProviderConfig).DockerClient networks, err := client.ListNetworks() if err != nil { return err diff --git a/docker/resource_docker_volume.go b/docker/resource_docker_volume.go index 33c22d58..eb52202e 100644 --- a/docker/resource_docker_volume.go +++ b/docker/resource_docker_volume.go @@ -40,7 +40,7 @@ func resourceDockerVolume() *schema.Resource { } func resourceDockerVolumeCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*dc.Client) + client := meta.(*ProviderConfig).DockerClient createOpts := dc.CreateVolumeOptions{} if v, ok := d.GetOk("name"); ok { @@ -71,7 +71,7 @@ func resourceDockerVolumeCreate(d *schema.ResourceData, meta interface{}) error } func resourceDockerVolumeRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*dc.Client) + client := meta.(*ProviderConfig).DockerClient var err error var retVolume *dc.Volume @@ -91,7 +91,7 @@ func resourceDockerVolumeRead(d *schema.ResourceData, meta interface{}) error { } func resourceDockerVolumeDelete(d *schema.ResourceData, meta interface{}) error { - client := meta.(*dc.Client) + client := meta.(*ProviderConfig).DockerClient if err := client.RemoveVolume(d.Id()); err != nil && err != dc.ErrNoSuchVolume { return fmt.Errorf("Error deleting volume %s: %s", d.Id(), err) diff --git a/docker/resource_docker_volume_test.go b/docker/resource_docker_volume_test.go index 38fec3c4..6f840074 100644 --- a/docker/resource_docker_volume_test.go +++ b/docker/resource_docker_volume_test.go @@ -39,7 +39,7 @@ func checkDockerVolume(n string, volume *dc.Volume) resource.TestCheckFunc { return fmt.Errorf("No ID is set") } - client := testAccProvider.Meta().(*dc.Client) + client := testAccProvider.Meta().(*ProviderConfig).DockerClient volumes, err := client.ListVolumes(dc.ListVolumesOptions{}) if err != nil { return err diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index f55e0973..22e85c9c 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -37,8 +37,50 @@ resource "docker_image" "ubuntu" { ## Registry Credentials -The initial (current) version of the Docker provider **doesn't** support registry authentication. -This limits any use cases to public images for now. +Registry credentials can be provided on a per-registry basis with the `registry_auth` +field, passing either a config file or the username/password directly. + +``` hcl +provider "docker" { + host = "tcp://localhost:2376" + + registry_auth { + address = "registry.hub.docker.com" + config_file = "~/.docker/config.json" + } + + registry_auth { + address = "quay.io:8181" + username = "someuser" + password = "somepass" + } +} + +data "docker_registry_image" "quay" { + name = "myorg/privateimage" +} + +data "docker_registry_image" "quay" { + name = "quay.io:8181/myorg/privateimage" +} +``` + +**NOTES** +- The location of the config file is on the machine terraform runs on, nevertheless if the specified docker host is on another machine. +- When passing in a config file make sure every repo in the `auths` object has +an `auth` string. If not you'll get an `ErrCannotParseDockercfg` by the underlying `go-dockerclient`. On OSX the `auth` base64 string is stored in the `osxkeychain`, but reading from there is not yet supported. See [go-dockerclient#677](https://github.com/fsouza/go-dockerclient/issues/677) for details. In this case, either use `username` and `password` directly or add the string manually to the `config.json` by creating it via `echo -n "user:pass" | base64`. + +`~/.docker/config.json` +```json +{ + "auths": { + "repo.mycompany:8181": { + "auth": "dXNlcjpwYXNz=" + } + }, + ... +} +``` ## Argument Reference @@ -54,6 +96,25 @@ The following arguments are supported: * `ca_material`, `cert_material`, `key_material`, - (Optional) Content of `ca.pem`, `cert.pem`, and `key.pem` files for TLS authentication. Cannot be used together with `cert_path`. +* `registry_auth` - (Optional) A block specifying the credentials for a target + v2 Docker registry. + + * `address` - (Required) The address of the registry. + + * `username` - (Optional) The username to use for authenticating to the registry. + Cannot be used with the `config_file` option. If this is blank, the `DOCKER_REGISTRY_USER` + will also be checked. + + * `password` - (Optional) The password to use for authenticating to the registry. + Cannot be used with the `config_file` option. If this is blank, the `DOCKER_REGISTRY_PASS` + will also be checked. + + * `config_file` - (Optional) The path to a config file containing credentials for + authenticating to the registry. Cannot be used with the `username`/`password` options. + If this is blank, the `DOCKER_CONFIG` will also be checked. + + + ~> **NOTE on Certificates and `docker-machine`:** As per [Docker Remote API documentation](https://docs.docker.com/engine/reference/api/docker_remote_api/), in any docker-machine environment, the Docker daemon uses an encrypted TCP