fix/service image name (#212)

* fix(service): remove image name suppress function
* feat: add docker image data-source
* docs(service): add example for iamge datasource usage
* fix: image repo digest with tag determination
* fix: always return a repoDigest
* fix(image): deprecation notice for latest attribute
* fix(ci): explicitly go get tfplugindocs
* fix(ci): remove gocenter.io proxy
This commit is contained in:
Manuel Vogel 2021-06-21 09:24:02 +02:00 committed by GitHub
parent 7f4c14d1f5
commit 4d40b84443
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 650 additions and 243 deletions

View file

@ -12,7 +12,7 @@ on:
- '.github/workflows/**'
env:
GOPROXY: https://proxy.golang.org,https://gocenter.io,direct
GOPROXY: https://proxy.golang.org,direct
DEBIAN_FRONTEND: noninteractive
DOCKER_CE_VERSION: "5:20.10.5~3-0~ubuntu-focal"
GO_VERSION: "1.16"

View file

@ -12,7 +12,7 @@ on:
- '.github/workflows/**'
env:
GOPROXY: https://proxy.golang.org,https://gocenter.io,direct
GOPROXY: https://proxy.golang.org,direct
GO_VERSION: "1.16"
jobs:

View file

@ -12,7 +12,7 @@ on:
- '.github/workflows/**'
env:
GOPROXY: https://proxy.golang.org,https://gocenter.io,direct
GOPROXY: https://proxy.golang.org,direct
GO_VERSION: "1.16"
jobs:

View file

@ -11,7 +11,7 @@ on:
- docs/**
env:
GOPROXY: https://proxy.golang.org,https://gocenter.io,direct
GOPROXY: https://proxy.golang.org,direct
GO_VERSION: "1.16"
jobs:
@ -24,6 +24,8 @@ jobs:
go-version: ${{ env.GO_VERSION }}
- name: Setup tools
run: make setup
- name: Explicitly get tfplugindocs
run: go get github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs
- name: Generate the website
run: make website-generation
- name: Verify Changed files

View file

@ -8,7 +8,6 @@ build: fmtcheck
go install
setup:
go mod download
cd tools && GO111MODULE=on go install github.com/client9/misspell/cmd/misspell
cd tools && GO111MODULE=on go install github.com/katbyte/terrafmt
cd tools && GO111MODULE=on go install github.com/golangci/golangci-lint/cmd/golangci-lint
@ -76,7 +75,7 @@ website-lint:
echo "Unexpected mispelling found in website files."; \
echo "To automatically fix the misspelling, run 'make website-lint-fix' and commit the changes."; \
exit 1)
@docker run -v $(PWD):/markdown 06kellyjac/markdownlint-cli docs/ || (echo; \
@docker run --rm -v $(PWD):/markdown 06kellyjac/markdownlint-cli docs/ || (echo; \
echo "Unexpected issues found in website Markdown files."; \
echo "To apply any automatic fixes, run 'make website-lint-fix' and commit the changes."; \
exit 1)
@ -89,7 +88,7 @@ website-lint:
website-lint-fix:
@echo "==> Applying automatic website linter fixes..."
@misspell -w -source=text docs/
@docker run -v $(PWD):/markdown 06kellyjac/markdownlint-cli --fix docs/
@docker run --rm -v $(PWD):/markdown 06kellyjac/markdownlint-cli --fix docs/
@terrafmt fmt ./docs --pattern '*.md'
.PHONY: build test testacc vet fmt fmtcheck errcheck test-compile website-link-check website-lint website-lint-fix

View file

@ -25,7 +25,7 @@ terraform {
# since new versions are released frequently
docker = {
source = "kreuzwerker/docker"
version = "2.12.1"
version = "2.12.2"
}
}
}
@ -42,9 +42,38 @@ resource "docker_image" "nginx" {
}
# Create a docker container resource
# -> same as 'docker run --name nginx -d nginx:latest'
# -> same as 'docker run --name nginx -p8080:80 -d nginx:latest'
resource "docker_container" "nginx" {
name = "nginx"
image = docker_image.nginx.latest
ports {
external = 8080
internal = 80
}
}
# Or create a service resource
# -> same as 'docker service create -d -p 8081:80 --name nginx-service --replicas 2 nginx:latest'
resource "docker_service" "nginx_service" {
name = "nginx-service"
task_spec {
container_spec {
image = docker_image.nginx.repo_digest
}
}
mode {
replicated {
replicas = 2
}
}
endpoint_spec {
ports {
published_port = 8081
target_port = 80
}
}
}
```

View file

@ -0,0 +1,52 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "docker_image Data Source - terraform-provider-docker"
subcategory: ""
description: |-
docker_image provides details about a specific Docker Image which need to be presend on the Docker Host
---
# docker_image (Data Source)
`docker_image` provides details about a specific Docker Image which need to be presend on the Docker Host
## Example Usage
```terraform
# uses the 'latest' tag
data "docker_image" "latest" {
name = "nginx"
}
# uses a specific tag
data "docker_image" "specific" {
name = "nginx:1.17.6"
}
# use the image digest
data "docker_image" "digest" {
name = "nginx@sha256:36b74457bccb56fbf8b05f79c85569501b721d4db813b684391d63e02287c0b2"
}
# uses the tag and the image digest
data "docker_image" "tag_and_digest" {
name = "nginx:1.19.1@sha256:36b74457bccb56fbf8b05f79c85569501b721d4db813b684391d63e02287c0b2"
}
```
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- **name** (String) The name of the Docker image, including any tags or SHA256 repo digests.
### Optional
- **id** (String) The ID of this resource.
### Read-Only
- **repo_digest** (String) The image sha256 digest in the form of `repo[:tag]@sha256:<hash>`. It may be empty in the edge case where the local image was pulled from a repo, tagged locally, and then referred to in the data source by that local name/tag.

View file

@ -27,7 +27,7 @@ resource "docker_image" "ubuntu" {
### Dynamic updates
To be able to update an update dynamically when the `sha256` sum changes,
To be able to update an image dynamically when the `sha256` sum changes,
you need to use it in combination with `docker_registry_image` as follows:
```terraform
@ -43,8 +43,8 @@ resource "docker_image" "ubuntu" {
### Build
You can also use the resource to build and image.
In thid case the image "zoo" and "zoo:develop" are built.
You can also use the resource to build an image.
In this case the image "zoo" and "zoo:develop" are built.
```terraform
resource "docker_image" "zoo" {
@ -80,8 +80,9 @@ resource "docker_image" "zoo" {
### Read-Only
- **latest** (String) The ID of the image.
- **latest** (String, Deprecated) The ID of the image in the form of `sha256:<hash>` image digest. Do not confuse it with the default `latest` tag.
- **output** (String, Deprecated)
- **repo_digest** (String) The image sha256 digest in the form of `repo[:tag]@sha256:<hash>`.
<a id="nestedblock--build"></a>
### Nested Schema for `build`
@ -99,4 +100,4 @@ Optional:
- **no_cache** (Boolean) Do not use cache when building the image
- **remove** (Boolean) Remove intermediate containers after a successful build. Defaults to `true`.
- **tag** (List of String) Name and optionally a tag in the 'name:tag' format
- **target** (String) Set the target build stage to build
- **target** (String) Set the target build stage to build

View file

@ -48,6 +48,33 @@ The following command is the equivalent:
docker service create -d -p 8080 --name foo-service repo.mycompany.com:8080/foo-service:v1
```
### Basic with Datasource
Alternatively, if the image is already present on the Docker Host and not managed
by `terraform`, you can also use the `docker_image` datasource:
```terraform
data "docker_image" "foo" {
name = "repo.mycompany.com:8080/foo-service:v1"
}
resource "docker_service" "foo" {
name = "foo-service"
task_spec {
container_spec {
image = data.docker_image.foo.repo_digest
}
}
endpoint_spec {
ports {
target_port = "8080"
}
}
}
```
### Advanced
The following configuration shows the full capabilities of a Docker Service,
@ -307,7 +334,7 @@ Optional:
Required:
- **image** (String) The image name to use for the containers of the service
- **image** (String) The image name to use for the containers of the service, like `nginx:1.17.6`. Also use the data-source or resource of `docker_image` with the `repo_digest` or `docker_registry_image` with the `name` attribute for this, as shown in the examples.
Optional:

View file

@ -0,0 +1,19 @@
# uses the 'latest' tag
data "docker_image" "latest" {
name = "nginx"
}
# uses a specific tag
data "docker_image" "specific" {
name = "nginx:1.17.6"
}
# use the image digest
data "docker_image" "digest" {
name = "nginx@sha256:36b74457bccb56fbf8b05f79c85569501b721d4db813b684391d63e02287c0b2"
}
# uses the tag and the image digest
data "docker_image" "tag_and_digest" {
name = "nginx:1.19.1@sha256:36b74457bccb56fbf8b05f79c85569501b721d4db813b684391d63e02287c0b2"
}

View file

@ -0,0 +1,19 @@
data "docker_image" "foo" {
name = "repo.mycompany.com:8080/foo-service:v1"
}
resource "docker_service" "foo" {
name = "foo-service"
task_spec {
container_spec {
image = data.docker_image.foo.repo_digest
}
}
endpoint_spec {
ports {
target_port = "8080"
}
}
}

View file

@ -1,3 +1,4 @@
resource "docker_service" "foo" {
name = "foo-service"
@ -12,4 +13,4 @@ resource "docker_service" "foo" {
target_port = "8080"
}
}
}
}

View file

@ -0,0 +1,101 @@
package provider
import (
"context"
"log"
"strings"
"github.com/docker/docker/api/types"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func dataSourceDockerImage() *schema.Resource {
return &schema.Resource{
Description: "`docker_image` provides details about a specific Docker Image which need to be presend on the Docker Host",
ReadContext: dataSourceDockerImageRead,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Description: "The name of the Docker image, including any tags or SHA256 repo digests.",
Required: true,
},
"repo_digest": {
Type: schema.TypeString,
Description: "The image sha256 digest in the form of `repo[:tag]@sha256:<hash>`. It may be empty in the edge case where the local image was pulled from a repo, tagged locally, and then referred to in the data source by that local name/tag.",
Computed: true,
},
},
}
}
func dataSourceDockerImageRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*ProviderConfig).DockerClient
var data Data
if err := fetchLocalImages(ctx, &data, client); err != nil {
return diag.Errorf("Error reading docker image list: %s", err)
}
for id := range data.DockerImages {
log.Printf("[DEBUG] local images data: %v", id)
}
imageName := d.Get("name").(string)
foundImage := searchLocalImages(ctx, client, data, imageName)
if foundImage == nil {
return diag.Errorf("did not find docker image '%s'", imageName)
}
repoDigest := determineRepoDigest(imageName, foundImage)
d.SetId(foundImage.ID)
d.Set("name", imageName)
d.Set("repo_digest", repoDigest)
return nil
}
// determineRepoDigest determines the repo digest for a local image name.
// It will always return a digest and if none was found it returns an empty string.
// See https://github.com/kreuzwerker/terraform-provider-docker/pull/212#discussion_r646025706 for details
func determineRepoDigest(imageName string, imageToQuery *types.ImageSummary) string {
// the edge case where the local image was pulled from a repo, tagged locally,
// and then referred to in the data source by that local name/tag...
if len(imageToQuery.RepoDigests) == 0 {
return ""
}
// the standard case when there is only one digest
if len(imageToQuery.RepoDigests) == 1 {
return imageToQuery.RepoDigests[0]
}
// the special case when the same image is in multiple registries
// we first need to strip a possible tag because the digest do not contain it
imageNameWithoutTag := imageName
tagColonIndex := strings.Index(imageName, ":")
// we have a tag
if tagColonIndex != -1 {
imageNameWithoutTag = imageName[:tagColonIndex]
}
for _, repoDigest := range imageToQuery.RepoDigests {
// we look explicitly at the beginning of the digest
// as the image name is e.g. nginx:1.19.1 or 127.0.0.1/nginx:1.19.1 and the digests are
// "RepoDigests": [
// "127.0.0.1:15000/nginx@sha256:a5a1e8e5148de5ebc9697b64e4edb2296b5aac1f05def82efdc00ccb7b457171",
// "nginx@sha256:36b74457bccb56fbf8b05f79c85569501b721d4db813b684391d63e02287c0b2"
// ],
if strings.HasPrefix(repoDigest, imageNameWithoutTag) {
return repoDigest
}
}
// another edge case where the image was pulled from somewhere, pushed somewhere else,
// but the tag being referenced in the data is that local-only tag
log.Printf("[WARN] could not determine repo digest for image name '%s' and repo digests: %v. Will fall back to '%s'", imageName, imageToQuery.RepoDigests, imageToQuery.RepoDigests[0])
return imageToQuery.RepoDigests[0]
}

View file

@ -0,0 +1,203 @@
package provider
import (
"context"
"fmt"
"os/exec"
"regexp"
"strings"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
var imageRepoDigestRegexp = regexp.MustCompile(`^.*@sha256:[A-Fa-f0-9]+$`)
var imageNameWithTagAndDigestRegexp = regexp.MustCompile(`^.*:.*@sha256:[A-Fa-f0-9]+$`)
func TestAccDockerImageDataSource_withSpecificTag(t *testing.T) {
ctx := context.Background()
imageName := "nginx:1.17.6"
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
pullImageForTest(t, imageName)
},
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: loadTestConfiguration(t, DATA_SOURCE, "docker_image", "testAccDockerImageDataSourceWithSpecificTag"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.docker_image.foo", "name", imageName),
resource.TestCheckResourceAttr("data.docker_image.foo", "repo_digest", "nginx@sha256:b2d89d0a210398b4d1120b3e3a7672c16a4ba09c2c4a0395f18b9f7999b768f2"),
),
},
},
CheckDestroy: func(state *terraform.State) error {
return removeImageForTest(ctx, state, imageName)
},
})
}
func TestAccDockerImageDataSource_withDefaultTag(t *testing.T) {
ctx := context.Background()
imageName := "nginx"
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
pullImageForTest(t, imageName)
},
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: loadTestConfiguration(t, DATA_SOURCE, "docker_image", "testAccDockerImageDataSourceWithDefaultTag"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.docker_image.foo", "name", imageName),
resource.TestMatchResourceAttr("data.docker_image.foo", "repo_digest", imageRepoDigestRegexp),
),
},
},
CheckDestroy: func(state *terraform.State) error {
return removeImageForTest(ctx, state, imageName)
},
})
}
func TestAccDockerImageDataSource_withSha256Digest(t *testing.T) {
ctx := context.Background()
imageName := "nginx@sha256:36b74457bccb56fbf8b05f79c85569501b721d4db813b684391d63e02287c0b2"
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
pullImageForTest(t, imageName)
},
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: loadTestConfiguration(t, DATA_SOURCE, "docker_image", "testAccDockerImageDataSourceWithSha256Digest"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.docker_image.foo", "name", imageName),
resource.TestMatchResourceAttr("data.docker_image.foo", "repo_digest", imageRepoDigestRegexp),
),
},
},
CheckDestroy: func(state *terraform.State) error {
return removeImageForTest(ctx, state, imageName)
},
})
}
func TestAccDockerImageDataSource_withTagAndSha256Digest(t *testing.T) {
ctx := context.Background()
imageName := "nginx:1.19.1@sha256:36b74457bccb56fbf8b05f79c85569501b721d4db813b684391d63e02287c0b2"
resource.Test(t, resource.TestCase{
PreCheck: func() {
testAccPreCheck(t)
pullImageForTest(t, imageName)
},
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: loadTestConfiguration(t, DATA_SOURCE, "docker_image", "testAccDockerImageDataSourceWithTagAndSha256Digest"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.docker_image.foo", "name", imageName),
resource.TestMatchResourceAttr("data.docker_image.foo", "repo_digest", imageRepoDigestRegexp),
),
},
},
CheckDestroy: func(state *terraform.State) error {
return removeImageForTest(ctx, state, imageName)
},
})
}
func TestAccDockerImageDataSource_withNonExistentImage(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: `
data "docker_image" "foo" {
name = "image-does-not-exist"
}
`,
ExpectError: regexp.MustCompile(`.*did not find docker image.*`),
},
{
Config: `
data "docker_image" "foo" {
name = "nginx:tag-does-not-exist"
}
`,
ExpectError: regexp.MustCompile(`.*did not find docker image.*`),
},
{
Config: `
data "docker_image" "foo" {
name = "nginx@shaDoesNotExist"
}
`,
ExpectError: regexp.MustCompile(`.*did not find docker image.*`),
},
{
Config: `
data "docker_image" "foo" {
name = "nginx:1.19.1@shaDoesNotExist"
}
`,
ExpectError: regexp.MustCompile(`.*did not find docker image.*`),
},
},
})
}
// Helpers
func pullImageForTest(t *testing.T, imageName string) {
cmd := exec.Command("docker", "pull", imageName)
if err := cmd.Run(); err != nil {
t.Fatalf("Failed to pull image '%s': %s", imageName, err)
}
}
func removeImageForTest(ctx context.Context, s *terraform.State, imageName string) error {
client := testAccProvider.Meta().(*ProviderConfig).DockerClient
// for images with tag and digest like e.g.
// 'nginx:1.19.1@sha256:36b74457bccb56fbf8b05f79c85569501b721d4db813b684391d63e02287c0b2'
// no image is found. This is why we strip the tag to remain with
// 'nginx@sha256:36b74457bccb56fbf8b05f79c85569501b721d4db813b684391d63e02287c0b2'
if imageNameWithTagAndDigestRegexp.MatchString(imageName) {
tagStartIndex := strings.Index(imageName, ":")
digestStartIndex := strings.Index(imageName, "@")
imageName = imageName[:tagStartIndex] + imageName[digestStartIndex:]
}
filters := filters.NewArgs()
filters.Add("reference", imageName)
images, err := client.ImageList(ctx, types.ImageListOptions{
Filters: filters,
})
if err != nil {
return err
}
if len(images) == 0 {
return fmt.Errorf("did not find any image with name '%s' to delete", imageName)
}
for _, image := range images {
_, err := client.ImageRemove(ctx, image.ID, types.ImageRemoveOptions{
Force: true,
})
if err != nil {
return fmt.Errorf("failed to remove image with ID '%s'", image.ID)
}
}
return nil
}

View file

@ -134,6 +134,7 @@ func New(version string) func() *schema.Provider {
"docker_registry_image": dataSourceDockerRegistryImage(),
"docker_network": dataSourceDockerNetwork(),
"docker_plugin": dataSourceDockerPlugin(),
"docker_image": dataSourceDockerImage(),
},
}

View file

@ -120,7 +120,6 @@ func resourceDockerContainer() *schema.Resource {
Description: "The ID of the image to back this container. The easiest way to get this value is to use the `docker_image` resource as is shown in the example.",
Required: true,
ForceNew: true,
// DiffSuppressFunc: suppressIfSHAwasAdded(), // TODO mvogel
},
"hostname": {

View file

@ -734,7 +734,6 @@ func resourceDockerContainerReadRefreshFunc(ctx context.Context,
if container.State.Running ||
!container.State.Running && !d.Get("must_run").(bool) {
log.Printf("[DEBUG] Container %s is running: %v", containerID, container.State.Running)
// break
return container, "running", nil
}
@ -753,6 +752,18 @@ func resourceDockerContainerReadRefreshFunc(ctx context.Context,
return container, "pending", errContainerExitedImmediately
}
// TODO mavogel wait until all properties are exposed from the API
// dns = []
// dns_opts = []
// dns_search = []
// group_add = []
// id = "9e6d9e987923e2c3a99f17e8781c7ce3515558df0e45f8ab06f6adb2dda0de50"
// links = []
// log_opts = {}
// name = "nginx"
// sysctls = {}
// tmpfs = {}
return container, "running", nil
}
}

View file

@ -90,7 +90,6 @@ func resourceDockerContainerV1() *schema.Resource {
Type: schema.TypeString,
Required: true,
ForceNew: true,
// DiffSuppressFunc: suppressIfSHAwasAdded(), // TODO mvogel
},
"hostname": {

View file

@ -22,7 +22,14 @@ func resourceDockerImage() *schema.Resource {
"latest": {
Type: schema.TypeString,
Description: "The ID of the image.",
Description: "The ID of the image in the form of `sha256:<hash>` image digest. Do not confuse it with the default `latest` tag.",
Computed: true,
Deprecated: "Use repo_digest instead",
},
"repo_digest": {
Type: schema.TypeString,
Description: "The image sha256 digest in the form of `repo[:tag]@sha256:<hash>`.",
Computed: true,
},

View file

@ -63,9 +63,12 @@ func resourceDockerImageRead(ctx context.Context, d *schema.ResourceData, meta i
return nil
}
repoDigest := determineRepoDigest(imageName, foundImage)
// TODO mavogel: remove the appended name from the ID
d.SetId(foundImage.ID + d.Get("name").(string))
d.Set("latest", foundImage.ID)
d.Set("repo_digest", repoDigest)
return nil
}
@ -86,6 +89,7 @@ func resourceDockerImageUpdate(ctx context.Context, d *schema.ResourceData, meta
func resourceDockerImageDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*ProviderConfig).DockerClient
// TODO mavogel: add retries. see e.g. service updateFailsAndRollbackConvergeConfig test
err := removeImage(ctx, d, client)
if err != nil {
return diag.Errorf("Unable to remove Docker image: %s", err)

View file

@ -82,6 +82,7 @@ func TestAccDockerImage_private(t *testing.T) {
Config: loadTestConfiguration(t, RESOURCE, "docker_image", "testAddDockerPrivateImageConfig"),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_image.foobar", "latest", contentDigestRegexp),
resource.TestMatchResourceAttr("docker_image.foobar", "repo_digest", imageRepoDigestRegexp),
testAccImageCreated("docker_image.foobar", &i),
testCheckImageInspect,
),
@ -114,6 +115,7 @@ func TestAccDockerImage_destroy(t *testing.T) {
Config: loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageKeepLocallyConfig"),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_image.foobarzoo", "latest", contentDigestRegexp),
resource.TestMatchResourceAttr("docker_image.foobarzoo", "repo_digest", imageRepoDigestRegexp),
),
},
},
@ -130,6 +132,7 @@ func TestAccDockerImage_data(t *testing.T) {
Config: loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageFromDataConfig"),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_image.foobarbaz", "latest", contentDigestRegexp),
resource.TestMatchResourceAttr("docker_image.foobarbaz", "repo_digest", imageRepoDigestRegexp),
),
},
},
@ -146,6 +149,7 @@ func TestAccDockerImage_data_pull_trigger(t *testing.T) {
Config: loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageFromDataConfigWithPullTrigger"),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_image.foobarbazoo", "latest", contentDigestRegexp),
resource.TestMatchResourceAttr("docker_image.foobarbazoo", "repo_digest", imageRepoDigestRegexp),
),
},
},
@ -166,6 +170,7 @@ func TestAccDockerImage_data_private(t *testing.T) {
Config: fmt.Sprintf(loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageFromDataPrivateConfig"), registry, image),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_image.foo_private", "latest", contentDigestRegexp),
resource.TestMatchResourceAttr("docker_image.foo_private", "repo_digest", imageRepoDigestRegexp),
),
},
},
@ -191,6 +196,7 @@ func TestAccDockerImage_data_private_config_file(t *testing.T) {
Config: fmt.Sprintf(loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageFromDataPrivateConfigFile"), registry, dockerConfig, image),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_image.foo_private", "latest", contentDigestRegexp),
resource.TestMatchResourceAttr("docker_image.foo_private", "repo_digest", imageRepoDigestRegexp),
),
},
},
@ -216,6 +222,7 @@ func TestAccDockerImage_data_private_config_file_content(t *testing.T) {
Config: fmt.Sprintf(loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageFromDataPrivateConfigFileContent"), registry, dockerConfig, image),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_image.foo_private", "latest", contentDigestRegexp),
resource.TestMatchResourceAttr("docker_image.foo_private", "repo_digest", imageRepoDigestRegexp),
),
},
},
@ -238,6 +245,7 @@ func TestAccDockerImage_sha265(t *testing.T) {
Config: loadTestConfiguration(t, RESOURCE, "docker_image", "testAddDockerImageWithSHA256RepoDigest"),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_image.foobar", "latest", contentDigestRegexp),
resource.TestMatchResourceAttr("docker_image.foobar", "repo_digest", imageRepoDigestRegexp),
),
},
},
@ -272,6 +280,7 @@ func TestAccDockerImage_tag_sha265(t *testing.T) {
Config: loadTestConfiguration(t, RESOURCE, "docker_image", "testAccDockerImageWithTagAndSHA256RepoDigest"),
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_image.nginx", "latest", contentDigestRegexp),
resource.TestMatchResourceAttr("docker_image.nginx", "repo_digest", imageRepoDigestRegexp),
),
},
},

View file

@ -1,10 +1,6 @@
package provider
import (
"fmt"
"log"
"strings"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
@ -83,10 +79,9 @@ func resourceDockerService() *schema.Resource {
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"image": {
Type: schema.TypeString,
Description: "The image name to use for the containers of the service",
Required: true,
DiffSuppressFunc: suppressIfSHAwasAdded(),
Type: schema.TypeString,
Description: "The image name to use for the containers of the service, like `nginx:1.17.6`. Also use the data-source or resource of `docker_image` with the `repo_digest` or `docker_registry_image` with the `name` attribute for this, as shown in the examples.",
Required: true,
},
"labels": {
Type: schema.TypeSet,
@ -944,96 +939,3 @@ func resourceDockerService() *schema.Resource {
},
}
}
func suppressIfSHAwasAdded() schema.SchemaDiffSuppressFunc {
return func(k, old, new string, d *schema.ResourceData) bool {
// the initial case when the service is created
if old == "" && new != "" {
return false
}
oldURL, oldImage, oldTag, oldDigest, oldErr := splitImageName(old)
if oldErr != nil {
log.Printf("[DEBUG] invalid old image name: %s\n", oldErr.Error())
return false
}
log.Printf("[DEBUG] old image parse: %s, %s, %s, %s\n", oldURL, oldImage, oldTag, oldDigest)
newURL, newImage, newTag, newDigest, newErr := splitImageName(new)
if newErr != nil {
log.Printf("[DEBUG] invalid new image name: %s\n", newErr.Error())
return false
}
log.Printf("[DEBUG] new image parse: %s, %s, %s, %s\n", newURL, newImage, newTag, newDigest)
if oldURL != newURL || oldImage != newImage {
return false
}
// special case with latest
if oldTag == "latest" && (newTag == "" || newTag == "latest") {
if oldDigest != "" && newDigest == "" {
return true
}
return false
}
// https://success.docker.com/article/images-tagging-vs-digests
// we always pull if the tag changes, also in the empty and 'latest' case
if (oldTag == "latest" || newTag == "") || (oldTag == "" && newTag == "latest") {
return false
}
if oldTag != newTag {
return false
}
// tags are the same and so should be its digests
if oldDigest == newDigest || (oldDigest == "" && newDigest != "") || (oldDigest != "" && newDigest == "") {
return true
}
// we only update if the digests are given and different
if oldDigest != newDigest {
return false
}
return true
}
}
// spitImageName splits an image with name 127.0.0.1:15000/tftest-service:v1@sha256:24..
// into its parts. Handles edge cases like no tag and no digest
func splitImageName(imageNameToSplit string) (url, image, tag, digest string, err error) {
urlToRestSplit := strings.Split(imageNameToSplit, "/")
if len(urlToRestSplit) != 2 {
return "", "", "", "", fmt.Errorf("image name is not valid: %s", imageNameToSplit)
}
url = urlToRestSplit[0]
imageNameToRestSplit := strings.Split(urlToRestSplit[1], ":")
// we only have an image name without tag and sha256
if len(imageNameToRestSplit) == 1 {
image = imageNameToRestSplit[0]
return url, image, "", "", nil
}
// has tag and sha256
if len(imageNameToRestSplit) == 3 {
image = imageNameToRestSplit[0]
tag = strings.Replace(imageNameToRestSplit[1], "@sha256", "", 1)
digest = imageNameToRestSplit[2]
return url, image, tag, digest, nil
}
// can be either with tag or sha256, which implies 'latest' tag
if len(imageNameToRestSplit) == 2 {
image = imageNameToRestSplit[0]
if strings.Contains(imageNameToRestSplit[1], "sha256") {
digest = imageNameToRestSplit[1]
return url, image, "", digest, nil
}
tag = strings.Replace(imageNameToRestSplit[1], "@sha256", "", 1)
return url, image, tag, "", nil
}
return "", "", "", "", fmt.Errorf("image name is not valid: %s", imageNameToSplit)
}

View file

@ -99,10 +99,9 @@ func resourceDockerServiceV1() *schema.Resource {
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"image": {
Type: schema.TypeString,
Description: "The image name to use for the containers of the service",
Required: true,
DiffSuppressFunc: suppressIfSHAwasAdded(),
Type: schema.TypeString,
Description: "The image name to use for the containers of the service",
Required: true,
},
"labels": {
Type: schema.TypeSet,
@ -1021,10 +1020,9 @@ func resourceDockerServiceV0() *schema.Resource {
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"image": {
Type: schema.TypeString,
Description: "The image name to use for the containers of the service",
Required: true,
DiffSuppressFunc: suppressIfSHAwasAdded(),
Type: schema.TypeString,
Description: "The image name to use for the containers of the service",
Required: true,
},
"labels": {
Type: schema.TypeMap,

View file

@ -277,79 +277,6 @@ func checkAttribute(t *testing.T, name, actual, expected string) {
}
}
func TestDockerImageNameSuppress(t *testing.T) {
suppressFunc := suppressIfSHAwasAdded()
old := ""
new := "alpine3.1"
suppress := suppressFunc("k", old, new, nil)
if suppress {
t.Fatalf("Expected no suppress for \n\told '%s' \n\tnew '%s'", old, new)
}
old = "127.0.0.1:15000/tftest-service:v1"
new = "127.0.0.1:15000/tftest-service:v1@sha256:74d04f400723d9770187ee284255d1eb556f3d51700792fb2bfd6ab13da50981"
suppress = suppressFunc("k", old, new, nil)
if !suppress {
t.Fatalf("Expected suppress for \n\told '%s' \n\tnew '%s'", old, new)
}
old = "127.0.0.1:15000/tftest-service:latest@sha256:74d04f400723d9770187ee284255d1eb556f3d51700792fb2bfd6ab13da50981"
new = "127.0.0.1:15000/tftest-service"
suppress = suppressFunc("k", old, new, nil)
if !suppress {
t.Fatalf("Expected suppress for \n\told '%s' \n\tnew '%s'", old, new)
}
old = "127.0.0.1:15000/tftest-service:latest"
new = "127.0.0.1:15000/tftest-service:latest@sha256:74d04f400723d9770187ee284255d1eb556f3d51700792fb2bfd6ab13da50981"
suppress = suppressFunc("k", old, new, nil)
if suppress {
t.Fatalf("Expected no suppress for \n\told '%s' \n\tnew '%s'", old, new)
}
old = "127.0.0.1:15000/tftest-service"
new = "127.0.0.1:15000/tftest-service:latest@sha256:74d04f400723d9770187ee284255d1eb556f3d51700792fb2bfd6ab13da50981"
suppress = suppressFunc("k", old, new, nil)
if suppress {
t.Fatalf("Expected no suppress for \n\told '%s' \n\tnew '%s'", old, new)
}
old = "127.0.0.1:15000/tftest-service:v1"
new = "127.0.0.1:15000/tftest-service:v2@sha256:ed8e15d68bb13e3a04abddc295f87d2a8b7d849d5ff91f00dbdd66dc10fd8aac"
suppress = suppressFunc("k", old, new, nil)
if suppress {
t.Fatalf("Expected no suppress for image tag update from \n\told '%s' \n\tnew '%s'", old, new)
}
old = "127.0.0.1:15000/tftest-service:v1@sha256:74d04f400723d9770187ee284255d1eb556f3d51700792fb2bfd6ab13da50981"
new = "127.0.0.1:15000/tftest-service:v2@sha256:74d04f400723d9770187ee284255d1eb556f3d51700792fb2bfd6ab13da50981"
suppress = suppressFunc("k", old, new, nil)
if suppress {
t.Fatalf("Expected no suppress for image tag update from \n\told '%s' \n\tnew '%s'", old, new)
}
old = "127.0.0.1:15000/tftest-service:latest@sha256:74d04f400723d9770187ee284255d1eb556f3d51700792fb2bfd6ab13da50981"
new = "127.0.0.1:15000/tftest-service:latest@sha256:c9d1055182f0607632b7d859d2f220126fb1c0d10aedc4451817840b30c1af86"
suppress = suppressFunc("k", old, new, nil)
if suppress {
t.Fatalf("Expected no suppress for image digest update from \n\told '%s' \n\tnew '%s'", old, new)
}
old = "127.0.0.1:15000/tftest-service:v3@sha256:74d04f400723d9770187ee284255d1eb556f3d51700792fb2bfd6ab13da50981"
new = "127.0.0.1:15000/tftest-service:latest@sha256:c9d1055182f0607632b7d859d2f220126fb1c0d10aedc4451817840b30c1af86"
suppress = suppressFunc("k", old, new, nil)
if suppress {
t.Fatalf("Expected no suppress for image tag but no digest update from \n\told '%s' \n\tnew '%s'", old, new)
}
old = "127.0.0.1:15000/tftest-service@sha256:74d04f400723d9770187ee284255d1eb556f3d51700792fb2bfd6ab13da50981"
new = "127.0.0.1:15000/tftest-service@sha256:c9d1055182f0607632b7d859d2f220126fb1c0d10aedc4451817840b30c1af86"
suppress = suppressFunc("k", old, new, nil)
if suppress {
t.Fatalf("Expected no suppress for image tag but no digest update from \n\told '%s' \n\tnew '%s'", old, new)
}
}
// ----------------------------------------
// ----------- ACCEPTANCE TESTS -----------
// ----------------------------------------
@ -367,7 +294,7 @@ func TestAccDockerService_minimalSpec(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_service.foo", "id", serviceIDRegex),
resource.TestCheckResourceAttr("docker_service.foo", "name", "tftest-service-basic"),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`127.0.0.1:15000/tftest-service:v1@sha256.*`)),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`sha256.*`)),
),
},
{
@ -639,7 +566,7 @@ func TestAccDockerService_fullSpec(t *testing.T) {
resource.TestMatchResourceAttr("docker_service.foo", "id", serviceIDRegex),
resource.TestCheckResourceAttr("docker_service.foo", "name", "tftest-service-basic"),
testCheckLabelMap("docker_service.foo", "labels", map[string]string{"servicelabel": "true"}),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`127.0.0.1:15000/tftest-service:v1.*`)),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`sha256.*`)),
testCheckLabelMap("docker_service.foo", "task_spec.0.container_spec.0.labels", map[string]string{"foo": "bar"}),
resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.command.0", "ls"),
resource.TestCheckResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.args.0", "-las"),
@ -740,7 +667,7 @@ func TestAccDockerService_partialReplicationConfig(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_service.foo", "id", serviceIDRegex),
resource.TestCheckResourceAttr("docker_service.foo", "name", "tftest-service-basic"),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`127.0.0.1:15000/tftest-service:v1@sha256.*`)),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`sha256.*`)),
resource.TestCheckResourceAttr("docker_service.foo", "mode.0.replicated.0.replicas", "1"),
),
},
@ -749,7 +676,7 @@ func TestAccDockerService_partialReplicationConfig(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_service.foo", "id", serviceIDRegex),
resource.TestCheckResourceAttr("docker_service.foo", "name", "tftest-service-basic"),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`127.0.0.1:15000/tftest-service:v1@sha256.*`)),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`sha256.*`)),
resource.TestCheckResourceAttr("docker_service.foo", "mode.0.replicated.0.replicas", "1"),
),
},
@ -758,7 +685,7 @@ func TestAccDockerService_partialReplicationConfig(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_service.foo", "id", serviceIDRegex),
resource.TestCheckResourceAttr("docker_service.foo", "name", "tftest-service-basic"),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`127.0.0.1:15000/tftest-service:v1@sha256.*`)),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`sha256.*`)),
resource.TestCheckResourceAttr("docker_service.foo", "mode.0.replicated.0.replicas", "2"),
),
},
@ -785,7 +712,7 @@ func TestAccDockerService_globalReplicationMode(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_service.foo", "id", serviceIDRegex),
resource.TestCheckResourceAttr("docker_service.foo", "name", "tftest-service-basic"),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`127.0.0.1:15000/tftest-service:v1@sha256.*`)),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`sha256.*`)),
resource.TestCheckResourceAttr("docker_service.foo", "mode.0.global", "true"),
),
},
@ -850,7 +777,7 @@ func TestAccDockerService_privateImageConverge(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_service.foo", "id", serviceIDRegex),
resource.TestCheckResourceAttr("docker_service.foo", "name", "tftest-service-foo"),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`127.0.0.1:15000/tftest-service:v1@sha256.*`)),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`sha256.*`)),
),
},
},
@ -903,7 +830,7 @@ func TestAccDockerService_convergeAndStopGracefully(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_service.foo", "id", serviceIDRegex),
resource.TestCheckResourceAttr("docker_service.foo", "name", "tftest-service-basic-converge"),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`127.0.0.1:15000/tftest-service:v1.*`)),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`sha256.*`)),
resource.TestCheckResourceAttr("docker_service.foo", "mode.0.replicated.0.replicas", "2"),
testValueHigherEqualThan("docker_service.foo", "endpoint_spec.0.ports.0.target_port", 8080),
testValueHigherEqualThan("docker_service.foo", "endpoint_spec.0.ports.0.published_port", 30000),
@ -930,7 +857,7 @@ func TestAccDockerService_updateFailsAndRollbackConverge(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_service.foo", "id", serviceIDRegex),
resource.TestCheckResourceAttr("docker_service.foo", "name", "tftest-service-updateFailsAndRollbackConverge"),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`127.0.0.1:15000/tftest-service:v1.*`)),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`sha256.*`)),
resource.TestCheckResourceAttr("docker_service.foo", "mode.0.replicated.0.replicas", "2"),
),
},
@ -940,7 +867,7 @@ func TestAccDockerService_updateFailsAndRollbackConverge(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_service.foo", "id", serviceIDRegex),
resource.TestCheckResourceAttr("docker_service.foo", "name", "tftest-service-updateFailsAndRollbackConverge"),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`127.0.0.1:15000/tftest-service:v1.*`)),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`sha256.*`)),
resource.TestCheckResourceAttr("docker_service.foo", "mode.0.replicated.0.replicas", "2"),
),
},
@ -1078,7 +1005,15 @@ func TestAccDockerService_updateMultiplePropertiesConverge(t *testing.T) {
portsSpec3 := portsSpec2
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
PreCheck: func() {
testAccPreCheck(t)
// Note mavogel: we download all images upfront and use a data_source then
// becausee the test is only flaky in CI. See
// https://github.com/kreuzwerker/terraform-provider-docker/runs/2732063570
pullImageForTest(t, image)
pullImageForTest(t, image2)
pullImageForTest(t, image3)
},
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
@ -1086,7 +1021,7 @@ func TestAccDockerService_updateMultiplePropertiesConverge(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_service.foo", "id", serviceIDRegex),
resource.TestCheckResourceAttr("docker_service.foo", "name", "tftest-fnf-service-up-crihiadr"),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`127.0.0.1:15000/tftest-service:v1@sha256.*`)),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`sha256.*`)),
resource.TestCheckResourceAttr("docker_service.foo", "mode.0.replicated.0.replicas", strconv.Itoa(replicas)),
resource.TestCheckResourceAttr("docker_service.foo", "update_config.0.parallelism", "2"),
resource.TestCheckResourceAttr("docker_service.foo", "update_config.0.delay", "3s"),
@ -1132,7 +1067,7 @@ func TestAccDockerService_updateMultiplePropertiesConverge(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_service.foo", "id", serviceIDRegex),
resource.TestCheckResourceAttr("docker_service.foo", "name", "tftest-fnf-service-up-crihiadr"),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`127.0.0.1:15000/tftest-service:v2.*`)),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`sha256.*`)),
resource.TestCheckResourceAttr("docker_service.foo", "mode.0.replicated.0.replicas", strconv.Itoa(replicas2)),
resource.TestCheckResourceAttr("docker_service.foo", "update_config.0.parallelism", "2"),
resource.TestCheckResourceAttr("docker_service.foo", "update_config.0.delay", "3s"),
@ -1180,7 +1115,7 @@ func TestAccDockerService_updateMultiplePropertiesConverge(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestMatchResourceAttr("docker_service.foo", "id", serviceIDRegex),
resource.TestCheckResourceAttr("docker_service.foo", "name", "tftest-fnf-service-up-crihiadr"),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`127.0.0.1:15000/tftest-service:v2.*`)),
resource.TestMatchResourceAttr("docker_service.foo", "task_spec.0.container_spec.0.image", regexp.MustCompile(`sha256.*`)),
resource.TestCheckResourceAttr("docker_service.foo", "mode.0.replicated.0.replicas", strconv.Itoa(replicas3)),
resource.TestCheckResourceAttr("docker_service.foo", "update_config.0.parallelism", "2"),
resource.TestCheckResourceAttr("docker_service.foo", "update_config.0.delay", "3s"),
@ -1233,6 +1168,10 @@ func TestAccDockerService_updateMultiplePropertiesConverge(t *testing.T) {
const updateMultiplePropertiesConfigConverge = `
provider "docker" {
alias = "private"
registry_auth {
address = "127.0.0.1:15000"
}
}
resource "docker_volume" "foo" {
@ -1262,19 +1201,24 @@ const updateMultiplePropertiesConfigConverge = `
create_before_destroy = true
}
}
data "docker_image" "tftest_image" {
name = "%s"
}
resource "docker_service" "foo" {
provider = "docker.private"
name = "tftest-fnf-service-up-crihiadr"
auth {
server_address = "127.0.0.1:15000"
username = "testuser"
password = "testpwd"
server_address = "127.0.0.1:15000"
username = "testuser"
password = "testpwd"
}
task_spec {
container_spec {
image = "%s"
image = data.docker_image.tftest_image.repo_digest
%s

View file

@ -27,6 +27,13 @@ The following command is the equivalent:
{{codefile "shell" "examples/resources/docker_service/resource-basic-create.sh" }}
### Basic with Datasource
Alternatively, if the image is already present on the Docker Host and not managed
by `terraform`, you can also use the `docker_image` datasource:
{{tffile "examples/resources/docker_service/resource-basic-image-ds.tf"}}
### Advanced
The following configuration shows the full capabilities of a Docker Service,

View file

@ -0,0 +1,3 @@
data "docker_image" "foo" {
name = "nginx"
}

View file

@ -0,0 +1,3 @@
data "docker_image" "foo" {
name = "nginx@sha256:36b74457bccb56fbf8b05f79c85569501b721d4db813b684391d63e02287c0b2"
}

View file

@ -0,0 +1,3 @@
data "docker_image" "foo" {
name = "nginx:1.17.6"
}

View file

@ -0,0 +1,3 @@
data "docker_image" "foo" {
name = "nginx:1.19.1@sha256:36b74457bccb56fbf8b05f79c85569501b721d4db813b684391d63e02287c0b2"
}

View file

@ -1,11 +1,11 @@
provider "docker" {
alias = "private"
registry_auth {
address = "%s"
}
alias = "private"
registry_auth {
address = "%s"
}
}
data "docker_registry_image" "foobar" {
provider = "docker.private"
name = "%s"
insecure_skip_verify = true
provider = "docker.private"
name = "%s"
insecure_skip_verify = true
}

View file

@ -1,10 +1,10 @@
resource "docker_plugin" "test" {
name = "docker.io/tiborvass/sample-volume-plugin:latest"
alias = "sample:latest"
enabled = false
force_destroy = true
force_disable = true
enable_timeout = 60
name = "docker.io/tiborvass/sample-volume-plugin:latest"
alias = "sample:latest"
enabled = false
force_destroy = true
force_disable = true
enable_timeout = 60
env = [
"DEBUG=1"
]

View file

@ -4,11 +4,16 @@ provider "docker" {
}
}
resource "docker_image" "tftest_image" {
name = "127.0.0.1:15000/tftest-service:v1"
keep_locally = false
}
resource "docker_service" "foo" {
name = "tftest-service-basic-converge"
task_spec {
container_spec {
image = "127.0.0.1:15000/tftest-service:v1"
image = docker_image.tftest_image.repo_digest
stop_grace_period = "10s"
healthcheck {
test = ["CMD", "curl", "-f", "localhost:8080/health"]

View file

@ -4,6 +4,11 @@ provider "docker" {
}
}
resource "docker_image" "tftest_image" {
name = "127.0.0.1:15000/tftest-service:v1"
keep_locally = false
}
resource "docker_volume" "test_volume" {
name = "tftest-volume"
}
@ -33,7 +38,7 @@ resource "docker_service" "foo" {
task_spec {
container_spec {
image = "127.0.0.1:15000/tftest-service:v1"
image = docker_image.tftest_image.repo_digest
labels {
label = "foo"

View file

@ -4,11 +4,16 @@ provider "docker" {
}
}
resource "docker_image" "tftest_image" {
name = "127.0.0.1:15000/tftest-service:v1"
keep_locally = false
}
resource "docker_service" "foo" {
name = "tftest-service-basic"
task_spec {
container_spec {
image = "127.0.0.1:15000/tftest-service:v1"
image = docker_image.tftest_image.repo_digest
stop_grace_period = "10s"
}
}

View file

@ -8,7 +8,7 @@ resource "docker_service" "foo" {
name = "tftest-service-basic"
task_spec {
container_spec {
image = "127.0.0.1:15000/tftest-service:v1"
image = "127.0.0.1:15000/tftest-service:v1@sha256:2ca4c7a50df3515ea96106caab374759879830f6e4d6b400cee064e2e8db08c0"
stop_grace_period = "10s"
}
}

View file

@ -4,11 +4,17 @@ provider "docker" {
}
}
resource "docker_image" "tftest_image" {
name = "127.0.0.1:15000/tftest-service:v1"
keep_locally = false
force_remove = true
}
resource "docker_service" "foo" {
name = "tftest-service-basic"
task_spec {
container_spec {
image = "127.0.0.1:15000/tftest-service:v1"
image = docker_image.tftest_image.repo_digest
stop_grace_period = "10s"
}
}

View file

@ -4,11 +4,17 @@ provider "docker" {
}
}
resource "docker_image" "tftest_image" {
name = "127.0.0.1:15000/tftest-service:v1"
keep_locally = false
force_remove = true
}
resource "docker_service" "foo" {
name = "tftest-service-basic"
task_spec {
container_spec {
image = "127.0.0.1:15000/tftest-service:v1"
image = docker_image.tftest_image.repo_digest
stop_grace_period = "10s"
}
}

View file

@ -4,11 +4,17 @@ provider "docker" {
}
}
resource "docker_image" "tftest_image" {
name = "127.0.0.1:15000/tftest-service:v1"
keep_locally = false
force_remove = true
}
resource "docker_service" "foo" {
name = "tftest-service-basic"
task_spec {
container_spec {
image = "127.0.0.1:15000/tftest-service:v1"
image = docker_image.tftest_image.repo_digest
stop_grace_period = "10s"
}
}

View file

@ -1,14 +1,29 @@
provider "docker" {
alias = "private"
registry_auth {
address = "%s"
}
}
data "docker_registry_image" "tftest_image" {
provider = "docker.private"
name = "%s"
insecure_skip_verify = true
}
resource "docker_image" "tftest_image" {
provider = "docker.private"
name = data.docker_registry_image.tftest_image.name
keep_locally = false
force_remove = true
pull_triggers = [data.docker_registry_image.tftest_image.sha256_digest]
}
resource "docker_service" "foo" {
name = "tftest-service-foo"
provider = "docker.private"
name = "tftest-service-foo"
task_spec {
container_spec {
image = "%s"
image = docker_image.tftest_image.repo_digest
stop_grace_period = "10s"
}

View file

@ -5,12 +5,25 @@ provider "docker" {
}
}
data "docker_registry_image" "tftest_image" {
provider = "docker.private"
name = "%s"
insecure_skip_verify = true
}
resource "docker_image" "tftest_image" {
provider = "docker.private"
name = data.docker_registry_image.tftest_image.name
keep_locally = false
force_remove = true
pull_triggers = [data.docker_registry_image.tftest_image.sha256_digest]
}
resource "docker_service" "foo" {
provider = "docker.private"
name = "tftest-service-updateFailsAndRollbackConverge"
task_spec {
container_spec {
image = "%s"
image = docker_image.tftest_image.repo_digest
stop_grace_period = "10s"
healthcheck {