microsoft/openvmm

Public

mirrored from https://github.com/microsoft/openvmmAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
release/2411

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

flowey/flowey_core/src/pipeline.rs

1354lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Core types and traits used to create and work with flowey pipelines.
5
6use self::internal::*;
7use crate::node::steps::ado::AdoResourcesRepositoryId;
8use crate::node::user_facing::AdoRuntimeVar;
9use crate::node::user_facing::GhContextVar;
10use crate::node::user_facing::GhPermission;
11use crate::node::user_facing::GhPermissionValue;
12use crate::node::FlowArch;
13use crate::node::FlowNodeBase;
14use crate::node::FlowPlatform;
15use crate::node::FlowPlatformLinuxDistro;
16use crate::node::IntoRequest;
17use crate::node::NodeHandle;
18use crate::node::ReadVar;
19use crate::node::WriteVar;
20use crate::patch::PatchResolver;
21use crate::patch::ResolvedPatches;
22use serde::de::DeserializeOwned;
23use serde::Serialize;
24use std::collections::BTreeMap;
25use std::collections::BTreeSet;
26use std::path::PathBuf;
27
28/// Pipeline types which are considered "user facing", and included in the
29/// `flowey` prelude.
30pub mod user_facing {
31 pub use super::AdoCiTriggers;
32 pub use super::AdoPrTriggers;
33 pub use super::AdoResourcesRepository;
34 pub use super::AdoResourcesRepositoryRef;
35 pub use super::AdoResourcesRepositoryType;
36 pub use super::AdoScheduleTriggers;
37 pub use super::GhCiTriggers;
38 pub use super::GhPrTriggers;
39 pub use super::GhRunner;
40 pub use super::GhRunnerOsLabel;
41 pub use super::GhScheduleTriggers;
42 pub use super::HostExt;
43 pub use super::IntoPipeline;
44 pub use super::ParameterKind;
45 pub use super::Pipeline;
46 pub use super::PipelineBackendHint;
47 pub use super::PipelineJob;
48 pub use super::PipelineJobCtx;
49 pub use super::PipelineJobHandle;
50 pub use super::PublishArtifact;
51 pub use super::UseArtifact;
52 pub use super::UseParameter;
53 pub use crate::node::FlowArch;
54 pub use crate::node::FlowPlatform;
55}
56
57fn linux_distro() -> FlowPlatformLinuxDistro {
58 if let Ok(etc_os_release) = fs_err::read_to_string("/etc/os-release") {
59 if etc_os_release.contains("ID=ubuntu") {
60 FlowPlatformLinuxDistro::Ubuntu
61 } else if etc_os_release.contains("ID=fedora") {
62 FlowPlatformLinuxDistro::Fedora
63 } else {
64 FlowPlatformLinuxDistro::Unknown
65 }
66 } else {
67 FlowPlatformLinuxDistro::Unknown
68 }
69}
70
71pub trait HostExt: Sized {
72 /// Return the value for the current host machine.
73 ///
74 /// Will panic on non-local backends.
75 fn host(backend_hint: PipelineBackendHint) -> Self;
76}
77
78impl HostExt for FlowPlatform {
79 /// Return the platform of the current host machine.
80 ///
81 /// Will panic on non-local backends.
82 fn host(backend_hint: PipelineBackendHint) -> Self {
83 if !matches!(backend_hint, PipelineBackendHint::Local) {
84 panic!("can only use `FlowPlatform::host` when defining a local-only pipeline");
85 }
86
87 if cfg!(target_os = "windows") {
88 Self::Windows
89 } else if cfg!(target_os = "linux") {
90 Self::Linux(linux_distro())
91 } else if cfg!(target_os = "macos") {
92 Self::MacOs
93 } else {
94 panic!("no valid host-os")
95 }
96 }
97}
98
99impl HostExt for FlowArch {
100 /// Return the arch of the current host machine.
101 ///
102 /// Will panic on non-local backends.
103 fn host(backend_hint: PipelineBackendHint) -> Self {
104 if !matches!(backend_hint, PipelineBackendHint::Local) {
105 panic!("can only use `FlowArch::host` when defining a local-only pipeline");
106 }
107
108 // xtask-fmt allow-target-arch oneoff-flowey
109 if cfg!(target_arch = "x86_64") {
110 Self::X86_64
111 // xtask-fmt allow-target-arch oneoff-flowey
112 } else if cfg!(target_arch = "aarch64") {
113 Self::Aarch64
114 } else {
115 panic!("no valid host-arch")
116 }
117 }
118}
119
120/// Trigger ADO pipelines via Continuous Integration
121#[derive(Default, Debug)]
122pub struct AdoScheduleTriggers {
123 /// Friendly name for the scheduled run
124 pub display_name: String,
125 /// Run the pipeline whenever there is a commit on these specified branches
126 /// (supports glob syntax)
127 pub branches: Vec<String>,
128 /// Specify any branches which should be filtered out from the list of
129 /// `branches` (supports glob syntax)
130 pub exclude_branches: Vec<String>,
131 /// Run the pipeline in a schedule, as specified by a cron string
132 pub cron: String,
133}
134
135/// Trigger ADO pipelines per PR
136#[derive(Debug)]
137pub struct AdoPrTriggers {
138 /// Run the pipeline whenever there is a PR to these specified branches
139 /// (supports glob syntax)
140 pub branches: Vec<String>,
141 /// Specify any branches which should be filtered out from the list of
142 /// `branches` (supports glob syntax)
143 pub exclude_branches: Vec<String>,
144 /// Run the pipeline even if the PR is a draft PR. Defaults to `false`.
145 pub run_on_draft: bool,
146 /// Automatically cancel the pipeline run if a new commit lands in the
147 /// branch. Defaults to `true`.
148 pub auto_cancel: bool,
149}
150
151/// Trigger ADO pipelines per PR
152#[derive(Debug, Default)]
153pub struct AdoCiTriggers {
154 /// Run the pipeline whenever there is a PR to these specified branches
155 /// (supports glob syntax)
156 pub branches: Vec<String>,
157 /// Specify any branches which should be filtered out from the list of
158 /// `branches` (supports glob syntax)
159 pub exclude_branches: Vec<String>,
160 /// Whether to batch changes per branch.
161 pub batch: bool,
162}
163
164impl Default for AdoPrTriggers {
165 fn default() -> Self {
166 Self {
167 branches: Vec::new(),
168 exclude_branches: Vec::new(),
169 run_on_draft: false,
170 auto_cancel: true,
171 }
172 }
173}
174
175/// ADO repository resource.
176#[derive(Debug)]
177pub struct AdoResourcesRepository {
178 /// Type of repo that is being connected to.
179 pub repo_type: AdoResourcesRepositoryType,
180 /// Repository name. Format depends on `repo_type`.
181 pub name: String,
182 /// git ref to checkout.
183 pub git_ref: AdoResourcesRepositoryRef,
184 /// (optional) ID of the service endpoint connecting to this repository.
185 pub endpoint: Option<String>,
186}
187
188/// ADO repository resource type
189#[derive(Debug)]
190pub enum AdoResourcesRepositoryType {
191 /// Azure Repos Git repository
192 AzureReposGit,
193 /// Github repository
194 GitHub,
195}
196
197/// ADO repository ref
198#[derive(Debug)]
199pub enum AdoResourcesRepositoryRef<P = UseParameter<String>> {
200 /// Hard-coded ref (e.g: refs/heads/main)
201 Fixed(String),
202 /// Connected to pipeline-level parameter
203 Parameter(P),
204}
205
206/// Trigger Github Actions pipelines via Continuous Integration
207///
208/// NOTE: Github Actions doesn't support specifying the branch when triggered by `schedule`.
209/// To run on a specific branch, modify the branch checked out in the pipeline.
210#[derive(Default, Debug)]
211pub struct GhScheduleTriggers {
212 /// Run the pipeline in a schedule, as specified by a cron string
213 pub cron: String,
214}
215
216/// Trigger Github Actions pipelines per PR
217#[derive(Debug)]
218pub struct GhPrTriggers {
219 /// Run the pipeline whenever there is a PR to these specified branches
220 /// (supports glob syntax)
221 pub branches: Vec<String>,
222 /// Specify any branches which should be filtered out from the list of
223 /// `branches` (supports glob syntax)
224 pub exclude_branches: Vec<String>,
225 /// Run the pipeline even if the PR is a draft PR. Defaults to `false`.
226 pub run_on_draft: bool,
227 /// Automatically cancel the pipeline run if a new commit lands in the
228 /// branch. Defaults to `true`.
229 pub auto_cancel: bool,
230}
231
232/// Trigger Github Actions pipelines per PR
233#[derive(Debug, Default)]
234pub struct GhCiTriggers {
235 /// Run the pipeline whenever there is a PR to these specified branches
236 /// (supports glob syntax)
237 pub branches: Vec<String>,
238 /// Specify any branches which should be filtered out from the list of
239 /// `branches` (supports glob syntax)
240 pub exclude_branches: Vec<String>,
241 /// Run the pipeline whenever there is a PR to these specified tags
242 /// (supports glob syntax)
243 pub tags: Vec<String>,
244 /// Specify any tags which should be filtered out from the list of `tags`
245 /// (supports glob syntax)
246 pub exclude_tags: Vec<String>,
247}
248
249impl Default for GhPrTriggers {
250 fn default() -> Self {
251 Self {
252 branches: Vec::new(),
253 exclude_branches: Vec::new(),
254 run_on_draft: false,
255 auto_cancel: true,
256 }
257 }
258}
259
260#[derive(Debug, Clone, PartialEq)]
261pub enum GhRunnerOsLabel {
262 UbuntuLatest,
263 Ubuntu2204,
264 Ubuntu2004,
265 WindowsLatest,
266 Windows2022,
267 Windows2019,
268 MacOsLatest,
269 MacOs14,
270 MacOs13,
271 MacOs12,
272 MacOs11,
273 Custom(String),
274}
275
276/// GitHub runner type
277#[derive(Debug, Clone, PartialEq)]
278pub enum GhRunner {
279 // See <https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#choosing-github-hosted-runners>
280 // for more details.
281 GhHosted(GhRunnerOsLabel),
282 // Self hosted runners are selected by matching runner labels to <labels>.
283 // 'self-hosted' is a common label for self hosted runners, but is not required.
284 // Labels are case-insensitive and can take the form of arbitrary strings.
285 // See <https://docs.github.com/en/actions/hosting-your-own-runners> for more details.
286 SelfHosted(Vec<String>),
287 // This uses a runner belonging to <group> that matches all labels in <labels>.
288 // See <https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#choosing-github-hosted-runners>
289 // for more details.
290 RunnerGroup { group: String, labels: Vec<String> },
291}
292
293/// Parameter type (unstable / stable).
294#[derive(Debug, Clone)]
295pub enum ParameterKind {
296 // The parameter is considered an unstable API, and should not be
297 // taken as a dependency.
298 Unstable,
299 // The parameter is considered a stable API, and can be used by
300 // external pipelines to control behavior of the pipeline.
301 Stable,
302}
303
304#[derive(Clone, Debug)]
305#[must_use]
306pub struct UseParameter<T> {
307 idx: usize,
308 _kind: std::marker::PhantomData<T>,
309}
310
311/// Opaque handle to an artifact which must be published by a single job.
312#[must_use]
313pub struct PublishArtifact {
314 idx: usize,
315}
316
317/// Opaque handle to an artifact which can be used by one or more jobs.
318#[derive(Clone)]
319#[must_use]
320pub struct UseArtifact {
321 idx: usize,
322}
323
324#[derive(Default)]
325pub struct Pipeline {
326 jobs: Vec<PipelineJobMetadata>,
327 artifacts: Vec<ArtifactMeta>,
328 parameters: Vec<ParameterMeta>,
329 extra_deps: BTreeSet<(usize, usize)>,
330 // builder internal
331 artifact_names: BTreeSet<String>,
332 dummy_done_idx: usize,
333 global_patchfns: Vec<crate::patch::PatchFn>,
334 inject_all_jobs_with: Option<Box<dyn for<'a> Fn(PipelineJob<'a>) -> PipelineJob<'a>>>,
335 // backend specific
336 ado_name: Option<String>,
337 ado_schedule_triggers: Vec<AdoScheduleTriggers>,
338 ado_ci_triggers: Option<AdoCiTriggers>,
339 ado_pr_triggers: Option<AdoPrTriggers>,
340 ado_resources_repository: Vec<InternalAdoResourcesRepository>,
341 ado_bootstrap_template: String,
342 ado_variables: BTreeMap<String, String>,
343 ado_post_process_yaml_cb: Option<Box<dyn FnOnce(serde_yaml::Value) -> serde_yaml::Value>>,
344 gh_name: Option<String>,
345 gh_schedule_triggers: Vec<GhScheduleTriggers>,
346 gh_ci_triggers: Option<GhCiTriggers>,
347 gh_pr_triggers: Option<GhPrTriggers>,
348 gh_bootstrap_template: String,
349}
350
351impl Pipeline {
352 pub fn new() -> Pipeline {
353 Pipeline::default()
354 }
355
356 /// Inject all pipeline jobs with some common logic. (e.g: to resolve common
357 /// configuration requirements shared by all jobs).
358 ///
359 /// Can only be invoked once per pipeline.
360 #[track_caller]
361 pub fn inject_all_jobs_with(
362 &mut self,
363 cb: impl for<'a> Fn(PipelineJob<'a>) -> PipelineJob<'a> + 'static,
364 ) -> &mut Self {
365 if self.inject_all_jobs_with.is_some() {
366 panic!("can only call inject_all_jobs_with once!")
367 }
368 self.inject_all_jobs_with = Some(Box::new(cb));
369 self
370 }
371
372 /// (ADO only) Provide a YAML template used to bootstrap flowey at the start
373 /// of an ADO pipeline.
374 ///
375 /// The template has access to the following vars, which will be statically
376 /// interpolated into the template's text:
377 ///
378 /// - `{{FLOWEY_OUTDIR}}`
379 /// - Directory to copy artifacts into.
380 /// - NOTE: this var will include `\` on Windows, and `/` on linux!
381 /// - `{{FLOWEY_BIN_EXTENSION}}`
382 /// - Extension of the expected flowey bin (either "", or ".exe")
383 /// - `{{FLOWEY_CRATE}}`
384 /// - Name of the project-specific flowey crate to be built
385 /// - `{{FLOWEY_TARGET}}`
386 /// - The target-triple flowey is being built for
387 /// - `{{FLOWEY_PIPELINE_PATH}}`
388 /// - Repo-root relative path to the pipeline (as provided when
389 /// generating the pipeline via the flowey CLI)
390 ///
391 /// The template's sole responsibility is to copy 3 files into the
392 /// `{{FLOWEY_OUTDIR}}`:
393 ///
394 /// 1. The bootstrapped flowey binary, with the file name
395 /// `flowey{{FLOWEY_BIN_EXTENSION}}`
396 /// 2. Two files called `pipeline.yaml` and `pipeline.json`, which are
397 /// copied of the pipeline YAML and pipeline JSON currently being run.
398 /// `{{FLOWEY_PIPELINE_PATH}}` is provided as a way to disambiguate in
399 /// cases where the same template is being for multiple pipelines (e.g: a
400 /// debug vs. release pipeline).
401 pub fn ado_set_flowey_bootstrap_template(&mut self, template: String) -> &mut Self {
402 self.ado_bootstrap_template = template;
403 self
404 }
405
406 /// (ADO only) Provide a callback function which will be used to
407 /// post-process any YAML flowey generates for the pipeline.
408 ///
409 /// Until flowey defines a stable API for maintaining out-of-tree backends,
410 /// this method can be used to integrate the output from the generic ADO
411 /// backend with any organization-specific templates that one may be
412 /// required to use (e.g: for compliance reasons).
413 pub fn ado_post_process_yaml(
414 &mut self,
415 cb: impl FnOnce(serde_yaml::Value) -> serde_yaml::Value + 'static,
416 ) -> &mut Self {
417 self.ado_post_process_yaml_cb = Some(Box::new(cb));
418 self
419 }
420
421 /// (ADO only) Add a new scheduled CI trigger. Can be called multiple times
422 /// to set up multiple schedules runs.
423 pub fn ado_add_schedule_trigger(&mut self, triggers: AdoScheduleTriggers) -> &mut Self {
424 self.ado_schedule_triggers.push(triggers);
425 self
426 }
427
428 /// (ADO only) Set a PR trigger. Calling this method multiple times will
429 /// overwrite any previously set triggers.
430 pub fn ado_set_pr_triggers(&mut self, triggers: AdoPrTriggers) -> &mut Self {
431 self.ado_pr_triggers = Some(triggers);
432 self
433 }
434
435 /// (ADO only) Set a CI trigger. Calling this method multiple times will
436 /// overwrite any previously set triggers.
437 pub fn ado_set_ci_triggers(&mut self, triggers: AdoCiTriggers) -> &mut Self {
438 self.ado_ci_triggers = Some(triggers);
439 self
440 }
441
442 /// (ADO only) Declare a new repository resource, returning a type-safe
443 /// handle which downstream ADO steps are able to consume via
444 /// [`AdoStepServices::resolve_repository_id`](crate::node::user_facing::AdoStepServices::resolve_repository_id).
445 pub fn ado_add_resources_repository(
446 &mut self,
447 repo: AdoResourcesRepository,
448 ) -> AdoResourcesRepositoryId {
449 let AdoResourcesRepository {
450 repo_type,
451 name,
452 git_ref,
453 endpoint,
454 } = repo;
455
456 let repo_id = format!("repo{}", self.ado_resources_repository.len());
457
458 self.ado_resources_repository
459 .push(InternalAdoResourcesRepository {
460 repo_id: repo_id.clone(),
461 repo_type,
462 name,
463 git_ref: match git_ref {
464 AdoResourcesRepositoryRef::Fixed(s) => AdoResourcesRepositoryRef::Fixed(s),
465 AdoResourcesRepositoryRef::Parameter(p) => {
466 AdoResourcesRepositoryRef::Parameter(p.idx)
467 }
468 },
469 endpoint,
470 });
471 AdoResourcesRepositoryId { repo_id }
472 }
473
474 /// (GitHub Actions only) Set the pipeline-level name.
475 ///
476 /// <https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#name>
477 pub fn gh_set_name(&mut self, name: impl AsRef<str>) -> &mut Self {
478 self.gh_name = Some(name.as_ref().into());
479 self
480 }
481
482 /// Provide a YAML template used to bootstrap flowey at the start of an GitHub
483 /// pipeline.
484 ///
485 /// The template has access to the following vars, which will be statically
486 /// interpolated into the template's text:
487 ///
488 /// - `{{FLOWEY_OUTDIR}}`
489 /// - Directory to copy artifacts into.
490 /// - NOTE: this var will include `\` on Windows, and `/` on linux!
491 /// - `{{FLOWEY_BIN_EXTENSION}}`
492 /// - Extension of the expected flowey bin (either "", or ".exe")
493 /// - `{{FLOWEY_CRATE}}`
494 /// - Name of the project-specific flowey crate to be built
495 /// - `{{FLOWEY_TARGET}}`
496 /// - The target-triple flowey is being built for
497 /// - `{{FLOWEY_PIPELINE_PATH}}`
498 /// - Repo-root relative path to the pipeline (as provided when
499 /// generating the pipeline via the flowey CLI)
500 ///
501 /// The template's sole responsibility is to copy 3 files into the
502 /// `{{FLOWEY_OUTDIR}}`:
503 ///
504 /// 1. The bootstrapped flowey binary, with the file name
505 /// `flowey{{FLOWEY_BIN_EXTENSION}}`
506 /// 2. Two files called `pipeline.yaml` and `pipeline.json`, which are
507 /// copied of the pipeline YAML and pipeline JSON currently being run.
508 /// `{{FLOWEY_PIPELINE_PATH}}` is provided as a way to disambiguate in
509 /// cases where the same template is being for multiple pipelines (e.g: a
510 /// debug vs. release pipeline).
511 pub fn gh_set_flowey_bootstrap_template(&mut self, template: String) -> &mut Self {
512 self.gh_bootstrap_template = template;
513 self
514 }
515
516 /// (GitHub Actions only) Add a new scheduled CI trigger. Can be called multiple times
517 /// to set up multiple schedules runs.
518 pub fn gh_add_schedule_trigger(&mut self, triggers: GhScheduleTriggers) -> &mut Self {
519 self.gh_schedule_triggers.push(triggers);
520 self
521 }
522
523 /// (GitHub Actions only) Set a PR trigger. Calling this method multiple times will
524 /// overwrite any previously set triggers.
525 pub fn gh_set_pr_triggers(&mut self, triggers: GhPrTriggers) -> &mut Self {
526 self.gh_pr_triggers = Some(triggers);
527 self
528 }
529
530 /// (GitHub Actions only) Set a CI trigger. Calling this method multiple times will
531 /// overwrite any previously set triggers.
532 pub fn gh_set_ci_triggers(&mut self, triggers: GhCiTriggers) -> &mut Self {
533 self.gh_ci_triggers = Some(triggers);
534 self
535 }
536
537 /// (GitHub Actions only) Use a pre-defined GitHub Actions secret variable.
538 ///
539 /// For more information on defining secrets for use in GitHub Actions, see
540 /// <https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions>
541 pub fn gh_use_secret(&mut self, secret_name: impl AsRef<str>) -> GhContextVar {
542 GhContextVar::from_secrets(secret_name)
543 }
544
545 pub fn new_job(
546 &mut self,
547 platform: FlowPlatform,
548 arch: FlowArch,
549 label: impl AsRef<str>,
550 ) -> PipelineJob<'_> {
551 let idx = self.jobs.len();
552 self.jobs.push(PipelineJobMetadata {
553 root_nodes: BTreeMap::new(),
554 patches: ResolvedPatches::build(),
555 label: label.as_ref().into(),
556 platform,
557 arch,
558 cond_param_idx: None,
559 ado_pool: None,
560 ado_variables: BTreeMap::new(),
561 gh_override_if: None,
562 gh_global_env: BTreeMap::new(),
563 gh_pool: None,
564 gh_permissions: BTreeMap::new(),
565 });
566
567 PipelineJob {
568 pipeline: self,
569 job_idx: idx,
570 }
571 }
572
573 /// Declare a dependency between two jobs that does is not a result of an
574 /// artifact.
575 pub fn non_artifact_dep(
576 &mut self,
577 job: &PipelineJobHandle,
578 depends_on_job: &PipelineJobHandle,
579 ) -> &mut Self {
580 self.extra_deps
581 .insert((depends_on_job.job_idx, job.job_idx));
582 self
583 }
584
585 #[track_caller]
586 pub fn new_artifact(&mut self, name: impl AsRef<str>) -> (PublishArtifact, UseArtifact) {
587 let name = name.as_ref();
588 let owned_name = name.to_string();
589
590 let not_exists = self.artifact_names.insert(owned_name.clone());
591 if !not_exists {
592 panic!("duplicate artifact name: {}", name)
593 }
594
595 let idx = self.artifacts.len();
596 self.artifacts.push(ArtifactMeta {
597 name: owned_name,
598 published_by_job: None,
599 used_by_jobs: BTreeSet::new(),
600 });
601
602 (PublishArtifact { idx }, UseArtifact { idx })
603 }
604
605 /// (ADO only) Set the pipeline-level name.
606 ///
607 /// <https://learn.microsoft.com/en-us/azure/devops/pipelines/process/run-number?view=azure-devops&tabs=yaml>
608 pub fn ado_add_name(&mut self, name: String) -> &mut Self {
609 self.ado_name = Some(name);
610 self
611 }
612
613 /// (ADO only) Declare a pipeline-level, named, read-only ADO variable.
614 ///
615 /// `name` and `value` are both arbitrary strings.
616 ///
617 /// Returns an instance of [`AdoRuntimeVar`], which, if need be, can be
618 /// converted into a [`ReadVar<String>`] using
619 /// [`NodeCtx::get_ado_variable`].
620 ///
621 /// NOTE: Unless required by some particular third-party task, it's strongly
622 /// recommended to _avoid_ using this method, and to simply use
623 /// [`ReadVar::from_static`] to get a obtain a static variable.
624 ///
625 /// [`NodeCtx::get_ado_variable`]: crate::node::NodeCtx::get_ado_variable
626 pub fn ado_new_named_variable(
627 &mut self,
628 name: impl AsRef<str>,
629 value: impl AsRef<str>,
630 ) -> AdoRuntimeVar {
631 let name = name.as_ref();
632 let value = value.as_ref();
633
634 self.ado_variables.insert(name.into(), value.into());
635
636 // safe, since we'll ensure that the global exists in the ADO backend
637 AdoRuntimeVar::dangerous_from_global(name, false)
638 }
639
640 /// (ADO only) Declare multiple pipeline-level, named, read-only ADO
641 /// variables at once.
642 ///
643 /// This is a convenience method to streamline invoking
644 /// [`Self::ado_new_named_variable`] multiple times.
645 ///
646 /// NOTE: Unless required by some particular third-party task, it's strongly
647 /// recommended to _avoid_ using this method, and to simply use
648 /// [`ReadVar::from_static`] to get a obtain a static variable.
649 ///
650 /// DEVNOTE: In the future, this API may be updated to return a handle that
651 /// will allow resolving the resulting `AdoRuntimeVar`, but for
652 /// implementation expediency, this API does not currently do this. If you
653 /// need to read the value of this variable at runtime, you may need to
654 /// invoke [`AdoRuntimeVar::dangerous_from_global`] manually.
655 ///
656 /// [`NodeCtx::get_ado_variable`]: crate::node::NodeCtx::get_ado_variable
657 pub fn ado_new_named_variables<K, V>(
658 &mut self,
659 vars: impl IntoIterator<Item = (K, V)>,
660 ) -> &mut Self
661 where
662 K: AsRef<str>,
663 V: AsRef<str>,
664 {
665 self.ado_variables.extend(
666 vars.into_iter()
667 .map(|(k, v)| (k.as_ref().into(), v.as_ref().into())),
668 );
669 self
670 }
671
672 /// Declare a pipeline-level runtime parameter with type `bool`.
673 ///
674 /// To obtain a [`ReadVar<bool>`] that can be used within a node, use the
675 /// [`PipelineJobCtx::use_parameter`] method.
676 ///
677 /// `name` is the name of the parameter.
678 ///
679 /// `description` is an arbitrary string, which will be be shown to users.
680 ///
681 /// `kind` is the type of parameter and if it should be treated as a stable
682 /// external API to callers of the pipeline.
683 ///
684 /// `default` is the default value for the parameter. If none is provided,
685 /// the parameter _must_ be specified in order for the pipeline to run.
686 ///
687 /// `possible_values` can be used to limit the set of valid values the
688 /// parameter accepts.
689 pub fn new_parameter_bool(
690 &mut self,
691 name: impl AsRef<str>,
692 description: impl AsRef<str>,
693 kind: ParameterKind,
694 default: Option<bool>,
695 ) -> UseParameter<bool> {
696 let idx = self.parameters.len();
697 let name = new_parameter_name(name, kind.clone());
698 self.parameters.push(ParameterMeta {
699 parameter: Parameter::Bool {
700 name,
701 description: description.as_ref().into(),
702 kind,
703 default,
704 },
705 used_by_jobs: BTreeSet::new(),
706 });
707
708 UseParameter {
709 idx,
710 _kind: std::marker::PhantomData,
711 }
712 }
713
714 /// Declare a pipeline-level runtime parameter with type `i64`.
715 ///
716 /// To obtain a [`ReadVar<i64>`] that can be used within a node, use the
717 /// [`PipelineJobCtx::use_parameter`] method.
718 ///
719 /// `name` is the name of the parameter.
720 ///
721 /// `description` is an arbitrary string, which will be be shown to users.
722 ///
723 /// `kind` is the type of parameter and if it should be treated as a stable
724 /// external API to callers of the pipeline.
725 ///
726 /// `default` is the default value for the parameter. If none is provided,
727 /// the parameter _must_ be specified in order for the pipeline to run.
728 ///
729 /// `possible_values` can be used to limit the set of valid values the
730 /// parameter accepts.
731 pub fn new_parameter_num(
732 &mut self,
733 name: impl AsRef<str>,
734 description: impl AsRef<str>,
735 kind: ParameterKind,
736 default: Option<i64>,
737 possible_values: Option<Vec<i64>>,
738 ) -> UseParameter<i64> {
739 let idx = self.parameters.len();
740 let name = new_parameter_name(name, kind.clone());
741 self.parameters.push(ParameterMeta {
742 parameter: Parameter::Num {
743 name,
744 description: description.as_ref().into(),
745 kind,
746 default,
747 possible_values,
748 },
749 used_by_jobs: BTreeSet::new(),
750 });
751
752 UseParameter {
753 idx,
754 _kind: std::marker::PhantomData,
755 }
756 }
757
758 /// Declare a pipeline-level runtime parameter with type `String`.
759 ///
760 /// To obtain a [`ReadVar<String>`] that can be used within a node, use the
761 /// [`PipelineJobCtx::use_parameter`] method.
762 ///
763 /// `name` is the name of the parameter.
764 ///
765 /// `description` is an arbitrary string, which will be be shown to users.
766 ///
767 /// `kind` is the type of parameter and if it should be treated as a stable
768 /// external API to callers of the pipeline.
769 ///
770 /// `default` is the default value for the parameter. If none is provided,
771 /// the parameter _must_ be specified in order for the pipeline to run.
772 ///
773 /// `possible_values` allows restricting inputs to a set of possible values.
774 /// Depending on the backend, these options may be presented as a set of
775 /// radio buttons, a dropdown menu, or something in that vein. If `None`,
776 /// then any string is allowed.
777 pub fn new_parameter_string(
778 &mut self,
779 name: impl AsRef<str>,
780 description: impl AsRef<str>,
781 kind: ParameterKind,
782 default: Option<impl AsRef<str>>,
783 possible_values: Option<Vec<String>>,
784 ) -> UseParameter<String> {
785 let idx = self.parameters.len();
786 let name = new_parameter_name(name, kind.clone());
787 self.parameters.push(ParameterMeta {
788 parameter: Parameter::String {
789 name,
790 description: description.as_ref().into(),
791 kind,
792 default: default.map(|x| x.as_ref().into()),
793 possible_values,
794 },
795 used_by_jobs: BTreeSet::new(),
796 });
797
798 UseParameter {
799 idx,
800 _kind: std::marker::PhantomData,
801 }
802 }
803}
804
805pub struct PipelineJobCtx<'a> {
806 pipeline: &'a mut Pipeline,
807 job_idx: usize,
808}
809
810impl PipelineJobCtx<'_> {
811 /// Create a new `WriteVar<SideEffect>` anchored to the pipeline job.
812 pub fn new_done_handle(&mut self) -> WriteVar<crate::node::SideEffect> {
813 self.pipeline.dummy_done_idx += 1;
814 crate::node::thin_air_write_runtime_var(
815 format!("start{}", self.pipeline.dummy_done_idx),
816 false,
817 )
818 }
819
820 /// Claim that this job will use this artifact, obtaining a path to a folder
821 /// with the artifact's contents.
822 pub fn use_artifact(&mut self, artifact: &UseArtifact) -> ReadVar<PathBuf> {
823 self.pipeline.artifacts[artifact.idx]
824 .used_by_jobs
825 .insert(self.job_idx);
826
827 crate::node::thin_air_read_runtime_var(
828 consistent_artifact_runtime_var_name(&self.pipeline.artifacts[artifact.idx].name, true),
829 false,
830 )
831 }
832
833 /// Claim that this job will publish this artifact, obtaining a path to a
834 /// fresh, empty folder which will be published as the specific artifact at
835 /// the end of the job.
836 pub fn publish_artifact(&mut self, artifact: PublishArtifact) -> ReadVar<PathBuf> {
837 let existing = self.pipeline.artifacts[artifact.idx]
838 .published_by_job
839 .replace(self.job_idx);
840 assert!(existing.is_none()); // PublishArtifact isn't cloneable
841
842 crate::node::thin_air_read_runtime_var(
843 consistent_artifact_runtime_var_name(
844 &self.pipeline.artifacts[artifact.idx].name,
845 false,
846 ),
847 false,
848 )
849 }
850
851 /// Obtain a `ReadVar<T>` corresponding to a pipeline parameter which is
852 /// specified at runtime.
853 pub fn use_parameter<T>(&mut self, param: UseParameter<T>) -> ReadVar<T>
854 where
855 T: Serialize + DeserializeOwned,
856 {
857 self.pipeline.parameters[param.idx]
858 .used_by_jobs
859 .insert(self.job_idx);
860
861 crate::node::thin_air_read_runtime_var(
862 self.pipeline.parameters[param.idx]
863 .parameter
864 .name()
865 .to_string(),
866 false,
867 )
868 }
869
870 /// Shortcut which allows defining a bool pipeline parameter within a Job.
871 ///
872 /// To share a single parameter between multiple jobs, don't use this method
873 /// - use [`Pipeline::new_parameter_bool`] + [`Self::use_parameter`] instead.
874 pub fn new_parameter_bool(
875 &mut self,
876 name: impl AsRef<str>,
877 description: impl AsRef<str>,
878 kind: ParameterKind,
879 default: Option<bool>,
880 ) -> ReadVar<bool> {
881 let param = self
882 .pipeline
883 .new_parameter_bool(name, description, kind, default);
884 self.use_parameter(param)
885 }
886
887 /// Shortcut which allows defining a number pipeline parameter within a Job.
888 ///
889 /// To share a single parameter between multiple jobs, don't use this method
890 /// - use [`Pipeline::new_parameter_num`] + [`Self::use_parameter`] instead.
891 pub fn new_parameter_num(
892 &mut self,
893 name: impl AsRef<str>,
894 description: impl AsRef<str>,
895 kind: ParameterKind,
896 default: Option<i64>,
897 possible_values: Option<Vec<i64>>,
898 ) -> ReadVar<i64> {
899 let param =
900 self.pipeline
901 .new_parameter_num(name, description, kind, default, possible_values);
902 self.use_parameter(param)
903 }
904
905 /// Shortcut which allows defining a string pipeline parameter within a Job.
906 ///
907 /// To share a single parameter between multiple jobs, don't use this method
908 /// - use [`Pipeline::new_parameter_string`] + [`Self::use_parameter`] instead.
909 pub fn new_parameter_string(
910 &mut self,
911 name: impl AsRef<str>,
912 description: impl AsRef<str>,
913 kind: ParameterKind,
914 default: Option<String>,
915 possible_values: Option<Vec<String>>,
916 ) -> ReadVar<String> {
917 let param =
918 self.pipeline
919 .new_parameter_string(name, description, kind, default, possible_values);
920 self.use_parameter(param)
921 }
922}
923
924#[must_use]
925pub struct PipelineJob<'a> {
926 pipeline: &'a mut Pipeline,
927 job_idx: usize,
928}
929
930impl PipelineJob<'_> {
931 /// (ADO only) specify which agent pool this job will be run on.
932 pub fn ado_set_pool(self, pool: impl AsRef<str>) -> Self {
933 self.ado_set_pool_with_demands(pool, Vec::new())
934 }
935
936 /// (ADO only) specify which agent pool this job will be run on, with
937 /// additional special runner demands.
938 pub fn ado_set_pool_with_demands(self, pool: impl AsRef<str>, demands: Vec<String>) -> Self {
939 self.pipeline.jobs[self.job_idx].ado_pool = Some(AdoPool {
940 name: pool.as_ref().into(),
941 demands,
942 });
943 self
944 }
945
946 /// (ADO only) Declare a job-level, named, read-only ADO variable.
947 ///
948 /// `name` and `value` are both arbitrary strings, which may include ADO
949 /// template expressions.
950 ///
951 /// NOTE: Unless required by some particular third-party task, it's strongly
952 /// recommended to _avoid_ using this method, and to simply use
953 /// [`ReadVar::from_static`] to get a obtain a static variable.
954 ///
955 /// DEVNOTE: In the future, this API may be updated to return a handle that
956 /// will allow resolving the resulting `AdoRuntimeVar`, but for
957 /// implementation expediency, this API does not currently do this. If you
958 /// need to read the value of this variable at runtime, you may need to
959 /// invoke [`AdoRuntimeVar::dangerous_from_global`] manually.
960 ///
961 /// [`NodeCtx::get_ado_variable`]: crate::node::NodeCtx::get_ado_variable
962 pub fn ado_new_named_variable(self, name: impl AsRef<str>, value: impl AsRef<str>) -> Self {
963 let name = name.as_ref();
964 let value = value.as_ref();
965 self.pipeline.jobs[self.job_idx]
966 .ado_variables
967 .insert(name.into(), value.into());
968 self
969 }
970
971 /// (ADO only) Declare multiple job-level, named, read-only ADO variables at
972 /// once.
973 ///
974 /// This is a convenience method to streamline invoking
975 /// [`Self::ado_new_named_variable`] multiple times.
976 ///
977 /// NOTE: Unless required by some particular third-party task, it's strongly
978 /// recommended to _avoid_ using this method, and to simply use
979 /// [`ReadVar::from_static`] to get a obtain a static variable.
980 ///
981 /// DEVNOTE: In the future, this API may be updated to return a handle that
982 /// will allow resolving the resulting `AdoRuntimeVar`, but for
983 /// implementation expediency, this API does not currently do this. If you
984 /// need to read the value of this variable at runtime, you may need to
985 /// invoke [`AdoRuntimeVar::dangerous_from_global`] manually.
986 ///
987 /// [`NodeCtx::get_ado_variable`]: crate::node::NodeCtx::get_ado_variable
988 pub fn ado_new_named_variables<K, V>(self, vars: impl IntoIterator<Item = (K, V)>) -> Self
989 where
990 K: AsRef<str>,
991 V: AsRef<str>,
992 {
993 self.pipeline.jobs[self.job_idx].ado_variables.extend(
994 vars.into_iter()
995 .map(|(k, v)| (k.as_ref().into(), v.as_ref().into())),
996 );
997 self
998 }
999
1000 /// (GitHub Actions only) specify which Github runner this job will be run on.
1001 pub fn gh_set_pool(self, pool: GhRunner) -> Self {
1002 self.pipeline.jobs[self.job_idx].gh_pool = Some(pool);
1003 self
1004 }
1005
1006 /// (GitHub Actions only) Manually override the `if:` condition for this
1007 /// particular job.
1008 ///
1009 /// **This is dangerous**, as an improperly set `if` condition may break
1010 /// downstream flowey jobs which assume flowey is in control of the job's
1011 /// scheduling logic.
1012 ///
1013 /// See
1014 /// <https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idif>
1015 /// for more info.
1016 pub fn gh_dangerous_override_if(self, condition: impl AsRef<str>) -> Self {
1017 self.pipeline.jobs[self.job_idx].gh_override_if = Some(condition.as_ref().into());
1018 self
1019 }
1020
1021 /// (GitHub Actions only) Declare a global job-level environment variable,
1022 /// visible to all downstream steps.
1023 ///
1024 /// `name` and `value` are both arbitrary strings, which may include GitHub
1025 /// Actions template expressions.
1026 ///
1027 /// **This is dangerous**, as it is easy to misuse this API in order to
1028 /// write a node which takes an implicit dependency on there being a global
1029 /// variable set on its behalf by the top-level pipeline code, making it
1030 /// difficult to "locally reason" about the behavior of a node simply by
1031 /// reading its code.
1032 ///
1033 /// Whenever possible, nodes should "late bind" environment variables:
1034 /// accepting a compile-time / runtime flowey parameter, and then setting it
1035 /// prior to executing a child command that requires it.
1036 ///
1037 /// Only use this API in exceptional cases, such as obtaining an environment
1038 /// variable whose value is determined by a job-level GitHub Actions
1039 /// expression evaluation.
1040 pub fn gh_dangerous_global_env_var(
1041 self,
1042 name: impl AsRef<str>,
1043 value: impl AsRef<str>,
1044 ) -> Self {
1045 let name = name.as_ref();
1046 let value = value.as_ref();
1047 self.pipeline.jobs[self.job_idx]
1048 .gh_global_env
1049 .insert(name.into(), value.into());
1050 self
1051 }
1052
1053 /// (GitHub Actions only) Grant permissions required by nodes in the job.
1054 ///
1055 /// For a given node handle, grant the specified permissions.
1056 /// The list provided must match the permissions specified within the node
1057 /// using `requires_permission`.
1058 ///
1059 /// NOTE: While this method is called at a node-level for auditability, the emitted
1060 /// yaml grants permissions at the job-level.
1061 ///
1062 /// This can lead to weird situations where node 1 might not specify a permission
1063 /// required according to Github Actions, but due to job-level granting of the permission
1064 /// by another node 2, the pipeline executes even though it wouldn't if node 2 was removed.
1065 ///
1066 /// For available permission scopes and their descriptions, see
1067 /// <https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions>.
1068 pub fn gh_grant_permissions<N: FlowNodeBase + 'static>(
1069 self,
1070 permissions: impl IntoIterator<Item = (GhPermission, GhPermissionValue)>,
1071 ) -> Self {
1072 let node_handle = NodeHandle::from_type::<N>();
1073 for (permission, value) in permissions {
1074 self.pipeline.jobs[self.job_idx]
1075 .gh_permissions
1076 .entry(node_handle)
1077 .or_default()
1078 .insert(permission, value);
1079 }
1080 self
1081 }
1082
1083 pub fn apply_patchfn(self, patchfn: crate::patch::PatchFn) -> Self {
1084 self.pipeline.jobs[self.job_idx]
1085 .patches
1086 .apply_patchfn(patchfn);
1087 self
1088 }
1089
1090 /// Only run the job if the specified condition is true.
1091 ///
1092 /// When running locally, the `cond`'s default value will be used to
1093 /// determine if the job will be run.
1094 pub fn with_condition(self, cond: UseParameter<bool>) -> Self {
1095 self.pipeline.jobs[self.job_idx].cond_param_idx = Some(cond.idx);
1096 self
1097 }
1098
1099 /// Add a flow node which will be run as part of the job.
1100 pub fn dep_on<R: IntoRequest + 'static>(
1101 self,
1102 f: impl FnOnce(&mut PipelineJobCtx<'_>) -> R,
1103 ) -> Self {
1104 // JobToNodeCtx will ensure artifact deps are taken care of
1105 let req = f(&mut PipelineJobCtx {
1106 pipeline: self.pipeline,
1107 job_idx: self.job_idx,
1108 });
1109
1110 self.pipeline.jobs[self.job_idx]
1111 .root_nodes
1112 .entry(NodeHandle::from_type::<R::Node>())
1113 .or_default()
1114 .push(serde_json::to_vec(&req.into_request()).unwrap().into());
1115
1116 self
1117 }
1118
1119 /// Finish describing the pipeline job.
1120 pub fn finish(self) -> PipelineJobHandle {
1121 PipelineJobHandle {
1122 job_idx: self.job_idx,
1123 }
1124 }
1125
1126 /// Return the job's platform.
1127 pub fn get_platform(&self) -> FlowPlatform {
1128 self.pipeline.jobs[self.job_idx].platform
1129 }
1130
1131 /// Return the job's architecture.
1132 pub fn get_arch(&self) -> FlowArch {
1133 self.pipeline.jobs[self.job_idx].arch
1134 }
1135}
1136
1137#[derive(Clone)]
1138pub struct PipelineJobHandle {
1139 job_idx: usize,
1140}
1141
1142impl PipelineJobHandle {
1143 pub fn is_handle_for(&self, job: &PipelineJob<'_>) -> bool {
1144 self.job_idx == job.job_idx
1145 }
1146}
1147
1148#[derive(Clone, Copy)]
1149pub enum PipelineBackendHint {
1150 /// Pipeline is being run on the user's dev machine (via bash / direct run)
1151 Local,
1152 /// Pipeline is run on ADO
1153 Ado,
1154 /// Pipeline is run on GitHub Actions
1155 Github,
1156}
1157
1158pub trait IntoPipeline {
1159 fn into_pipeline(self, backend_hint: PipelineBackendHint) -> anyhow::Result<Pipeline>;
1160}
1161
1162fn new_parameter_name(name: impl AsRef<str>, kind: ParameterKind) -> String {
1163 match kind {
1164 ParameterKind::Unstable => format!("__unstable_{}", name.as_ref()),
1165 ParameterKind::Stable => name.as_ref().into(),
1166 }
1167}
1168
1169/// Structs which should only be used by top-level flowey emitters. If you're a
1170/// pipeline author, these are not types you need to care about!
1171pub mod internal {
1172 use super::*;
1173 use std::collections::BTreeMap;
1174
1175 pub fn consistent_artifact_runtime_var_name(artifact: impl AsRef<str>, is_use: bool) -> String {
1176 format!(
1177 "artifact_{}_{}",
1178 if is_use { "use_from" } else { "publish_from" },
1179 artifact.as_ref()
1180 )
1181 }
1182
1183 #[derive(Debug)]
1184 pub struct InternalAdoResourcesRepository {
1185 /// flowey-generated unique repo identifier
1186 pub repo_id: String,
1187 /// Type of repo that is being connected to.
1188 pub repo_type: AdoResourcesRepositoryType,
1189 /// Repository name. Format depends on `repo_type`.
1190 pub name: String,
1191 /// git ref to checkout.
1192 pub git_ref: AdoResourcesRepositoryRef<usize>,
1193 /// (optional) ID of the service endpoint connecting to this repository.
1194 pub endpoint: Option<String>,
1195 }
1196
1197 pub struct PipelineJobMetadata {
1198 pub root_nodes: BTreeMap<NodeHandle, Vec<Box<[u8]>>>,
1199 pub patches: PatchResolver,
1200 pub label: String,
1201 pub platform: FlowPlatform,
1202 pub arch: FlowArch,
1203 pub cond_param_idx: Option<usize>,
1204 // backend specific
1205 pub ado_pool: Option<AdoPool>,
1206 pub ado_variables: BTreeMap<String, String>,
1207 pub gh_override_if: Option<String>,
1208 pub gh_pool: Option<GhRunner>,
1209 pub gh_global_env: BTreeMap<String, String>,
1210 pub gh_permissions: BTreeMap<NodeHandle, BTreeMap<GhPermission, GhPermissionValue>>,
1211 }
1212
1213 // TODO: support a more structured format for demands
1214 // See https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/pool-demands
1215 #[derive(Debug, Clone)]
1216 pub struct AdoPool {
1217 pub name: String,
1218 pub demands: Vec<String>,
1219 }
1220
1221 #[derive(Debug)]
1222 pub struct ArtifactMeta {
1223 pub name: String,
1224 pub published_by_job: Option<usize>,
1225 pub used_by_jobs: BTreeSet<usize>,
1226 }
1227
1228 #[derive(Debug)]
1229 pub struct ParameterMeta {
1230 pub parameter: Parameter,
1231 pub used_by_jobs: BTreeSet<usize>,
1232 }
1233
1234 /// Mirror of [`Pipeline`], except with all field marked as `pub`.
1235 pub struct PipelineFinalized {
1236 pub jobs: Vec<PipelineJobMetadata>,
1237 pub artifacts: Vec<ArtifactMeta>,
1238 pub parameters: Vec<ParameterMeta>,
1239 pub extra_deps: BTreeSet<(usize, usize)>,
1240 // backend specific
1241 pub ado_name: Option<String>,
1242 pub ado_schedule_triggers: Vec<AdoScheduleTriggers>,
1243 pub ado_ci_triggers: Option<AdoCiTriggers>,
1244 pub ado_pr_triggers: Option<AdoPrTriggers>,
1245 pub ado_bootstrap_template: String,
1246 pub ado_resources_repository: Vec<InternalAdoResourcesRepository>,
1247 pub ado_post_process_yaml_cb:
1248 Option<Box<dyn FnOnce(serde_yaml::Value) -> serde_yaml::Value>>,
1249 pub ado_variables: BTreeMap<String, String>,
1250 pub gh_name: Option<String>,
1251 pub gh_schedule_triggers: Vec<GhScheduleTriggers>,
1252 pub gh_ci_triggers: Option<GhCiTriggers>,
1253 pub gh_pr_triggers: Option<GhPrTriggers>,
1254 pub gh_bootstrap_template: String,
1255 }
1256
1257 impl PipelineFinalized {
1258 pub fn from_pipeline(mut pipeline: Pipeline) -> Self {
1259 if let Some(cb) = pipeline.inject_all_jobs_with.take() {
1260 for job_idx in 0..pipeline.jobs.len() {
1261 let _ = cb(PipelineJob {
1262 pipeline: &mut pipeline,
1263 job_idx,
1264 });
1265 }
1266 }
1267
1268 let Pipeline {
1269 mut jobs,
1270 artifacts,
1271 parameters,
1272 extra_deps,
1273 ado_name,
1274 ado_bootstrap_template,
1275 ado_schedule_triggers,
1276 ado_ci_triggers,
1277 ado_pr_triggers,
1278 ado_resources_repository,
1279 ado_post_process_yaml_cb,
1280 ado_variables,
1281 gh_name,
1282 gh_schedule_triggers,
1283 gh_ci_triggers,
1284 gh_pr_triggers,
1285 gh_bootstrap_template,
1286 // not relevant to consumer code
1287 dummy_done_idx: _,
1288 artifact_names: _,
1289 global_patchfns,
1290 inject_all_jobs_with: _, // processed above
1291 } = pipeline;
1292
1293 for patchfn in global_patchfns {
1294 for job in &mut jobs {
1295 job.patches.apply_patchfn(patchfn)
1296 }
1297 }
1298
1299 Self {
1300 jobs,
1301 artifacts,
1302 parameters,
1303 extra_deps,
1304 ado_name,
1305 ado_schedule_triggers,
1306 ado_ci_triggers,
1307 ado_pr_triggers,
1308 ado_bootstrap_template,
1309 ado_resources_repository,
1310 ado_post_process_yaml_cb,
1311 ado_variables,
1312 gh_name,
1313 gh_schedule_triggers,
1314 gh_ci_triggers,
1315 gh_pr_triggers,
1316 gh_bootstrap_template,
1317 }
1318 }
1319 }
1320
1321 #[derive(Debug, Clone)]
1322 pub enum Parameter {
1323 Bool {
1324 name: String,
1325 description: String,
1326 kind: ParameterKind,
1327 default: Option<bool>,
1328 },
1329 String {
1330 name: String,
1331 description: String,
1332 default: Option<String>,
1333 kind: ParameterKind,
1334 possible_values: Option<Vec<String>>,
1335 },
1336 Num {
1337 name: String,
1338 description: String,
1339 default: Option<i64>,
1340 kind: ParameterKind,
1341 possible_values: Option<Vec<i64>>,
1342 },
1343 }
1344
1345 impl Parameter {
1346 pub fn name(&self) -> &str {
1347 match self {
1348 Parameter::Bool { name, .. } => name,
1349 Parameter::String { name, .. } => name,
1350 Parameter::Num { name, .. } => name,
1351 }
1352 }
1353 }
1354}
1355