github_actions_models/
action.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//! Data models for GitHub Actions action definitions.
//!
//! Resources:
//! * [Metadata syntax for GitHub Actions]
//! * [JSON Schema definition for GitHub Actions]
//!
//! [Metadata syntax for GitHub Actions]: https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions
//! [JSON Schema definition for GitHub Actions]: https://json.schemastore.org/github-action.json

use indexmap::IndexMap;
use serde::Deserialize;

use crate::common::{
    expr::{BoE, LoE},
    Env, If,
};

/// A GitHub Actions action definition.
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Action {
    pub name: String,
    pub author: Option<String>,
    pub description: Option<String>,
    #[serde(default)]
    pub inputs: IndexMap<String, Input>,
    #[serde(default)]
    pub outputs: IndexMap<String, Output>,
    pub runs: Runs,
}

/// An action input.
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Input {
    pub description: String,
    pub required: Option<bool>,
    pub default: Option<String>,
}

/// An action output.
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Output {
    pub description: String,
    // NOTE: not optional for composite actions, but this is not worth modeling.
    pub value: Option<String>,
}

/// An action `runs` definition.
///
/// A `runs` definition can be either a JavaScript action, a "composite" action
/// (made up of several constituent actions), or a Docker action.
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", untagged)]
pub enum Runs {
    JavaScript(JavaScript),
    Composite(Composite),
    Docker(Docker),
}

/// A `runs` definition for a JavaScript action.
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct JavaScript {
    /// The Node runtime to use for this action. This is one of:
    ///
    /// `"node12" | "node16" | "node20"`
    pub using: String,

    /// The action's entrypoint, as a JavaScript file.
    pub main: String,

    /// An optional script to run, before [`JavaScript::main`].
    pub pre: Option<String>,

    /// An optional expression that triggers [`JavaScript::pre`] if it evaluates to `true`.
    ///
    /// If not present, defaults to `always()`
    pub pre_if: Option<If>,

    /// An optional script to run, after [`JavaScript::main`].
    pub post: Option<String>,

    /// An optional expression that triggers [`JavaScript::post`] if it evaluates to `true`.
    ///
    /// If not present, defaults to `always()`
    pub post_if: Option<If>,
}

/// A `runs` definition for a composite action.
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Composite {
    /// Invariant: `"composite"`
    pub using: String,
    /// The individual steps that make up this composite action.
    pub steps: Vec<Step>,
}

/// An individual composite action step.
///
/// This is similar, but not identical to [`crate::workflow::job::Step`].
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Step {
    /// An optional ID for this composite step.
    pub id: Option<String>,

    /// An optional expression that prevents this composite step from running unless it evaluates to `true`.
    pub r#if: Option<If>,

    /// An optional name for this composite step.
    pub name: Option<String>,

    /// An optional boolean or expression that, if `true`, prevents the job from failing when
    /// this composite step fails.
    #[serde(default)]
    pub continue_on_error: BoE,

    /// The `run:` or `uses:` body for this composite step.
    #[serde(flatten)]
    pub body: StepBody,
}

/// The body of a composite action step.
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", untagged)]
pub enum StepBody {
    /// A step that uses another GitHub Action.
    Uses {
        /// The GitHub Action being used.
        uses: String,

        /// Any inputs to the action being used.
        #[serde(default)]
        with: Env,
    },
    /// A step that runs a command in a shell.
    Run {
        /// The command to run.
        run: String,

        /// The shell to run in.
        shell: String,

        /// An optional environment mapping for this step.
        #[serde(default)]
        env: LoE<Env>,

        /// An optional working directory to run [`RunShell::run`] from.
        working_directory: Option<String>,
    },
}

/// A `runs` definition for a Docker action.
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Docker {
    /// Invariant: `"docker"`
    pub using: String,

    /// The Docker image to use.
    pub image: String,

    /// An optional environment mapping for this step.
    #[serde(default)]
    pub env: Env,

    /// An optional Docker entrypoint, potentially overriding the image's
    /// default entrypoint.
    pub entrypoint: Option<String>,

    /// An optional "pre" entrypoint to run, before [`Docker::entrypoint`].
    pub pre_entrypoint: Option<String>,

    /// An optional expression that triggers [`Docker::pre_entrypoint`] if it evaluates to `true`.
    ///
    /// If not present, defaults to `always()`
    pub pre_if: Option<If>,

    /// An optional "post" entrypoint to run, after [`Docker::entrypoint`] or the default
    /// entrypoint.
    pub post_entrypoint: Option<String>,

    /// An optional expression that triggers [`Docker::post_entrypoint`] if it evaluates to `true`.
    ///
    /// If not present, defaults to `always()`
    pub post_if: Option<If>,
}