Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2

spindle/models: stream nixery image pull errors to clients

Signed-off-by: oppiliappan <me@oppi.li>

authored by oppi.li and committed by tangled.org e3285ce3 46115499

+67 -25
+16 -14
spindle/engine/engine.go
··· 30 } 31 } 32 33 var wg sync.WaitGroup 34 for eng, wfs := range pipeline.Workflows { 35 workflowTimeout := eng.WorkflowTimeout() ··· 50 Name: w.Name, 51 } 52 53 - err := db.StatusRunning(wid, n) 54 if err != nil { 55 l.Error("failed to set workflow status to running", "wid", wid, "err", err) 56 return 57 } 58 59 - err = eng.SetupWorkflow(ctx, wid, &w) 60 if err != nil { 61 // TODO(winter): Should this always set StatusFailed? 62 // In the original, we only do in a subset of cases. ··· 83 return 84 } 85 defer eng.DestroyWorkflow(ctx, wid) 86 - 87 - secretValues := make([]string, len(allSecrets)) 88 - for i, s := range allSecrets { 89 - secretValues[i] = s.Value 90 - } 91 - wfLogger, err := models.NewWorkflowLogger(cfg.Server.LogDir, wid, secretValues) 92 - if err != nil { 93 - l.Warn("failed to setup step logger; logs will not be persisted", "error", err) 94 - wfLogger = nil 95 - } else { 96 - defer wfLogger.Close() 97 - } 98 99 ctx, cancel := context.WithTimeout(ctx, workflowTimeout) 100 defer cancel()
··· 30 } 31 } 32 33 + secretValues := make([]string, len(allSecrets)) 34 + for i, s := range allSecrets { 35 + secretValues[i] = s.Value 36 + } 37 + 38 var wg sync.WaitGroup 39 for eng, wfs := range pipeline.Workflows { 40 workflowTimeout := eng.WorkflowTimeout() ··· 45 Name: w.Name, 46 } 47 48 + wfLogger, err := models.NewFileWorkflowLogger(cfg.Server.LogDir, wid, secretValues) 49 + if err != nil { 50 + l.Warn("failed to setup step logger; logs will not be persisted", "error", err) 51 + wfLogger = models.NullLogger{} 52 + } else { 53 + l.Info("setup step logger; logs will be persisted", "logDir", cfg.Server.LogDir, "wid", wid) 54 + defer wfLogger.Close() 55 + } 56 + 57 + err = db.StatusRunning(wid, n) 58 if err != nil { 59 l.Error("failed to set workflow status to running", "wid", wid, "err", err) 60 return 61 } 62 63 + err = eng.SetupWorkflow(ctx, wid, &w, wfLogger) 64 if err != nil { 65 // TODO(winter): Should this always set StatusFailed? 66 // In the original, we only do in a subset of cases. ··· 69 return 70 } 71 defer eng.DestroyWorkflow(ctx, wid) 72 73 ctx, cancel := context.WithTimeout(ctx, workflowTimeout) 74 defer cancel()
+49 -9
spindle/engines/nixery/engine.go
··· 1 package nixery 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "io" 8 "log/slog" 9 - "os" 10 "path" 11 "runtime" 12 "sync" ··· 169 return e, nil 170 } 171 172 - func (e *Engine) SetupWorkflow(ctx context.Context, wid models.WorkflowId, wf *models.Workflow) error { 173 - e.l.Info("setting up workflow", "workflow", wid) 174 175 _, err := e.docker.NetworkCreate(ctx, networkName(wid), network.CreateOptions{ 176 Driver: "bridge", 177 }) 178 if err != nil { 179 return err 180 } 181 e.registerCleanup(wid, func(ctx context.Context) error { 182 if err := e.docker.NetworkRemove(ctx, networkName(wid)); err != nil { 183 return fmt.Errorf("removing network: %w", err) ··· 198 return nil 199 }) 200 201 addl := wf.Data.(addlFields) 202 203 reader, err := e.docker.ImagePull(ctx, addl.image, image.PullOptions{}) 204 if err != nil { 205 - e.l.Error("pipeline image pull failed!", "image", addl.image, "workflowId", wid, "error", err.Error()) 206 - 207 return fmt.Errorf("pulling image: %w", err) 208 } 209 defer reader.Close() 210 - io.Copy(os.Stdout, reader) 211 212 resp, err := e.docker.ContainerCreate(ctx, &container.Config{ 213 Image: addl.image, ··· 259 ExtraHosts: []string{"host.docker.internal:host-gateway"}, 260 }, nil, nil, "") 261 if err != nil { 262 return fmt.Errorf("creating container: %w", err) 263 } 264 e.registerCleanup(wid, func(ctx context.Context) error { 265 if err := e.docker.ContainerStop(ctx, resp.ID, container.StopOptions{}); err != nil { 266 return fmt.Errorf("stopping container: %w", err) ··· 280 if err != nil { 281 return fmt.Errorf("removing container: %w", err) 282 } 283 return nil 284 }) 285 286 if err := e.docker.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { 287 return fmt.Errorf("starting container: %w", err) 288 } ··· 312 return err 313 } 314 315 execInspectResp, err := e.docker.ContainerExecInspect(ctx, mkExecResp.ID) 316 if err != nil { 317 return err ··· 330 return nil 331 } 332 333 - func (e *Engine) RunStep(ctx context.Context, wid models.WorkflowId, w *models.Workflow, idx int, secrets []secrets.UnlockedSecret, wfLogger *models.WorkflowLogger) error { 334 addl := w.Data.(addlFields) 335 workflowEnvs := ConstructEnvs(w.Environment) 336 // TODO(winter): should SetupWorkflow also have secret access? ··· 371 // start tailing logs in background 372 tailDone := make(chan error, 1) 373 go func() { 374 - tailDone <- e.tailStep(ctx, wfLogger, mkExecResp.ID, wid, idx, step) 375 }() 376 377 select { ··· 417 return nil 418 } 419 420 - func (e *Engine) tailStep(ctx context.Context, wfLogger *models.WorkflowLogger, execID string, wid models.WorkflowId, stepIdx int, step models.Step) error { 421 if wfLogger == nil { 422 return nil 423 }
··· 1 package nixery 2 3 import ( 4 + "bufio" 5 "context" 6 "errors" 7 "fmt" 8 "io" 9 "log/slog" 10 "path" 11 "runtime" 12 "sync" ··· 169 return e, nil 170 } 171 172 + func (e *Engine) SetupWorkflow(ctx context.Context, wid models.WorkflowId, wf *models.Workflow, wfLogger models.WorkflowLogger) error { 173 + /// -------------------------INITIAL SETUP------------------------------------------ 174 + l := e.l.With("workflow", wid) 175 + l.Info("setting up workflow") 176 177 + setupStep := Step{ 178 + name: "nixery image pull", 179 + kind: models.StepKindSystem, 180 + } 181 + setupStepIdx := -1 182 + 183 + wfLogger.ControlWriter(setupStepIdx, setupStep, models.StepStatusStart).Write([]byte{0}) 184 + defer wfLogger.ControlWriter(setupStepIdx, setupStep, models.StepStatusEnd).Write([]byte{0}) 185 + 186 + /// -------------------------NETWORK CREATION--------------------------------------- 187 _, err := e.docker.NetworkCreate(ctx, networkName(wid), network.CreateOptions{ 188 Driver: "bridge", 189 }) 190 if err != nil { 191 return err 192 } 193 + 194 e.registerCleanup(wid, func(ctx context.Context) error { 195 if err := e.docker.NetworkRemove(ctx, networkName(wid)); err != nil { 196 return fmt.Errorf("removing network: %w", err) ··· 185 return nil 186 }) 187 188 + /// -------------------------IMAGE PULL--------------------------------------------- 189 addl := wf.Data.(addlFields) 190 + l.Info("pulling image", "image", addl.image) 191 + fmt.Fprintf( 192 + wfLogger.DataWriter(setupStepIdx, "stdout"), 193 + "pulling image: %s", 194 + addl.image, 195 + ) 196 197 reader, err := e.docker.ImagePull(ctx, addl.image, image.PullOptions{}) 198 if err != nil { 199 + l.Error("pipeline image pull failed!", "error", err.Error()) 200 + fmt.Fprintf(wfLogger.DataWriter(setupStepIdx, "stderr"), "image pull failed: %s", err) 201 return fmt.Errorf("pulling image: %w", err) 202 } 203 defer reader.Close() 204 + 205 + scanner := bufio.NewScanner(reader) 206 + for scanner.Scan() { 207 + line := scanner.Text() 208 + wfLogger.DataWriter(setupStepIdx, "stdout").Write([]byte(line)) 209 + l.Info("image pull progress", "stdout", line) 210 + } 211 + 212 + /// -------------------------CONTAINER CREATION------------------------------------- 213 + l.Info("creating container") 214 + wfLogger.DataWriter(setupStepIdx, "stdout").Write([]byte("creating container...")) 215 216 resp, err := e.docker.ContainerCreate(ctx, &container.Config{ 217 Image: addl.image, ··· 229 ExtraHosts: []string{"host.docker.internal:host-gateway"}, 230 }, nil, nil, "") 231 if err != nil { 232 + fmt.Fprintf( 233 + wfLogger.DataWriter(setupStepIdx, "stderr"), 234 + "container creation failed: %s", 235 + err, 236 + ) 237 return fmt.Errorf("creating container: %w", err) 238 } 239 + 240 e.registerCleanup(wid, func(ctx context.Context) error { 241 if err := e.docker.ContainerStop(ctx, resp.ID, container.StopOptions{}); err != nil { 242 return fmt.Errorf("stopping container: %w", err) ··· 244 if err != nil { 245 return fmt.Errorf("removing container: %w", err) 246 } 247 + 248 return nil 249 }) 250 251 + /// -------------------------CONTAINER START---------------------------------------- 252 + wfLogger.DataWriter(setupStepIdx, "stdout").Write([]byte("starting container...")) 253 if err := e.docker.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { 254 return fmt.Errorf("starting container: %w", err) 255 } ··· 273 return err 274 } 275 276 + /// -----------------------------------FINISH--------------------------------------- 277 execInspectResp, err := e.docker.ContainerExecInspect(ctx, mkExecResp.ID) 278 if err != nil { 279 return err ··· 290 return nil 291 } 292 293 + func (e *Engine) RunStep(ctx context.Context, wid models.WorkflowId, w *models.Workflow, idx int, secrets []secrets.UnlockedSecret, wfLogger models.WorkflowLogger) error { 294 addl := w.Data.(addlFields) 295 workflowEnvs := ConstructEnvs(w.Environment) 296 // TODO(winter): should SetupWorkflow also have secret access? ··· 331 // start tailing logs in background 332 tailDone := make(chan error, 1) 333 go func() { 334 + tailDone <- e.tailStep(ctx, wfLogger, mkExecResp.ID, idx) 335 }() 336 337 select { ··· 377 return nil 378 } 379 380 + func (e *Engine) tailStep(ctx context.Context, wfLogger models.WorkflowLogger, execID string, stepIdx int) error { 381 if wfLogger == nil { 382 return nil 383 }
+2 -2
spindle/models/engine.go
··· 10 11 type Engine interface { 12 InitWorkflow(twf tangled.Pipeline_Workflow, tpl tangled.Pipeline) (*Workflow, error) 13 - SetupWorkflow(ctx context.Context, wid WorkflowId, wf *Workflow) error 14 WorkflowTimeout() time.Duration 15 DestroyWorkflow(ctx context.Context, wid WorkflowId) error 16 - RunStep(ctx context.Context, wid WorkflowId, w *Workflow, idx int, secrets []secrets.UnlockedSecret, wfLogger *WorkflowLogger) error 17 }
··· 10 11 type Engine interface { 12 InitWorkflow(twf tangled.Pipeline_Workflow, tpl tangled.Pipeline) (*Workflow, error) 13 + SetupWorkflow(ctx context.Context, wid WorkflowId, wf *Workflow, wfLogger WorkflowLogger) error 14 WorkflowTimeout() time.Duration 15 DestroyWorkflow(ctx context.Context, wid WorkflowId) error 16 + RunStep(ctx context.Context, wid WorkflowId, w *Workflow, idx int, secrets []secrets.UnlockedSecret, wfLogger WorkflowLogger) error 17 }