Skip to main content

opal/
lib.rs

1use serde::Deserialize;
2use std::path::PathBuf;
3use std::str::FromStr;
4use structopt::StructOpt;
5
6pub mod compiler;
7pub mod config;
8pub mod display;
9pub mod engine;
10pub mod env;
11pub mod execution_plan;
12pub mod executor;
13pub mod git;
14pub mod gitlab;
15pub mod history;
16pub mod logging;
17pub mod model;
18pub mod naming;
19pub mod pipeline;
20pub mod runner;
21pub mod runtime;
22pub mod secrets;
23pub mod terminal;
24pub mod ui;
25
26#[derive(StructOpt)]
27pub struct Cli {
28    #[structopt(
29        short,
30        long,
31        default_value = "info",
32        possible_values = &["trace", "debug", "info", "warn", "error"]
33    )]
34    pub log_level: tracing::Level,
35
36    #[structopt(subcommand)]
37    pub commands: Commands,
38}
39
40#[derive(StructOpt)]
41pub enum Commands {
42    Run(RunArgs),
43    Plan(PlanArgs),
44    View(ViewArgs),
45}
46
47#[derive(StructOpt)]
48pub struct RunArgs {
49    /// Which .gitlab-ci.yml file to use (defaults to <workdir>/.gitlab-ci.yml)
50    #[structopt(short, long)]
51    pub pipeline: Option<PathBuf>,
52
53    #[structopt(short, long)]
54    /// Context directory (defaults to current working directory)
55    pub workdir: Option<PathBuf>,
56
57    #[structopt(short, long)]
58    /// Optional base image override; falls back to pipeline/job image.
59    pub base_image: Option<String>,
60
61    #[structopt(
62        short = "E",
63        long = "env",
64        value_name = "GLOB",
65        help = "Include host env vars matching this glob (e.g. APP_*). Repeat to add more."
66    )]
67    pub env_includes: Vec<String>,
68
69    #[structopt(long = "max-parallel-jobs", default_value = "5")]
70    /// Maximum number of jobs to run concurrently
71    pub max_parallel_jobs: usize,
72
73    #[structopt(long = "trace-scripts")]
74    /// Print each job command as it runs (enables shell `set -x`)
75    pub trace_scripts: bool,
76
77    #[structopt(
78        long = "engine",
79        default_value = "auto",
80        possible_values = EngineChoice::VARIANTS,
81        help = "Container runtime to use (auto, container, docker, podman, nerdctl, orbstack). nerdctl is Linux-specific in Opal."
82    )]
83    pub engine: EngineChoice,
84
85    #[structopt(long = "no-tui")]
86    /// Disable the Ratatui interface
87    pub no_tui: bool,
88
89    #[structopt(long = "gitlab-base-url", env = "OPAL_GITLAB_BASE_URL")]
90    /// Base URL for GitLab API (default: https://gitlab.com)
91    pub gitlab_base_url: Option<String>,
92
93    #[structopt(long = "gitlab-token", env = "OPAL_GITLAB_TOKEN")]
94    /// Personal access token used when downloading cross-project artifacts
95    pub gitlab_token: Option<String>,
96
97    #[structopt(long = "job", value_name = "NAME")]
98    /// Limit execution to selected jobs plus their required upstream dependencies. Repeat to select multiple jobs.
99    pub jobs: Vec<String>,
100}
101
102#[derive(StructOpt)]
103pub struct ViewArgs {
104    #[structopt(short, long)]
105    /// Context directory (defaults to current working directory)
106    pub workdir: Option<PathBuf>,
107}
108
109#[derive(StructOpt)]
110pub struct PlanArgs {
111    /// Which .gitlab-ci.yml file to inspect (defaults to <workdir>/.gitlab-ci.yml)
112    #[structopt(short, long)]
113    pub pipeline: Option<PathBuf>,
114
115    #[structopt(short, long)]
116    /// Context directory (defaults to current working directory)
117    pub workdir: Option<PathBuf>,
118
119    #[structopt(long = "gitlab-base-url", env = "OPAL_GITLAB_BASE_URL")]
120    /// Base URL for GitLab API (default: https://gitlab.com)
121    pub gitlab_base_url: Option<String>,
122
123    #[structopt(long = "gitlab-token", env = "OPAL_GITLAB_TOKEN")]
124    /// Personal access token used when resolving cross-project includes
125    pub gitlab_token: Option<String>,
126
127    #[structopt(long = "job", value_name = "NAME")]
128    /// Limit planning to selected jobs plus their required upstream dependencies. Repeat to select multiple jobs.
129    pub jobs: Vec<String>,
130
131    #[structopt(long = "no-pager")]
132    /// Print the plan directly instead of opening it in a pager
133    pub no_pager: bool,
134
135    #[structopt(long = "json")]
136    /// Emit the execution plan as JSON
137    pub json: bool,
138}
139
140#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize)]
141#[serde(rename_all = "lowercase")]
142pub enum EngineChoice {
143    Auto,
144    Container,
145    Docker,
146    Podman,
147    Nerdctl,
148    Orbstack,
149}
150
151impl EngineChoice {
152    pub const VARIANTS: &'static [&'static str] = &[
153        "auto",
154        "container",
155        "docker",
156        "podman",
157        "nerdctl",
158        "orbstack",
159    ];
160}
161
162impl FromStr for EngineChoice {
163    type Err = String;
164
165    fn from_str(s: &str) -> Result<Self, Self::Err> {
166        match s.to_ascii_lowercase().as_str() {
167            "auto" => Ok(Self::Auto),
168            "container" => Ok(Self::Container),
169            "docker" => Ok(Self::Docker),
170            "podman" => Ok(Self::Podman),
171            "nerdctl" => Ok(Self::Nerdctl),
172            "orbstack" => Ok(Self::Orbstack),
173            other => Err(format!("unknown engine '{other}'")),
174        }
175    }
176}
177
178#[derive(Clone, Copy, Debug)]
179pub enum EngineKind {
180    ContainerCli,
181    Docker,
182    Podman,
183    Nerdctl,
184    Orbstack,
185}
186
187#[derive(Debug, Clone)]
188pub struct ExecutorConfig {
189    pub image: Option<String>,
190    pub workdir: PathBuf,
191    pub pipeline: PathBuf,
192    pub env_includes: Vec<String>,
193    pub selected_jobs: Vec<String>,
194    pub max_parallel_jobs: usize,
195    pub enable_tui: bool,
196    pub engine: EngineKind,
197    pub gitlab: Option<GitLabRemoteConfig>,
198    pub settings: config::OpalConfig,
199    pub trace_scripts: bool,
200}
201
202#[derive(Clone, Debug)]
203pub struct GitLabRemoteConfig {
204    pub base_url: String,
205    pub token: String,
206}