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
2845var creationTime time.Time
2946
3047func 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
514531func 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+
710755func 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" ,
0 commit comments