-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathstatus.go
More file actions
274 lines (220 loc) · 6.82 KB
/
status.go
File metadata and controls
274 lines (220 loc) · 6.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
package docker
import (
"context"
"fmt"
"time"
"github.com/docker/docker/api/types/container"
)
// Status represents the container status information.
type Status struct {
// ID is the container ID
ID string
// Name is the container name
Name string
// Image is the container image
Image string
// State is the container state (running, exited, etc.)
State string
// Status is the detailed status string
Status string
// Running indicates if the container is running
Running bool
// Paused indicates if the container is paused
Paused bool
// Restarting indicates if the container is restarting
Restarting bool
// ExitCode is the exit code (only valid if container has exited)
ExitCode int
// Error contains any error message from the container
Error string
// StartedAt is when the container started
StartedAt time.Time
// FinishedAt is when the container finished (only if stopped)
FinishedAt time.Time
// Health is the health check status (if configured)
Health *HealthStatus
}
// HealthStatus represents container health check status.
type HealthStatus struct {
// Status is the health status (healthy, unhealthy, starting)
Status string
// FailingStreak is the number of consecutive failures
FailingStreak int
// Log contains recent health check results
Log []HealthLog
}
// HealthLog represents a single health check result.
type HealthLog struct {
// Start is when the check started
Start time.Time
// End is when the check completed
End time.Time
// ExitCode is the health check exit code
ExitCode int
// Output is the health check output
Output string
}
// Status retrieves the current container status.
func (e *Executor) Status(ctx context.Context) (*Status, error) {
e.mu.RLock()
containerID := e.containerID
e.mu.RUnlock()
if containerID == "" {
return nil, fmt.Errorf("container not started")
}
inspect, err := e.client.ContainerInspect(ctx, containerID)
if err != nil {
return nil, fmt.Errorf("failed to inspect container: %w", err)
}
// State holds the machine-readable container state (e.g., "running", "exited").
// Status holds the same value; Docker's inspect API does not expose a separate
// human-readable status string beyond inspect.State.Status.
status := &Status{
ID: inspect.ID,
Name: inspect.Name,
Image: inspect.Config.Image,
State: inspect.State.Status,
Status: inspect.State.Status,
Running: inspect.State.Running,
Paused: inspect.State.Paused,
Restarting: inspect.State.Restarting,
ExitCode: inspect.State.ExitCode,
Error: inspect.State.Error,
}
// Parse timestamps
if startedAt, err := time.Parse(time.RFC3339Nano, inspect.State.StartedAt); err == nil {
status.StartedAt = startedAt
}
if finishedAt, err := time.Parse(time.RFC3339Nano, inspect.State.FinishedAt); err == nil {
status.FinishedAt = finishedAt
}
// Parse health status
if inspect.State.Health != nil {
status.Health = &HealthStatus{
Status: inspect.State.Health.Status,
FailingStreak: inspect.State.Health.FailingStreak,
Log: make([]HealthLog, len(inspect.State.Health.Log)),
}
for i, log := range inspect.State.Health.Log {
status.Health.Log[i] = HealthLog{
Start: log.Start,
End: log.End,
ExitCode: log.ExitCode,
Output: log.Output,
}
}
}
return status, nil
}
// IsRunning checks if the container is currently running.
func (e *Executor) IsRunning(ctx context.Context) (bool, error) {
status, err := e.Status(ctx)
if err != nil {
return false, err
}
return status.Running, nil
}
// ExitCode retrieves the container exit code.
// Returns an error if the container hasn't exited yet.
func (e *Executor) ExitCode(ctx context.Context) (int, error) {
status, err := e.Status(ctx)
if err != nil {
return 0, err
}
if status.Running {
return 0, fmt.Errorf("container is still running")
}
return status.ExitCode, nil
}
// Inspect returns the full container inspection details.
// This provides access to all container metadata.
func (e *Executor) Inspect(ctx context.Context) (*container.InspectResponse, error) {
e.mu.RLock()
containerID := e.containerID
e.mu.RUnlock()
if containerID == "" {
return nil, fmt.Errorf("container not started")
}
inspect, err := e.client.ContainerInspect(ctx, containerID)
if err != nil {
return nil, fmt.Errorf("failed to inspect container: %w", err)
}
return &inspect, nil
}
// HealthCheck retrieves the current health status.
// Returns an error if health check is not configured for the container.
func (e *Executor) HealthCheck(ctx context.Context) (*HealthStatus, error) {
status, err := e.Status(ctx)
if err != nil {
return nil, err
}
if status.Health == nil {
return nil, fmt.Errorf("health check not configured for this container")
}
return status.Health, nil
}
// WaitForState waits for the container to reach a specific state.
// Valid states: running, paused, restarting, removing, exited, dead
func (e *Executor) WaitForState(ctx context.Context, targetState string, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return fmt.Errorf("timeout waiting for state %s", targetState)
case <-ticker.C:
status, err := e.Status(ctx)
if err != nil {
return fmt.Errorf("failed to get status: %w", err)
}
if status.State == targetState {
return nil
}
}
}
}
// WaitForHealthy waits for the container to become healthy.
// Returns an error if the container doesn't have health checks configured.
func (e *Executor) WaitForHealthy(ctx context.Context, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return fmt.Errorf("timeout waiting for container to be healthy")
case <-ticker.C:
health, err := e.HealthCheck(ctx)
if err != nil {
return err
}
if health.Status == "healthy" {
return nil
}
if health.Status == "unhealthy" {
return fmt.Errorf("container became unhealthy")
}
}
}
}
// GetStats retrieves container resource usage statistics.
// This includes CPU, memory, network, and disk I/O stats.
// Returns the stats response reader which can be read and decoded by the caller.
// Remember to close the response body after reading.
func (e *Executor) GetStats(ctx context.Context) (container.StatsResponseReader, error) {
e.mu.RLock()
containerID := e.containerID
e.mu.RUnlock()
var emptyStats container.StatsResponseReader
if containerID == "" {
return emptyStats, fmt.Errorf("container not started")
}
stats, err := e.client.ContainerStats(ctx, containerID, false)
if err != nil {
return emptyStats, fmt.Errorf("failed to get stats: %w", err)
}
return stats, nil
}