microsoft/openvmm
Publicmirrored fromhttps://github.com/microsoft/openvmmAvailable
Guide/src/dev_guide/dev_tools/flowey/steps.md
155lines · modecode
| 1 | # Steps |
| 2 | |
| 3 | **Steps** are units of work that will be executed at runtime. Different |
| 4 | step types exist for different purposes. |
| 5 | |
| 6 | ## Types of Steps |
| 7 | |
| 8 | ### Rust Steps |
| 9 | |
| 10 | Rust steps execute Rust code at runtime and are the most common step type in flowey. |
| 11 | |
| 12 | **`emit_rust_step`**: The primary method for emitting steps that run Rust code. Steps can claim variables, read inputs, perform work, and write outputs. Returns an optional `ReadVar<SideEffect>` that other steps can use as a dependency. |
| 13 | |
| 14 | **`emit_minor_rust_step`**: Similar to `emit_rust_step` but for steps that cannot fail (no `Result` return) and don't need visibility in CI logs. Used for simple transformations and glue logic. Using minor steps also improve performance, since there is a slight cost to starting and ending a 'step' in GitHub and ADO. During the build stage, minor steps that are adjacent to each other will get merged into one giant CI step. |
| 15 | |
| 16 | **`emit_rust_stepv`**: Convenience method that combines creating a new variable and emitting a step in one call. The step's return value is automatically written to the new variable. |
| 17 | |
| 18 | For detailed examples of Rust steps, see the [`NodeCtx` emit methods documentation](https://openvmm.dev/rustdoc/linux/flowey/node/prelude/struct.NodeCtx.html). |
| 19 | |
| 20 | ### ADO Steps |
| 21 | |
| 22 | **`emit_ado_step`**: Emits a step that generates Azure DevOps Pipeline YAML. Takes a closure that returns a YAML string snippet which is interpolated into the generated pipeline. |
| 23 | |
| 24 | For ADO step examples, see the [`NodeCtx::emit_ado_step` documentation](https://openvmm.dev/rustdoc/linux/flowey_core/node/struct.NodeCtx.html#method.emit_ado_step). |
| 25 | |
| 26 | ### GitHub Steps |
| 27 | |
| 28 | **`emit_gh_step`**: Creates a GitHub Actions step using the fluent `GhStepBuilder` API. Supports specifying the action, parameters, outputs, dependencies, and permissions. Returns a builder that must be finalized with `.finish(ctx)`. |
| 29 | |
| 30 | For GitHub step examples, see the [`GhStepBuilder` documentation](https://openvmm.dev/rustdoc/linux/flowey_core/node/steps/github/struct.GhStepBuilder.html). |
| 31 | |
| 32 | ### Side Effect Steps |
| 33 | |
| 34 | **`emit_side_effect_step`**: Creates a dependency relationship without executing code. Useful for aggregating multiple side effect dependencies into a single side effect. More efficient than emitting an empty Rust step. |
| 35 | |
| 36 | For side effect step examples, see the [`NodeCtx::emit_side_effect_step` documentation](https://openvmm.dev/rustdoc/linux/flowey_core/node/struct.NodeCtx.html#method.emit_side_effect_step). |
| 37 | |
| 38 | ### Isolated Working Directories and Path Immutability |
| 39 | |
| 40 | ```admonish warning title="Critical Constraint" |
| 41 | **Each step gets its own fresh local working directory.** This avoids the "single global working directory dumping ground" common in bash + YAML systems. |
| 42 | |
| 43 | However, while flowey variables enforce sharing XOR mutability at the type-system level, **developers must manually enforce this at the filesystem level**: |
| 44 | |
| 45 | **Steps must NEVER modify the contents of paths referenced by `ReadVar<PathBuf>`.** |
| 46 | ``` |
| 47 | |
| 48 | When you write a path to `WriteVar<PathBuf>`, you're creating an immutable contract. Other steps reading that path must treat it as read-only. If you need to modify files from a `ReadVar<PathBuf>`, copy them to your step's working directory. |
| 49 | |
| 50 | ## Runtime Services |
| 51 | |
| 52 | Runtime services provide the API available during step execution (inside the |
| 53 | closures passed to `emit_rust_step`, etc.). |
| 54 | |
| 55 | ### RustRuntimeServices |
| 56 | |
| 57 | [`RustRuntimeServices`](https://openvmm.dev/rustdoc/linux/flowey_core/node/steps/rust/struct.RustRuntimeServices.html) is the primary runtime service available in Rust steps. It provides: |
| 58 | |
| 59 | #### Variable Operations |
| 60 | |
| 61 | - Reading and writing flowey variables |
| 62 | - Secret handling (automatic secret propagation for safety) |
| 63 | - Support for reading values of any type that implements [`ReadVarValue`](https://openvmm.dev/rustdoc/linux/flowey_core/node/trait.ReadVarValue.html) |
| 64 | |
| 65 | #### Environment Queries |
| 66 | |
| 67 | - Backend identification (Local, ADO, or GitHub) |
| 68 | - Platform detection (Windows, Linux, macOS) |
| 69 | - Architecture information (x86_64, Aarch64) |
| 70 | |
| 71 | ### AdoStepServices |
| 72 | |
| 73 | [`AdoStepServices`](https://openvmm.dev/rustdoc/linux/flowey_core/node/steps/ado/struct.AdoStepServices.html) provides integration with Azure DevOps-specific features when emitting ADO YAML steps: |
| 74 | |
| 75 | **ADO Variable Bridge:** |
| 76 | |
| 77 | - Convert ADO runtime variables (like `BUILD.SOURCEBRANCH`) into flowey vars |
| 78 | - Convert flowey vars back into ADO variables for use in YAML |
| 79 | - Handle secret variables appropriately |
| 80 | |
| 81 | **Repository Resources:** |
| 82 | |
| 83 | - Resolve repository IDs declared as pipeline resources |
| 84 | - Access repository information in ADO-specific steps |
| 85 | |
| 86 | ### GhStepBuilder |
| 87 | |
| 88 | [`GhStepBuilder`](https://openvmm.dev/rustdoc/linux/flowey_core/node/steps/github/struct.GhStepBuilder.html) is a fluent builder for constructing GitHub Actions steps with: |
| 89 | |
| 90 | **Step Configuration:** |
| 91 | |
| 92 | - Specifying the action to use (e.g., `actions/checkout@v4`) |
| 93 | - Adding input parameters via `.with()` |
| 94 | - Capturing step outputs into flowey variables |
| 95 | - Setting conditional execution based on variables |
| 96 | |
| 97 | **Dependency Management:** |
| 98 | |
| 99 | - Declaring side-effect dependencies via `.run_after()` |
| 100 | - Ensuring steps run in the correct order |
| 101 | |
| 102 | **Permissions:** |
| 103 | |
| 104 | - Declaring required GITHUB_TOKEN permissions |
| 105 | - Automatic permission aggregation at the job level |
| 106 | |
| 107 | ## Secret Variables and CI Backend Integration |
| 108 | |
| 109 | Flowey provides built-in support for handling sensitive data like API keys, tokens, and credentials through **secret variables**. Secret variables are treated specially to prevent accidental exposure in logs and CI outputs. |
| 110 | |
| 111 | ### How Secret Handling Works |
| 112 | |
| 113 | When a variable is marked as secret, flowey ensures: |
| 114 | |
| 115 | - The value is not logged or printed in step output |
| 116 | - CI backends (ADO, GitHub Actions) are instructed to mask the value in their logs |
| 117 | - Secret status is automatically propagated to prevent leaks |
| 118 | |
| 119 | ### Automatic Secret Propagation |
| 120 | |
| 121 | To prevent accidental leaks, flowey uses conservative automatic secret propagation: |
| 122 | |
| 123 | ```admonish warning |
| 124 | If a step reads a secret value, **all subsequent writes from that step are automatically marked as secret** by default. This prevents accidentally leaking secrets through derived values. |
| 125 | ``` |
| 126 | |
| 127 | For example: |
| 128 | |
| 129 | ```rust,ignore |
| 130 | ctx.emit_rust_step("process token", |ctx| { |
| 131 | let secret_token = secret_token.claim(ctx); |
| 132 | let output_var = output_var.claim(ctx); |
| 133 | |rt| { |
| 134 | let token = rt.read(secret_token); // Reading a secret |
| 135 | |
| 136 | // This write is AUTOMATICALLY marked as secret |
| 137 | // (even though we're just writing "done") |
| 138 | rt.write(output_var, &"done".to_string()); |
| 139 | |
| 140 | Ok(()) |
| 141 | } |
| 142 | }); |
| 143 | ``` |
| 144 | |
| 145 | If you need to write non-secret data after reading a secret, use `write_not_secret()`: |
| 146 | |
| 147 | ```rust,ignore |
| 148 | rt.write_not_secret(output_var, &"done".to_string()); |
| 149 | ``` |
| 150 | |
| 151 | ### Best Practices for Secrets |
| 152 | |
| 153 | 1. **Never use `ReadVar::from_static()` for secrets** - static values are encoded in plain text in the generated YAML |
| 154 | 2. **Always use `write_secret()`** when writing sensitive data like tokens, passwords, or keys |
| 155 | 3. **Minimize secret lifetime** - read secrets as late as possible and don't pass them through more variables than necessary |
| 156 | |