Deployment and lifecycle management for Nix
0
fork

Configure Feed

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

cli: support nix copy push along with attic

+143 -58
cli

This is a binary file and will not be displayed.

+141 -57
cmd/cli/builder/main.go
··· 7 7 "fmt" 8 8 "io" 9 9 "log/slog" 10 + "net/url" 10 11 "os" 11 12 "os/exec" 12 13 "strings" ··· 28 29 29 30 type inputDrv map[string][]string 30 31 31 - func Push(workers int, system string) error { 32 + // Uploader defines the interface for pushing built outputs to a remote cache 33 + type Uploader interface { 34 + Push(outputs []string) error 35 + } 36 + 37 + // AtticUploader pushes to an Attic cache 38 + type AtticUploader struct { 39 + Cache string 40 + Jobs string 41 + } 42 + 43 + // Push implements Uploader for AtticUploader 44 + func (a *AtticUploader) Push(outputs []string) error { 45 + outputStr := strings.Join(outputs, "\n") 46 + 47 + pushCmd := exec.Command("attic", "push", "--stdin", "--ignore-upstream-cache-filter", "--jobs", a.Jobs, a.Cache) 48 + stdout, err := pushCmd.StdoutPipe() 49 + if err != nil { 50 + return fmt.Errorf("error creating stdout: %v", err) 51 + } 52 + stderr, err := pushCmd.StderrPipe() 53 + if err != nil { 54 + return fmt.Errorf("error creating stderr: %v", err) 55 + } 56 + stdin, _ := pushCmd.StdinPipe() 57 + 58 + err = pushCmd.Start() 59 + if err != nil { 60 + return fmt.Errorf("failed to start command: %v", err) 61 + } 62 + 63 + var ioErr error 64 + go func() { 65 + _, ioErr = io.Copy(os.Stdout, stdout) 66 + if ioErr != nil { 67 + slog.Error("failed to configure stdout") 68 + } 69 + }() 70 + go func() { 71 + _, ioErr = io.Copy(os.Stderr, stderr) 72 + if ioErr != nil { 73 + slog.Error("failed to configure stderr") 74 + } 75 + }() 76 + 77 + _, err = stdin.Write([]byte(outputStr)) 78 + if err != nil { 79 + return fmt.Errorf("failed to send stdin to push: %v", err) 80 + } 81 + stdin.Close() 82 + 83 + err = pushCmd.Wait() 84 + if err != nil { 85 + return fmt.Errorf("failed to wait for run command: %v", err) 86 + } 87 + 88 + return nil 89 + } 90 + 91 + // NixCopyUploader pushes using nix copy 92 + type NixCopyUploader struct { 93 + Remote string 94 + } 95 + 96 + // Push implements Uploader for NixCopyUploader 97 + func (n *NixCopyUploader) Push(outputs []string) error { 98 + args := append([]string{"copy", "--to", n.Remote}, outputs...) 99 + cmd := exec.Command("nix", args...) 100 + 101 + err := commands.SimpleRun(cmd) 102 + if err != nil { 103 + return fmt.Errorf("failed to run nix copy: %v", err) 104 + } 105 + 106 + return nil 107 + } 108 + 109 + // NewUploader creates an Uploader from a target string 110 + func NewUploader(target string) (Uploader, error) { 111 + parts := strings.SplitN(target, ":", 2) 112 + if len(parts) != 2 { 113 + return nil, fmt.Errorf("invalid target format: %s (expected type:target)", target) 114 + } 115 + 116 + uploadType := parts[0] 117 + targetSpec := parts[1] 118 + 119 + switch uploadType { 120 + case "attic": 121 + // Parse cache name and optional query params 122 + cache := targetSpec 123 + jobs := "20" // default 124 + 125 + // Check if there are query params 126 + if idx := strings.Index(targetSpec, "?"); idx != -1 { 127 + cache = targetSpec[:idx] 128 + queryStr := targetSpec[idx+1:] 129 + 130 + params, err := url.ParseQuery(queryStr) 131 + if err != nil { 132 + return nil, fmt.Errorf("invalid query parameters in attic target: %v", err) 133 + } 134 + 135 + if j := params.Get("jobs"); j != "" { 136 + jobs = j 137 + } 138 + } 139 + 140 + return &AtticUploader{Cache: cache, Jobs: jobs}, nil 141 + 142 + case "nix-copy": 143 + return &NixCopyUploader{Remote: targetSpec}, nil 144 + 145 + default: 146 + return nil, fmt.Errorf("unknown upload target type %q, supported types: attic, nix-copy", uploadType) 147 + } 148 + } 149 + 150 + func Push(workers int, system string, uploadTarget string) error { 151 + uploader, err := NewUploader(uploadTarget) 152 + if err != nil { 153 + return err 154 + } 155 + 32 156 q := queue.NewPool(int64(workers)) 33 157 defer q.Release() 34 158 35 - err := evalJobs(workers, system, q, pushResult) 159 + // Create a closure that captures the uploader 160 + pushResultFunc := func(result evalResult) error { 161 + return pushResult(result, uploader) 162 + } 163 + 164 + err = evalJobs(workers, system, q, pushResultFunc) 36 165 if err != nil { 37 166 return err 38 167 } ··· 154 283 return nil 155 284 } 156 285 157 - func pushResult(result evalResult) error { 286 + func pushResult(result evalResult, uploader Uploader) error { 158 287 outputs, err := commands.Run(exec.Command("nix", "build", "--print-out-paths", fmt.Sprintf("%v^*", result.DrvPath))) 159 288 if err != nil { 160 289 return fmt.Errorf("failed to build: %v", err) 161 290 } 162 291 163 292 output_list := strings.Split(outputs, "\n") 293 + // Filter out empty strings 294 + var filteredOutputs []string 295 + for _, output := range output_list { 296 + if output != "" { 297 + filteredOutputs = append(filteredOutputs, output) 298 + } 299 + } 164 300 165 - slog.Debug("Build result", "outputs", output_list) 301 + slog.Debug("Build result", "outputs", filteredOutputs) 166 302 167 303 _ = printResult(result) 168 304 169 - attic_cache := os.Getenv("ATTIC_CACHE") 170 - if attic_cache == "" { 171 - slog.Error("Must set ATTIC_CACHE to name of pre-configured cache. e.g. myserver:mycache") 172 - os.Exit(1) 173 - } 174 - 175 - attic_jobs := os.Getenv("ATTIC_JOBS") 176 - if attic_jobs == "" { 177 - attic_jobs = "20" 178 - } 179 - 180 - pushCmd := exec.Command("attic", "push", "--stdin", "--ignore-upstream-cache-filter", "--jobs", attic_jobs, attic_cache) 181 - stdout, err := pushCmd.StdoutPipe() 182 - if err != nil { 183 - return fmt.Errorf("error creating stdout: %v", err) 184 - } 185 - stderr, err := pushCmd.StderrPipe() 186 - if err != nil { 187 - return fmt.Errorf("error creating stderr: %v", err) 188 - } 189 - stdin, _ := pushCmd.StdinPipe() 190 - 191 - err = pushCmd.Start() 192 - if err != nil { 193 - return fmt.Errorf("failed to start command: %v", err) 194 - } 195 - 196 - var ioErr error 197 - go func() { 198 - _, ioErr = io.Copy(os.Stdout, stdout) // Redirect stdout to terminal's stdout 199 - if ioErr != nil { 200 - slog.Error("failed to configure stdout") 201 - } 202 - }() 203 - go func() { 204 - _, ioErr = io.Copy(os.Stderr, stderr) // Redirect stderr to terminal's stderr 205 - if ioErr != nil { 206 - slog.Error("failed to configure stderr") 207 - } 208 - }() 209 - 210 - _, err = stdin.Write([]byte(outputs)) 211 - if err != nil { 212 - return fmt.Errorf("failed to send stdin to push: %v", err) 213 - } 214 - stdin.Close() 215 - 216 - err = pushCmd.Wait() 217 - if err != nil { 218 - return fmt.Errorf("failed to wait for run command: %v", err) 219 - } 220 - 221 - return nil 305 + return uploader.Push(filteredOutputs) 222 306 }
+2 -1
cmd/cli/main.go
··· 54 54 type builderPushCmd struct { 55 55 Workers int `arg:"--workers,-w"` 56 56 System string `arg:"--system"` 57 + Target string `arg:"--target,-t,required" help:"Upload target (attic:<cache>[?jobs=N] or nix-copy:<remote>)"` 57 58 } 58 59 59 60 type daemonCmd struct { ··· 244 245 os.Exit(1) 245 246 } 246 247 case cfg.Builder.Push != nil: 247 - err := builder.Push(cfg.Builder.Push.Workers, cfg.Builder.Push.System) 248 + err := builder.Push(cfg.Builder.Push.Workers, cfg.Builder.Push.System, cfg.Builder.Push.Target) 248 249 if err != nil { 249 250 slog.Error("Failed to eval", "error", err) 250 251 os.Exit(1)