microsoft/openvmm

Public

mirrored fromhttps://github.com/microsoft/openvmmAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
9fa0c3ee87af75e07fa974b6005348ae6b9349ff

Branches

Tags

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

Clone

HTTPS

Download ZIP

flowey/flowey_core/src/pipeline.rs

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