Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.6.0] - 2025-01-23
### Added
- Ability to restart containers

## [0.5.4] - 2024-09-02
### Update
- Bump `docker/docker` to `v27.2.0`
Expand Down
6 changes: 6 additions & 0 deletions acceptance/fixtures/proxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,21 @@ import (
"net/http/httputil"
"net/url"
"os"
"time"
)

// This is a dummy reverse proxy to illustrate how our services can access the acceptance tester
func main() {
port := os.Getenv("PROXY_PORT")
targetURLRaw := os.Getenv("PROXY_TARGETURL")
startedAt := time.Now()

mux := http.NewServeMux()
mux.HandleFunc("/status", func(rw http.ResponseWriter, _ *http.Request) {
_, err := rw.Write([]byte(fmt.Sprintf("{\"started_at\": %v}", startedAt.UnixMilli())))
if err != nil {
return
}
rw.WriteHeader(http.StatusOK)
})

Expand Down
42 changes: 42 additions & 0 deletions acceptance/suite/acceptance_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package suite

import (
"context"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"testing"
Expand Down Expand Up @@ -71,6 +73,22 @@ func (s *acceptanceSuite) TestProxyCall() {
s.Require().Equal(expectedMockedDependencyInventedHTTPStatusCode, resp.StatusCode)
}

func (s *acceptanceSuite) TestProxyCallAfterRestart() {
startTimeBefore, err := s.getStartTime()
s.Require().NoError(err)

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s.aceptadora.Restart(ctx, "proxy")

var startTimeAfter int64
s.Require().Eventually(func() bool {
startTimeAfter, err = s.getStartTime()
return err == nil
}, time.Minute, 50*time.Millisecond, "could not get start time after restart")
s.Require().Greater(startTimeAfter, startTimeBefore, "proxy didn't restart")
}

func (s *acceptanceSuite) TearDownSuite() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
Expand All @@ -95,6 +113,30 @@ func (s *acceptanceSuite) startMockedProxyDependency() {
}()
}

func (s *acceptanceSuite) getStartTime() (int64, error) {
url := fmt.Sprintf("http://%s:8888/status", s.cfg.ServicesAddress)
resp, err := http.Get(url)
if err != nil {
return 0, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return 0, fmt.Errorf("status endpoint returned %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return 0, err
}
statusResponse := struct {
StartedAt int64 `json:"started_at"`
}{}
if err := json.Unmarshal(body, &statusResponse); err != nil {
return 0, err
}
return statusResponse.StartedAt, nil
}

func TestAcceptanceSuite(t *testing.T) {
suite.Run(t, new(acceptanceSuite))
}
Expand Down
12 changes: 12 additions & 0 deletions aceptadora.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ type Config struct {
// StopTimeout will be used to stop containers gracefully.
// If zero (default), then containers will be forced to stop immediately saving some tear down time.
StopTimeout time.Duration `default:"0s"`

// RestartTimeout will be used to restart containers gracefully.
RestartTimeout time.Duration `default:"10s"`
}

type Aceptadora struct {
Expand Down Expand Up @@ -80,6 +83,15 @@ func (a *Aceptadora) Run(ctx context.Context, name string) {
a.order = append(a.order, name)
}

// Restart will restart the service with the name provided
func (a *Aceptadora) Restart(ctx context.Context, name string) {
svc, ok := a.services[name]
if !ok {
a.t.Fatalf("There's no service %q to restart", name)
}
svc.RestartWithTimeout(ctx, a.cfg.RestartTimeout)
}

// StopAll will stop all the services in the reverse order
// If you need to explicitly stop some service in first place, use Stop() previously.
func (a *Aceptadora) StopAll(ctx context.Context) {
Expand Down
15 changes: 15 additions & 0 deletions runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,21 @@ func (r *Runner) attachAndStreamLogs(ctx context.Context) {
r.logsStreamDoneCh = r.streamLogs(r.response)
}

// RestartWithTimeout will restart the container within the context provided and the timeout given.
func (r *Runner) RestartWithTimeout(ctx context.Context, timeout time.Duration) {
if r.client == nil {
// nothing to restart
return
}

timeoutSeconds := int(timeout.Seconds())
stopOpts := container.StopOptions{Timeout: &timeoutSeconds}
if err := r.client.ContainerRestart(ctx, r.container.ID, stopOpts); err != nil {
r.t.Errorf("Error restarting container %s: %v", r.container.ID, err)
}
r.t.Logf("Container %q restarted with ID %q", r.name, r.container.ID)
}

// Stop will try to stop the container within the context provided.
func (r *Runner) Stop(ctx context.Context) error {
return r.stop(ctx, nil)
Expand Down
Loading