Skip to content

Commit c404e0a

Browse files
authored
fix: replace for loops with StateChangeConf (kreuzwerker#182)
* fix: create container refresh function * fix: adapt statefunc delays and timeouts to reflect for loop * feat: add testaccDockerfile for local development * chore(ci): bump terraform versions and add 0.15.2
1 parent f8907bf commit c404e0a

File tree

4 files changed

+127
-39
lines changed

4 files changed

+127
-39
lines changed

.github/workflows/acc-test.yaml

+4-3
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ jobs:
2323
fail-fast: true
2424
matrix:
2525
terraform_version:
26-
- "0.12.30"
27-
- "0.13.6"
28-
- "0.14.8"
26+
- "0.12.31"
27+
- "0.13.7"
28+
- "0.14.11"
29+
- "0.15.2"
2930
steps:
3031
- uses: actions/checkout@v2
3132
- uses: actions/setup-go@v2

CONTRIBUTING.md

+9
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ make website-lint
8080
make website-lint-fix
8181
```
8282

83+
In case you need to run the GitHub actions setup locally in a docker container and run the tests there,
84+
run the following commands:
85+
```sh
86+
docker build -f testacc.Dockerfile -t testacc-local .
87+
docker run -it -v /var/run/docker.sock:/var/run/docker.sock -v $(pwd):/test testacc-local bash
88+
make testacc_setup
89+
TF_LOG=DEBUG TF_ACC=1 go test -v ./internal/provider -run ^TestAccDockerContainer_nostart$
90+
```
91+
8392
### Test against current terraform IaC descriptions
8493
In order to extend the provider and test it with `terraform`, build the provider as mentioned above with:
8594

internal/provider/resource_docker_container_funcs.go

+81-36
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
"encoding/base64"
99
"encoding/json"
10+
"errors"
1011
"fmt"
1112
"io/ioutil"
1213
"log"
@@ -22,9 +23,25 @@ import (
2223
"github.com/docker/go-connections/nat"
2324
"github.com/docker/go-units"
2425
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
26+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
2527
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
2628
)
2729

30+
const (
31+
containerReadRefreshTimeout = 15 * time.Second
32+
containerReadRefreshWaitBeforeRefreshes = 100 * time.Millisecond
33+
containerReadRefreshDelay = 100 * time.Millisecond
34+
)
35+
36+
var (
37+
errContainerFailedToBeCreated = errors.New("container failed to be created")
38+
errContainerFailedToBeDeleted = errors.New("container failed to be deleted")
39+
errContainerExitedImmediately = errors.New("container exited immediately")
40+
errContainerFailedToBeInRunningState = errors.New("container failed to be in running state")
41+
)
42+
43+
// NOTE mavogel: we keep this global var for tracking
44+
// the time in the create and read func
2845
var creationTime time.Time
2946

3047
func resourceDockerContainerCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
@@ -512,6 +529,7 @@ func resourceDockerContainerCreate(ctx context.Context, d *schema.ResourceData,
512529
}
513530

514531
func resourceDockerContainerRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
532+
log.Printf("[INFO] Waiting for container: '%s' to run: max '%v seconds'", d.Id(), containerReadRefreshTimeout)
515533
client := meta.(*ProviderConfig).DockerClient
516534

517535
apiContainer, err := fetchDockerContainer(ctx, d.Id(), client)
@@ -524,55 +542,40 @@ func resourceDockerContainerRead(ctx context.Context, d *schema.ResourceData, me
524542
return nil
525543
}
526544

527-
var container types.ContainerJSON
528-
529-
// TODO fix this with statefunc
530-
loops := 1 // if it hasn't just been created, don't delay
531-
if !creationTime.IsZero() {
532-
loops = 30 // with 500ms spacing, 15 seconds; ought to be plenty
545+
stateConf := &resource.StateChangeConf{
546+
Pending: []string{"pending"},
547+
Target: []string{"running"},
548+
Refresh: resourceDockerContainerReadRefreshFunc(ctx, d, meta),
549+
Timeout: containerReadRefreshTimeout,
550+
MinTimeout: containerReadRefreshWaitBeforeRefreshes,
551+
Delay: containerReadRefreshDelay,
533552
}
534-
sleepTime := 500 * time.Millisecond
535-
536-
for i := loops; i > 0; i-- {
537-
container, err = client.ContainerInspect(ctx, apiContainer.ID)
538-
if err != nil {
539-
return diag.Errorf("Error inspecting container %s: %s", apiContainer.ID, err)
540-
}
541-
542-
jsonObj, _ := json.MarshalIndent(container, "", "\t")
543-
log.Printf("[INFO] Docker container inspect: %s", jsonObj)
544-
545-
if container.State.Running ||
546-
!container.State.Running && !d.Get("must_run").(bool) {
547-
break
548-
}
549553

550-
if creationTime.IsZero() { // We didn't just create it, so don't wait around
554+
containerRaw, err := stateConf.WaitForStateContext(ctx)
555+
if err != nil {
556+
if errors.Is(err, errContainerFailedToBeCreated) {
551557
return resourceDockerContainerDelete(ctx, d, meta)
552558
}
553-
554-
finishTime, err := time.Parse(time.RFC3339, container.State.FinishedAt)
555-
if err != nil {
556-
return diag.Errorf("Container finish time could not be parsed: %s", container.State.FinishedAt)
557-
}
558-
if finishTime.After(creationTime) {
559-
// It exited immediately, so error out so dependent containers
560-
// aren't started
559+
if errors.Is(err, errContainerExitedImmediately) {
561560
if err := resourceDockerContainerDelete(ctx, d, meta); err != nil {
562561
log.Printf("[ERROR] Container %s failed to be deleted: %v", apiContainer.ID, err)
562+
return diag.FromErr(errContainerFailedToBeDeleted)
563563
}
564-
return diag.Errorf("Container %s exited after creation, error was: %s", apiContainer.ID, container.State.Error)
565564
}
566-
567-
time.Sleep(sleepTime)
565+
return diag.FromErr(err)
568566
}
569567

570-
// Handle the case of the for loop above running its course
568+
container := containerRaw.(types.ContainerJSON)
569+
jsonObj, _ := json.MarshalIndent(container, "", "\t")
570+
log.Printf("[DEBUG] Docker container inspect from stateFunc: %s", jsonObj)
571+
571572
if !container.State.Running && d.Get("must_run").(bool) {
572573
if err := resourceDockerContainerDelete(ctx, d, meta); err != nil {
573-
log.Printf("[ERROR] Container %s failed to be deleted: %v", apiContainer.ID, err)
574+
log.Printf("[ERROR] Container %s failed to be deleted: %v", container.ID, err)
575+
return err
574576
}
575-
return diag.Errorf("Container %s failed to be in running state", apiContainer.ID)
577+
log.Printf("[ERROR] Container %s failed to be in running state", container.ID)
578+
return diag.FromErr(errContainerFailedToBeInRunningState)
576579
}
577580

578581
if !container.State.Running {
@@ -704,9 +707,51 @@ func resourceDockerContainerRead(ctx context.Context, d *schema.ResourceData, me
704707
d.Set("group_add", container.HostConfig.GroupAdd)
705708
d.Set("tty", container.Config.Tty)
706709
d.Set("stdin_open", container.Config.OpenStdin)
710+
707711
return nil
708712
}
709713

714+
func resourceDockerContainerReadRefreshFunc(ctx context.Context,
715+
d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
716+
return func() (interface{}, string, error) {
717+
client := meta.(*ProviderConfig).DockerClient
718+
containerID := d.Id()
719+
720+
var container types.ContainerJSON
721+
container, err := client.ContainerInspect(ctx, containerID)
722+
if err != nil {
723+
return container, "pending", err
724+
}
725+
726+
jsonObj, _ := json.MarshalIndent(container, "", "\t")
727+
log.Printf("[DEBUG] Docker container inspect: %s", jsonObj)
728+
729+
if container.State.Running ||
730+
!container.State.Running && !d.Get("must_run").(bool) {
731+
log.Printf("[DEBUG] Container %s is running: %v", containerID, container.State.Running)
732+
// break
733+
return container, "running", nil
734+
}
735+
736+
if creationTime.IsZero() { // We didn't just create it, so don't wait around
737+
log.Printf("[DEBUG] Container %s was not created", containerID)
738+
return container, "pending", errContainerFailedToBeCreated
739+
}
740+
741+
finishTime, err := time.Parse(time.RFC3339, container.State.FinishedAt)
742+
if err != nil {
743+
log.Printf("[ERROR] Container %s finish time could not be parsed: %s", containerID, container.State.FinishedAt)
744+
return container, "pending", err
745+
}
746+
if finishTime.After(creationTime) {
747+
log.Printf("[INFO] Container %s exited immediately: started: %v - finished: %v", containerID, creationTime, finishTime)
748+
return container, "pending", errContainerExitedImmediately
749+
}
750+
751+
return container, "running", nil
752+
}
753+
}
754+
710755
func resourceDockerContainerUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
711756
attrs := []string{
712757
"restart", "max_retry_count", "cpu_shares", "memory", "cpu_set", "memory_swap",

scripts/testacc.Dockerfile

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
FROM ubuntu:20.04
2+
3+
ENV DEBIAN_FRONTEND=noninteractive
4+
ARG DOCKER_CE_VERSION="5:20.10.5~3-0~ubuntu-focal"
5+
ARG GOLANG_VERSION="1.16"
6+
ARG TERRAFORM_VERSION="0.15.2"
7+
8+
# Install the baseline
9+
RUN apt-get update && \
10+
apt-get -y install apt-transport-https ca-certificates curl gnupg-agent software-properties-common build-essential
11+
12+
# Install golang
13+
RUN curl -L https://dl.google.com/go/go${GOLANG_VERSION}.linux-amd64.tar.gz > go${GOLANG_VERSION}.linux-amd64.tar.gz && \
14+
tar xzf go${GOLANG_VERSION}.linux-amd64.tar.gz && \
15+
rm -f go${GOLANG_VERSION}.linux-amd64.tar.gz
16+
ENV GOPATH /go
17+
RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH"
18+
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
19+
20+
# Install docker
21+
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - && \
22+
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" && \
23+
apt-get update
24+
RUN apt-get -y install docker-ce=${DOCKER_CE_VERSION}
25+
26+
RUN sed 's/DOCKER_OPTS="/DOCKER_OPTS="--insecure-registry=127.0.0.1:15000 /g' -i /etc/default/docker && \
27+
cat /etc/default/docker
28+
29+
# Install terraform
30+
RUN curl -fsSL https://apt.releases.hashicorp.com/gpg | apt-key add - && \
31+
apt-add-repository "deb [arch=$(dpkg --print-architecture)] https://apt.releases.hashicorp.com $(lsb_release -cs) main" && \
32+
apt-get update
33+
RUN apt-get -y install terraform=${TERRAFORM_VERSION}

0 commit comments

Comments
 (0)