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