Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

container information stauff #271

Merged
merged 16 commits into from
Jul 21, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"io"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"

"github.com/docker/docker/pkg/archive"
Expand Down Expand Up @@ -45,6 +46,7 @@ type Container interface {
StartLogProducer(context.Context) error
StopLogProducer() error
Name(context.Context) (string, error) // get container name
State(context.Context) (*types.ContainerState, error) //returns container's running state
Networks(context.Context) ([]string, error) // get container networks
NetworkAliases(context.Context) (map[string][]string, error) // get container network aliases for a network
Exec(ctx context.Context, cmd []string) (int, error)
Expand Down
19 changes: 19 additions & 0 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,16 @@ func (c *DockerContainer) Terminate(ctx context.Context) error {
return err
}

// update container raw info
func (c *DockerContainer) inspectContainerFresh(ctx context.Context) (*types.ContainerJSON, error) {
01101101M marked this conversation as resolved.
Show resolved Hide resolved
inspect, err := c.provider.client.ContainerInspect(ctx, c.ID)
if err != nil {
return nil, err
}
c.raw = &inspect
return c.raw, nil
}

func (c *DockerContainer) inspectContainer(ctx context.Context) (*types.ContainerJSON, error) {
if c.raw != nil {
return c.raw, nil
Expand Down Expand Up @@ -232,6 +242,15 @@ func (c *DockerContainer) Name(ctx context.Context) (string, error) {
return inspect.Name, nil
}

// State returns container's running state
func (c *DockerContainer) State(ctx context.Context) (*types.ContainerState, error) {
inspect, err := c.inspectContainerFresh(ctx)
if err != nil {
return c.raw.State, err
}
return inspect.State, nil
}

// Networks gets the names of the networks the container is attached to.
func (c *DockerContainer) Networks(ctx context.Context) ([]string, error) {
inspect, err := c.inspectContainer(ctx)
Expand Down
3 changes: 1 addition & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
84 changes: 84 additions & 0 deletions wait/exit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package wait

import (
"context"
"strings"
"time"
)

// Implement interface
var _ Strategy = (*ExitStrategy)(nil)

// ExitStrategy will wait until container exit
type ExitStrategy struct {
// all Strategies should have a timeout to avoid waiting infinitely
exitTimeout time.Duration

// additional properties
PollInterval time.Duration
}

//NewExitStrategy constructs with polling interval of 100 milliseconds without timeout by default
func NewExitStrategy() *ExitStrategy {
return &ExitStrategy{
PollInterval: defaultPollInterval(),
}

}

// fluent builders for each property
// since go has neither covariance nor generics, the return type must be the type of the concrete implementation
// this is true for all properties, even the "shared" ones

// WithExitTimeout can be used to change the default exit timeout
func (ws *ExitStrategy) WithExitTimeout(exitTimeout time.Duration) *ExitStrategy {
ws.exitTimeout = exitTimeout
return ws
}

// WithPollInterval can be used to override the default polling interval of 100 milliseconds
func (ws *ExitStrategy) WithPollInterval(pollInterval time.Duration) *ExitStrategy {
ws.PollInterval = pollInterval
return ws
}

// ForExit is the default construction for the fluid interface.
//
// For Example:
// wait.
// ForExit().
// WithPollInterval(1 * time.Second)
func ForExit() *ExitStrategy {
return NewExitStrategy()
}

// WaitUntilReady implements Strategy.WaitUntilReady
func (ws *ExitStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) {
// limit context to exitTimeout
if ws.exitTimeout > 0 {
var cancelContext context.CancelFunc
ctx, cancelContext = context.WithTimeout(ctx, ws.exitTimeout)
defer cancelContext()
}

for {
select {
case <-ctx.Done():
return ctx.Err()
default:
state, err := target.State(ctx)
if err != nil {
if !strings.Contains(err.Error(), "No such container") {
return err
} else {
return nil
}
}
if state.Running {
time.Sleep(ws.PollInterval)
continue
}
return nil
}
}
}
47 changes: 47 additions & 0 deletions wait/exit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package wait

import (
"context"
"io"
"testing"
"time"

"github.com/docker/docker/api/types"
"github.com/docker/go-connections/nat"
)

type exitStrategyTarget struct {
isRunning bool
err error
}

func (st exitStrategyTarget) Host(ctx context.Context) (string, error) {
return "", nil
}

func (st exitStrategyTarget) MappedPort(ctx context.Context, n nat.Port) (nat.Port, error) {
return n, nil
}

func (st exitStrategyTarget) Logs(ctx context.Context) (io.ReadCloser, error) {
return nil, nil
}

func (st exitStrategyTarget) Exec(ctx context.Context, cmd []string) (int, error) {
return 0, nil
}

func (st exitStrategyTarget) State(ctx context.Context) (*types.ContainerState, error) {
return &types.ContainerState{Running: st.isRunning}, nil
}

func TestWaitForExit(t *testing.T) {
target := exitStrategyTarget{
isRunning: false,
}
wg := NewExitStrategy().WithExitTimeout(100 * time.Millisecond)
err := wg.WaitUntilReady(context.Background(), target)
if err != nil {
t.Fatal(err)
}
}
77 changes: 77 additions & 0 deletions wait/healt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package wait
01101101M marked this conversation as resolved.
Show resolved Hide resolved

import (
"context"
"time"
)

// Implement interface
var _ Strategy = (*HealthStrategy)(nil)

// HealthStrategy will wait until the container becomes healthy
type HealthStrategy struct {
// all Strategies should have a startupTimeout to avoid waiting infinitely
startupTimeout time.Duration

// additional properties
PollInterval time.Duration
}

// NewHealthStrategy constructs with polling interval of 100 milliseconds and startup timeout of 60 seconds by default
func NewHealthStrategy() *HealthStrategy {
return &HealthStrategy{
startupTimeout: defaultStartupTimeout(),
PollInterval: defaultPollInterval(),
}

}

// fluent builders for each property
// since go has neither covariance nor generics, the return type must be the type of the concrete implementation
// this is true for all properties, even the "shared" ones like startupTimeout

// WithStartupTimeout can be used to change the default startup timeout
func (ws *HealthStrategy) WithStartupTimeout(startupTimeout time.Duration) *HealthStrategy {
ws.startupTimeout = startupTimeout
return ws
}

// WithPollInterval can be used to override the default polling interval of 100 milliseconds
func (ws *HealthStrategy) WithPollInterval(pollInterval time.Duration) *HealthStrategy {
ws.PollInterval = pollInterval
return ws
}

// ForHealthCheck is the default construction for the fluid interface.
//
// For Example:
// wait.
// ForHealthCheck().
// WithPollInterval(1 * time.Second)
func ForHealthCheck() *HealthStrategy {
return NewHealthStrategy()
}

// WaitUntilReady implements Strategy.WaitUntilReady
func (ws *HealthStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) {
// limit context to exitTimeout
ctx, cancelContext := context.WithTimeout(ctx, ws.startupTimeout)
defer cancelContext()

for {
select {
case <-ctx.Done():
return ctx.Err()
default:
state, err := target.State(ctx)
if err != nil {
return err
}
if state.Health.Status != "healthy" {
time.Sleep(ws.PollInterval)
continue
}
return nil
}
}
}
4 changes: 4 additions & 0 deletions wait/log_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"
"time"

"github.com/docker/docker/api/types"
"github.com/docker/go-connections/nat"
)

Expand All @@ -30,6 +31,9 @@ func (st noopStrategyTarget) Logs(ctx context.Context) (io.ReadCloser, error) {
func (st noopStrategyTarget) Exec(ctx context.Context, cmd []string) (int, error) {
return 0, nil
}
func (st noopStrategyTarget) State(ctx context.Context) (*types.ContainerState, error) {
return nil, nil
}

func TestWaitForLog(t *testing.T) {
target := noopStrategyTarget{
Expand Down
2 changes: 2 additions & 0 deletions wait/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"time"

"github.com/docker/docker/api/types"
"github.com/docker/go-connections/nat"
)

Expand All @@ -17,6 +18,7 @@ type StrategyTarget interface {
MappedPort(context.Context, nat.Port) (nat.Port, error)
Logs(context.Context) (io.ReadCloser, error)
Exec(ctx context.Context, cmd []string) (int, error)
State(context.Context) (*types.ContainerState, error)
}

func defaultStartupTimeout() time.Duration {
Expand Down