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