mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2025-12-27 10:09:39 -05:00
Feat/private registry support (#21)
* Add support for private registry auth. Adapted documentation and tests. * Clarified README.
This commit is contained in:
parent
9e21a8f683
commit
76925f68f8
15 changed files with 366 additions and 37 deletions
23
README.md
23
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/.
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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}"]
|
||||
}
|
||||
`
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue