7
7
"context"
8
8
"encoding/base64"
9
9
"encoding/json"
10
+ "errors"
10
11
"fmt"
11
12
"io/ioutil"
12
13
"log"
@@ -22,9 +23,25 @@ import (
22
23
"github.com/docker/go-connections/nat"
23
24
"github.com/docker/go-units"
24
25
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
26
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
25
27
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
26
28
)
27
29
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
28
45
var creationTime time.Time
29
46
30
47
func resourceDockerContainerCreate (ctx context.Context , d * schema.ResourceData , meta interface {}) diag.Diagnostics {
@@ -512,6 +529,7 @@ func resourceDockerContainerCreate(ctx context.Context, d *schema.ResourceData,
512
529
}
513
530
514
531
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 )
515
533
client := meta .(* ProviderConfig ).DockerClient
516
534
517
535
apiContainer , err := fetchDockerContainer (ctx , d .Id (), client )
@@ -524,55 +542,40 @@ func resourceDockerContainerRead(ctx context.Context, d *schema.ResourceData, me
524
542
return nil
525
543
}
526
544
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 ,
533
552
}
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
- }
549
553
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 ) {
551
557
return resourceDockerContainerDelete (ctx , d , meta )
552
558
}
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 ) {
561
560
if err := resourceDockerContainerDelete (ctx , d , meta ); err != nil {
562
561
log .Printf ("[ERROR] Container %s failed to be deleted: %v" , apiContainer .ID , err )
562
+ return diag .FromErr (errContainerFailedToBeDeleted )
563
563
}
564
- return diag .Errorf ("Container %s exited after creation, error was: %s" , apiContainer .ID , container .State .Error )
565
564
}
566
-
567
- time .Sleep (sleepTime )
565
+ return diag .FromErr (err )
568
566
}
569
567
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
+
571
572
if ! container .State .Running && d .Get ("must_run" ).(bool ) {
572
573
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
574
576
}
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 )
576
579
}
577
580
578
581
if ! container .State .Running {
@@ -704,9 +707,51 @@ func resourceDockerContainerRead(ctx context.Context, d *schema.ResourceData, me
704
707
d .Set ("group_add" , container .HostConfig .GroupAdd )
705
708
d .Set ("tty" , container .Config .Tty )
706
709
d .Set ("stdin_open" , container .Config .OpenStdin )
710
+
707
711
return nil
708
712
}
709
713
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
+
710
755
func resourceDockerContainerUpdate (ctx context.Context , d * schema.ResourceData , meta interface {}) diag.Diagnostics {
711
756
attrs := []string {
712
757
"restart" , "max_retry_count" , "cpu_shares" , "memory" , "cpu_set" , "memory_swap" ,
0 commit comments