This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / spindle / engines / nixery / cache.go
5.3 kB 167 lines
1package nixery 2 3import ( 4 "bufio" 5 "context" 6 "fmt" 7 "io" 8 9 "github.com/docker/docker/api/types" 10 "github.com/docker/docker/api/types/container" 11 "github.com/docker/docker/pkg/stdcopy" 12 13 "tangled.org/core/spindle/engine" 14 "tangled.org/core/spindle/models" 15 "tangled.org/core/spindle/storage" 16) 17 18func (e *Engine) baseEnv() EnvVars { 19 envs := EnvVars{} 20 envs.AddEnv("HOME", homeDir) 21 envs.AddEnv("PATH", fmt.Sprintf("%s/.nix-profile/bin:/nix/var/nix/profiles/default/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", homeDir)) 22 return envs 23} 24 25func (e *Engine) execAttached(ctx context.Context, containerID string, opts container.ExecOptions) (string, types.HijackedResponse, error) { 26 execResp, err := e.docker.ContainerExecCreate(ctx, containerID, opts) 27 if err != nil { 28 return "", types.HijackedResponse{}, fmt.Errorf("create exec: %w", err) 29 } 30 attach, err := e.docker.ContainerExecAttach(ctx, execResp.ID, container.ExecAttachOptions{}) 31 if err != nil { 32 return "", types.HijackedResponse{}, fmt.Errorf("attach exec: %w", err) 33 } 34 return execResp.ID, attach, nil 35} 36 37func (e *Engine) containerID(wf *models.Workflow) (string, error) { 38 addl, ok := wf.Data.(addlFields) 39 if !ok || addl.container == "" { 40 return "", fmt.Errorf("nixery workflow has no container") 41 } 42 return addl.container, nil 43} 44 45func (e *Engine) RestoreCache(ctx context.Context, wid models.WorkflowId, wf *models.Workflow, store storage.Storage, caches []engine.ResolvedCache, wfLogger models.WorkflowLogger) error { 46 containerID, err := e.containerID(wf) 47 if err != nil { 48 return err 49 } 50 51 out := wfLogger.DataWriter(engine.CacheRestoreStepIdx, "stdout") 52 for _, entry := range caches { 53 if err := ctx.Err(); err != nil { 54 return err 55 } 56 if entry.RestoreKey == "" { 57 fmt.Fprintf(out, "cache %q: miss\n", entry.Key) 58 continue 59 } 60 if entry.RestoreName != "" { 61 fmt.Fprintf(out, "cache %q: restoring from %q\n", entry.Key, entry.RestoreName) 62 } 63 64 rc, err := store.Get(ctx, entry.RestoreKey) 65 if err != nil { 66 fmt.Fprintf(out, "cache %q: fetch failed: %v\n", entry.Key, err) 67 continue 68 } 69 70 br := bufio.NewReader(rc) 71 execID, attach, err := e.execAttached(ctx, containerID, container.ExecOptions{ 72 Cmd: []string{"bash", "-c", engine.CacheDecompressCmd(br) + " | tar -x -C /"}, 73 Env: e.baseEnv(), 74 AttachStdin: true, 75 AttachStdout: true, 76 AttachStderr: true, 77 }) 78 if err != nil { 79 rc.Close() 80 return fmt.Errorf("restore cache %q: %w", entry.Key, err) 81 } 82 83 // drain this now or tar can block on stderr before reading stdin 84 copyDone := make(chan error, 1) 85 go func() { 86 _, err := io.Copy(attach.Conn, br) 87 _ = attach.CloseWrite() 88 copyDone <- err 89 }() 90 _, _ = stdcopy.StdCopy(out, out, attach.Reader) 91 copyErr := <-copyDone 92 rc.Close() 93 attach.Close() 94 if copyErr != nil { 95 return fmt.Errorf("restore cache %q: stream archive: %w", entry.Key, copyErr) 96 } 97 98 inspect, err := e.docker.ContainerExecInspect(ctx, execID) 99 if err != nil { 100 return fmt.Errorf("restore cache %q: %w", entry.Key, err) 101 } 102 if inspect.ExitCode != 0 { 103 fmt.Fprintf(out, "cache %q: extract failed (exit %d)\n", entry.Key, inspect.ExitCode) 104 continue 105 } 106 fmt.Fprintf(out, "cache %q: restored\n", entry.Key) 107 } 108 return nil 109} 110 111func (e *Engine) SaveCache(ctx context.Context, wid models.WorkflowId, wf *models.Workflow, store storage.Storage, caches []engine.ResolvedCache, wfLogger models.WorkflowLogger) error { 112 containerID, err := e.containerID(wf) 113 if err != nil { 114 return err 115 } 116 117 out := wfLogger.DataWriter(engine.CacheSaveStepIdx, "stdout") 118 for _, entry := range caches { 119 if err := ctx.Err(); err != nil { 120 return err 121 } 122 123 script := engine.CacheSaveScript(entry.Paths, workspaceDir, entry.CompressionLevel) 124 125 execID, attach, err := e.execAttached(ctx, containerID, container.ExecOptions{ 126 Cmd: []string{"bash", "-c", script}, 127 Env: e.baseEnv(), 128 AttachStdout: true, 129 AttachStderr: true, 130 }) 131 if err != nil { 132 return fmt.Errorf("save cache %q: %w", entry.Key, err) 133 } 134 135 up := engine.NewCacheUpload(ctx, store, entry.SaveKey) 136 137 // StdCopy only returns once the archive is fully written 138 _, copyErr := stdcopy.StdCopy(up.Writer, out, attach.Reader) 139 attach.Close() 140 inspect, inspectErr := e.docker.ContainerExecInspect(ctx, execID) 141 142 switch { 143 case inspectErr == nil && inspect.ExitCode == engine.CacheExitNoPaths: 144 up.Abort(fmt.Errorf("no cache paths")) 145 fmt.Fprintf(out, "cache %q: nothing to save\n", entry.Key) 146 continue 147 case inspectErr == nil && inspect.ExitCode == engine.CacheExitNoCompressor: 148 up.Abort(fmt.Errorf("zstd not available")) 149 fmt.Fprintf(out, "cache %q: zstd not available in image; skipping\n", entry.Key) 150 continue 151 case copyErr != nil: 152 up.Abort(copyErr) 153 return fmt.Errorf("save cache %q: stream archive: %w", entry.Key, copyErr) 154 case inspectErr != nil: 155 up.Abort(inspectErr) 156 return fmt.Errorf("save cache %q: %w", entry.Key, inspectErr) 157 case inspect.ExitCode != 0: 158 up.Abort(fmt.Errorf("exited %d", inspect.ExitCode)) 159 return fmt.Errorf("save cache %q: tar exited %d", entry.Key, inspect.ExitCode) 160 } 161 if err := up.Finish(); err != nil { 162 return fmt.Errorf("save cache %q: %w", entry.Key, err) 163 } 164 fmt.Fprintf(out, "cache %q: saved\n", entry.Key) 165 } 166 return nil 167}