CMU Coding Bootcamp

feat: devops

thecoded.prof 2797a025 b3903c3a

verified
+410
devops/ci_success.png

This is a binary file and will not be displayed.

devops/ci_success.webp

This is a binary file and will not be displayed.

devops/cicd.pdf

This is a binary file and will not be displayed.

+396
devops/cicd.typ
··· 1 + // CI/CD Pipeline Description Template (Typst) 2 + // ------------------------------------------ 3 + // Fill in the bracketed placeholders and duplicate blocks as needed. 4 + 5 + #set page( 6 + margin: (top: 0.9in, bottom: 0.9in, left: 1.0in, right: 1.0in), 7 + ) 8 + #set text(font: "Libertinus Serif", size: 11pt) 9 + #set par(leading: 1.1em) 10 + #set heading(numbering: "1.") 11 + #set outline(indent: 1.1em) 12 + 13 + #let meta(label, value) = { 14 + table( 15 + columns: (22%, 78%), 16 + inset: (x: 6pt, y: 4pt), 17 + stroke: (paint: luma(220), thickness: 0.8pt), 18 + fill: luma(248), 19 + [*#label*], [#value], 20 + ) 21 + } 22 + 23 + #let tag(text) = { 24 + box( 25 + inset: (x: 6pt, y: 2pt), 26 + radius: 4pt, 27 + fill: luma(235), 28 + stroke: (paint: luma(210), thickness: 0.8pt), 29 + )[ `#text` ] 30 + } 31 + 32 + #let callout(title, body) = { 33 + block( 34 + inset: 10pt, 35 + radius: 6pt, 36 + fill: luma(248), 37 + stroke: (paint: luma(220), thickness: 0.8pt), 38 + )[ 39 + *#title* \ 40 + #body 41 + ] 42 + } 43 + 44 + #let gate(name, condition, on_fail) = { 45 + block[ 46 + \ 47 + == #name \ 48 + - *Pass condition:* #condition \ 49 + - *On failure:* #on_fail 50 + ] 51 + } 52 + 53 + #let stage( 54 + name, 55 + purpose: "", 56 + triggers: [], 57 + inputs: [], 58 + steps: [], 59 + outputs: [], 60 + runtime: "", 61 + runner: "", 62 + gates: [], 63 + ) = { 64 + heading(level: 2, name) 65 + 66 + if purpose != "" { 67 + [*Purpose:* #purpose] 68 + linebreak() 69 + } 70 + if runtime != "" { 71 + [*Expected runtime:* #runtime] 72 + linebreak() 73 + } 74 + if runner != "" { 75 + [*Runner/agent:* #runner] 76 + linebreak() 77 + } 78 + 79 + if triggers.len() > 0 { 80 + [*Triggers:*] 81 + for t in triggers { 82 + [- #t] 83 + } 84 + } 85 + 86 + if inputs.len() > 0 { 87 + [*Inputs:*] 88 + for i in inputs { 89 + [- #i] 90 + } 91 + } 92 + 93 + if steps.len() > 0 { 94 + [*Steps:*] 95 + for s in steps { 96 + [- #s] 97 + } 98 + } 99 + 100 + if outputs.len() > 0 { 101 + [*Outputs:*] 102 + for o in outputs { 103 + [- #o] 104 + } 105 + } 106 + 107 + if gates.len() > 0 { 108 + [*Quality gates:*] 109 + for g in gates { 110 + [- #g] 111 + } 112 + } 113 + linebreak() 114 + } 115 + 116 + #let env_table(rows) = { 117 + table( 118 + columns: (18%, 16%, 26%, 40%), 119 + inset: (x: 6pt, y: 4pt), 120 + stroke: (paint: luma(220), thickness: 0.8pt), 121 + align: (left, left, left, left), 122 + [*Environment*], [*Type*], [*Promotion rule*], [*Notes*], 123 + ..rows.flatten(), 124 + ) 125 + } 126 + 127 + #let simple_pipeline_diagram(items) = { 128 + let n = items.len() 129 + if n == 0 { return } 130 + let elements = () 131 + 132 + for (i, item) in items.enumerate() { 133 + elements.push(box( 134 + inset: 8pt, 135 + radius: 6pt, 136 + fill: luma(245), 137 + stroke: (paint: luma(210), thickness: 0.8pt), 138 + )[ *#item* ]) 139 + 140 + if i < n - 1 { 141 + elements.push(align(center + horizon)[→]) 142 + } 143 + } 144 + 145 + let box_width = 1fr 146 + let arrow_width = auto 147 + let columns = () 148 + 149 + for i in range(elements.len()) { 150 + if calc.even(i) { 151 + columns.push(box_width) 152 + } else { 153 + columns.push(arrow_width) 154 + } 155 + } 156 + 157 + grid( 158 + columns: columns, 159 + column-gutter: 8pt, 160 + align: center + horizon, 161 + ..elements 162 + ) 163 + } 164 + 165 + 166 + = CI/CD Pipeline: [PacketMix] 167 + 168 + #meta("Owner", "FreshlyBakedCake") 169 + #meta("Repository", "Patisserie (default branch: main)") 170 + #meta("CI/CD System", "Tangled Actions") 171 + 172 + #callout( 173 + "Scope", 174 + [ 175 + This document describes the CI/CD pipeline for *Patisserie/PacketMix*, including triggers, 176 + stages, environments, artifacts, quality gates, and operational procedures. 177 + ], 178 + ) 179 + 180 + #outline() 181 + 182 + = Overview 183 + 184 + == Goals 185 + - Deliver *PacketMix* safely and repeatably. 186 + - Enforce quality gates (tests, approvals). 187 + - Provide auditable deployments and fast rollback. 188 + 189 + == Non-goals 190 + - [e.g., Build system internals not covered] 191 + 192 + == High-level flow 193 + #simple_pipeline_diagram(( 194 + "Commit/PR", 195 + "Test", 196 + "Build", 197 + "Deploy", 198 + )) 199 + 200 + = Triggers & Branching 201 + 202 + == Trigger events 203 + - `push` to `[main]` 204 + - `pull_request` targeting `[main]` 205 + 206 + == Branching strategy 207 + - Primary branch: `[main]` 208 + - Release branch: `[release]` 209 + 210 + = Environments & Promotion 211 + 212 + #env_table(( 213 + ([Development], [ephemeral], [On PR], [Runs in Nixery engine]), 214 + ([Staging], [ephemeral], [On merge to main], [Runs in Nixery engine]), 215 + ([Release], [shared], [Main passes checks], [Sets code for release to machines]), 216 + )) 217 + 218 + == Promotion rules 219 + - Development → Staging: automatic on successful PR to main pipeline. 220 + - Staging → Production: automatic on main checks passing. 221 + 222 + = Secrets and Variables 223 + 224 + == Secrets 225 + - Storage: Bitwarden vault 226 + - Access model: Provisioned on machine run. 227 + 228 + == Key variables (examples) 229 + - `KEY_SSH_RELEASE`: SSH Key used for pushing to git 230 + - `KEY_SSH_REMOTE_BUILD`: SSH Key used for accessing the remote builder 231 + 232 + = Pipeline Stages 233 + 234 + #stage( 235 + "deadnix", 236 + purpose: "Check for dead nix expressions.", 237 + triggers: ("PR", "push to main"), 238 + inputs: ( 239 + "Source code", 240 + ), 241 + steps: ( 242 + "Check for unused nix bindings", 243 + ), 244 + outputs: (), 245 + runtime: "~5 seconds", 246 + runner: "linux-x64 via nixery container", 247 + gates: ( 248 + "No dead nix expressions.", 249 + ), 250 + ) 251 + 252 + #stage( 253 + "reuse", 254 + purpose: "Ensure all files are properly licensed", 255 + triggers: ("PR", "push to main"), 256 + inputs: ( 257 + "Source code", 258 + ), 259 + steps: ( 260 + "Check for REUSE compliance", 261 + ), 262 + outputs: (), 263 + runtime: "~5 seconds", 264 + runner: "linux-x64 via nixery container", 265 + gates: ( 266 + "All files are properly licensed.", 267 + ), 268 + ) 269 + 270 + #stage( 271 + "packetmix-npins-duplicate-check", 272 + purpose: "Catch duplicate npins keys.", 273 + triggers: ("PR", "push to main"), 274 + inputs: ( 275 + "Source code", 276 + ), 277 + steps: ( 278 + "Check for duplicate npins keys", 279 + ), 280 + outputs: (), 281 + runtime: "~5 seconds", 282 + runner: "linux-x64 via nixery container", 283 + gates: ( 284 + "No duplicate keys.", 285 + ), 286 + ) 287 + 288 + #stage( 289 + "packetmix-treefmt", 290 + purpose: "Check formatting of changed files.", 291 + triggers: ("PR", "push to main"), 292 + inputs: ("Source code", "KEY_SSH_REMOTE_BUILD secret"), 293 + steps: ( 294 + "Get remote builds ssh key.", 295 + "Add base system files.", 296 + "Ensure files are formatted.", 297 + ), 298 + outputs: (), 299 + runtime: "~30 seconds", 300 + runner: "linux-x64 via nixery container", 301 + gates: ("All files are formatted.",), 302 + ) 303 + 304 + #stage( 305 + "packetmix-build", 306 + purpose: "Build the project.", 307 + triggers: ("PR", "push to main"), 308 + inputs: ("Source code", "KEY_SSH_REMOTE_BUILD secret"), 309 + steps: ( 310 + "Get remote builds SSH key.", 311 + "Add base system files.", 312 + "Evaluate all systems", 313 + "Build all systems" 314 + ), 315 + outputs: ("Build artifacts",), 316 + runtime: "~1 hour", 317 + runner: "linux-x64 via nixery container", 318 + gates: ( 319 + "All systems evaluate successfully.", 320 + "All builds succeed.", 321 + ), 322 + ) 323 + 324 + #stage( 325 + "packetmix-release", 326 + purpose: "Release to systems.", 327 + triggers: ("push to main",), 328 + inputs: ("Source code", "KEY_SSH_REMOTE_BUILD secret", "KEY_SSH_REMOTE_RELEASE secret"), 329 + steps: ( 330 + "Get remote builds SSH key.", 331 + "Get release push SSH key.", 332 + "Add base system files.", 333 + "Evaluate all systems.", 334 + "Build all systems.", 335 + "Push to release", 336 + ), 337 + outputs: ("Build artifacts",), 338 + runtime: "~1 hour", 339 + runner: "linux-x64 via nixery container", 340 + gates: ( 341 + "All systems evaluate successfully.", 342 + "All builds succeed.", 343 + "Push to release succeeds.", 344 + ), 345 + ) 346 + 347 + #stage( 348 + "github", 349 + purpose: "Mirror to github.", 350 + triggers: ("push to release",), 351 + inputs: ("Source code", "KEY_SSH_GITHUB secret", "KEY_SSH_GITHUB_PACKETMIX secret", "KEY_SSH_GITHUB_SPRINKLES secret"), 352 + steps: ( 353 + "Write github SSH Key.", 354 + "Add base system files.", 355 + "Push * to GitHub.", 356 + "Write packetmix github SSH Key.", 357 + "Push packetmix to GitHub.", 358 + "Write sprinkles github SSH Key.", 359 + "Push sprinkles to GitHub." 360 + ), 361 + outputs: (), 362 + runtime: "~1 minute", 363 + runner: "linux-x64 via nixery container", 364 + gates: ("All pushes to github succeed",) 365 + ) 366 + 367 + = Quality Gates (Centralized) 368 + 369 + #gate( 370 + "Required checks on PR", 371 + [All checks succeed (see @ci_pass), Human review approved (if required).], 372 + "Block merge; require fix or approved exception.", 373 + ) 374 + 375 + = Rollback & Recovery 376 + 377 + == Rollback strategy 378 + - Primary: Force push release branch to last known good image. 379 + 380 + == When to rollback 381 + - Any system fails to start or crash loops. 382 + 383 + = Appendix 384 + 385 + == Reference links 386 + - #link("https://tangled.org/freshlybakedca.ke/patisserie/")[FreshlyBakedCake/Patisserie Repo] 387 + 388 + == Images 389 + #figure( 390 + image("ci_success.png"), 391 + caption: "CI Pipeline Success." 392 + ) <ci_pass> 393 + #figure( 394 + image("deploy_success.png", width: 125%), 395 + caption: "Deploy to machine successful." 396 + ) <deploys>
devops/deploy_success.png

This is a binary file and will not be displayed.

devops/deploy_success.webp

This is a binary file and will not be displayed.

+14
nilla.nix
··· 181 181 ]; 182 182 }; 183 183 }; 184 + shells.cicd = { 185 + systems = ["x86_64-linux"]; 186 + shell = 187 + { 188 + mkShell, 189 + pkgs 190 + }: 191 + mkShell { 192 + packages = [ 193 + pkgs.typst 194 + pkgs.tinymist 195 + ]; 196 + }; 197 + }; 184 198 }; 185 199 } 186 200 )