From 81211d13400157216e9bcbc731e63496f0faf6f1 Mon Sep 17 00:00:00 2001 From: Ian Fijolek Date: Thu, 26 Sep 2024 11:48:11 -0700 Subject: [PATCH] Attach to exec jobs and print their output Still no logging or means of detecting failed attempts --- itest/docker-compose.yml | 2 +- itest/itest.sh | 9 +++++++ main.go | 58 +++++++++++++++++++++++++++++++--------- main_test.go | 20 +++++++++++--- 4 files changed, 71 insertions(+), 18 deletions(-) diff --git a/itest/docker-compose.yml b/itest/docker-compose.yml index 29b6311..e96b252 100644 --- a/itest/docker-compose.yml +++ b/itest/docker-compose.yml @@ -27,4 +27,4 @@ services: labels: # Execute every minute - 'dockron.test.schedule=* * * * *' - - 'dockron.test.command=echo ok >> /result.txt' + - 'dockron.test.command=echo ok | tee /result.txt && echo "Yay!"' diff --git a/itest/itest.sh b/itest/itest.sh index c8bb4c3..3d2caa0 100755 --- a/itest/itest.sh +++ b/itest/itest.sh @@ -30,9 +30,18 @@ function main() { echo "Stopping containers" docker compose stop + # Print logs + docker compose logs + # Validate result shows minimum amount of executions check_results ./start_result.txt 2 check_results ./exec_result.txt 1 + + # Check for exec output + if ! (docker compose logs | grep -q "Yay!"); then + echo "Exec output 'Yay!' not found" + exit 1 + fi } main diff --git a/main.go b/main.go index 2eac505..fb6ad6f 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "flag" "fmt" "os" @@ -29,11 +30,12 @@ var ( version = "dev" ) -// ContainerClient provides an interface for interracting with Docker +// ContainerClient provides an interface for interracting with Docker. Makes it possible to mock in tests type ContainerClient interface { ContainerExecCreate(ctx context.Context, container string, config container.ExecOptions) (dockerTypes.IDResponse, error) ContainerExecInspect(ctx context.Context, execID string) (container.ExecInspect, error) ContainerExecStart(ctx context.Context, execID string, config container.ExecStartOptions) error + ContainerExecAttach(ctx context.Context, execID string, options container.ExecAttachOptions) (dockerTypes.HijackedResponse, error) ContainerInspect(ctx context.Context, containerID string) (dockerTypes.ContainerJSON, error) ContainerList(context context.Context, options container.ListOptions) ([]dockerTypes.Container, error) ContainerStart(context context.Context, containerID string, options container.StartOptions) error @@ -71,7 +73,7 @@ func (job ContainerStartJob) Run() { slog.OnErrPanicf(err, "Could not get container details for job %s", job.name) if containerJSON.State.Running { - slog.Warningf("Container is already running. Skipping %s", job.name) + slog.Warningf("%s: Container is already running. Skipping start.", job.name) return } @@ -86,7 +88,7 @@ func (job ContainerStartJob) Run() { // Check results of job for check := true; check; check = containerJSON.State.Running { - slog.Debugf("Still running %s", job.name) + slog.Debugf("%s: Still running", job.name) containerJSON, err = job.client.ContainerInspect( job.context, @@ -96,12 +98,12 @@ func (job ContainerStartJob) Run() { time.Sleep(1 * time.Second) } - slog.Debugf("Done execing %s. %+v", job.name, containerJSON.State) + slog.Debugf("%s: Done running. %+v", job.name, containerJSON.State) // Log exit code if failed if containerJSON.State.ExitCode != 0 { slog.Errorf( - "Exec job %s exited with code %d", + "%s: Exec job exited with code %d", job.name, containerJSON.State.ExitCode, ) @@ -142,7 +144,7 @@ func (job ContainerExecJob) Run() { slog.OnErrPanicf(err, "Could not get container details for job %s", job.name) if !containerJSON.State.Running { - slog.Warningf("Container not running. Skipping %s", job.name) + slog.Warningf("%s: Container not running. Skipping exec.", job.name) return } @@ -151,11 +153,19 @@ func (job ContainerExecJob) Run() { job.context, job.containerID, container.ExecOptions{ - Cmd: []string{"sh", "-c", strings.TrimSpace(job.shellCommand)}, + AttachStdout: true, + AttachStderr: true, + Cmd: []string{"sh", "-c", strings.TrimSpace(job.shellCommand)}, }, ) slog.OnErrPanicf(err, "Could not create container exec job for %s", job.name) + hj, err := job.client.ContainerExecAttach(job.context, execID.ID, container.ExecAttachOptions{}) + slog.OnErrWarnf(err, "%s: Error attaching to exec: %s", job.name, err) + defer hj.Close() + + scanner := bufio.NewScanner(hj.Reader) + err = job.client.ContainerExecStart( job.context, execID.ID, @@ -166,26 +176,48 @@ func (job ContainerExecJob) Run() { // Wait for job results execInfo := container.ExecInspect{Running: true} for execInfo.Running { + time.Sleep(1 * time.Second) + slog.Debugf("Still execing %s", job.name) execInfo, err = job.client.ContainerExecInspect( job.context, execID.ID, ) - slog.Debugf("Exec info: %+v", execInfo) + + // Maybe print output + if hj.Reader != nil { + slog.Debugf("%s: Getting exec reader", job.name) + for scanner.Scan() { + slog.Debugf("%s: Getting exec line", job.name) + + line := scanner.Text() + if len(line) > 0 { + slog.Infof("%s: Exec output: %s", job.name, line) + } else { + slog.Debugf("%s: Empty exec output", job.name) + } + + if err := scanner.Err(); err != nil { + slog.OnErrWarnf(err, "%s: Error reading from exec", job.name) + } + } + } else { + slog.Debugf("%s: No exec reader", job.name) + } + + slog.Debugf("%s: Exec info: %+v", job.name, execInfo) if err != nil { // Nothing we can do if we got an error here, so let's go - slog.OnErrWarnf(err, "Could not get status for exec job %s", job.name) + slog.OnErrWarnf(err, "%s: Could not get status for exec job", job.name) return } - - time.Sleep(1 * time.Second) } - slog.Debugf("Done execing %s. %+v", job.name, execInfo) + slog.Debugf("%s: Done execing. %+v", job.name, execInfo) // Log exit code if failed if execInfo.ExitCode != 0 { - slog.Errorf("Exec job %s existed with code %d", job.name, execInfo.ExitCode) + slog.Errorf("%s: Exec job existed with code %d", job.name, execInfo.ExitCode) } } diff --git a/main_test.go b/main_test.go index bf23ba2..9246bdb 100644 --- a/main_test.go +++ b/main_test.go @@ -144,6 +144,10 @@ func (fakeClient *FakeDockerClient) ContainerInspect(ctx context.Context, contai return } +func (fakeClient *FakeDockerClient) ContainerExecAttach(ctx context.Context, execID string, options container.ExecAttachOptions) (dockerTypes.HijackedResponse, error) { + return dockerTypes.HijackedResponse{}, nil +} + // NewFakeDockerClient creates an empty client func NewFakeDockerClient() *FakeDockerClient { return &FakeDockerClient{ @@ -730,7 +734,9 @@ func TestRunExecJobs(t *testing.T) { jobContext, jobContainerID, container.ExecOptions{ - Cmd: []string{"sh", "-c", jobCommand}, + AttachStdout: true, + AttachStderr: true, + Cmd: []string{"sh", "-c", jobCommand}, }, }, }, @@ -761,7 +767,9 @@ func TestRunExecJobs(t *testing.T) { jobContext, jobContainerID, container.ExecOptions{ - Cmd: []string{"sh", "-c", jobCommand}, + AttachStdout: true, + AttachStderr: true, + Cmd: []string{"sh", "-c", jobCommand}, }, }, }, @@ -798,7 +806,9 @@ func TestRunExecJobs(t *testing.T) { jobContext, jobContainerID, container.ExecOptions{ - Cmd: []string{"sh", "-c", jobCommand}, + AttachStdout: true, + AttachStderr: true, + Cmd: []string{"sh", "-c", jobCommand}, }, }, }, @@ -838,7 +848,9 @@ func TestRunExecJobs(t *testing.T) { jobContext, jobContainerID, container.ExecOptions{ - Cmd: []string{"sh", "-c", jobCommand}, + AttachStdout: true, + AttachStderr: true, + Cmd: []string{"sh", "-c", jobCommand}, }, }, },