Skip to main content

gl_env/
lib.rs

1#![forbid(unsafe_code)]
2
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7use gitlab::{Variable, VariableType};
8
9pub mod cli;
10pub mod gitlab;
11
12/// Name & version of the application
13pub const APP: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
14
15/// Name, version & repository URL of the app
16pub const APP_UA: &str = concat!(
17    env!("CARGO_PKG_NAME"),
18    "/",
19    env!("CARGO_PKG_VERSION"),
20    " (",
21    env!("CARGO_PKG_REPOSITORY"),
22    ")",
23);
24
25/// A map of variables, indexed by their key.
26///
27/// Must be a map such that is impossible to set the same variable twice.
28pub type Variables = BTreeMap<VariableKey, VariableValue>;
29
30/// Format of the YAML input/output
31#[derive(Debug, Default, Serialize, Deserialize)]
32pub struct State {
33    /// Default attributes to be applied to all variables
34    #[serde(default)]
35    pub defaults: Settings,
36
37    /// Variables with no specific environment scope (`*`)
38    #[serde(default)]
39    pub variables: Variables,
40
41    /// Variables belonging to a specific environment scope
42    #[serde(default)]
43    pub environments: BTreeMap<String, Variables>,
44}
45
46/// Variable settings
47#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
48pub struct Settings {
49    /// Whether the value should be filtered in job logs.
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    pub masked: Option<bool>,
52
53    /// Export variable to pipelines running on protected branches and tags only.
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub protected: Option<bool>,
56
57    /// If `false`, `$` will be treated as the start of a reference to another variable.
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub raw: Option<bool>,
60}
61
62impl From<State> for Vec<Variable> {
63    fn from(state: State) -> Self {
64        let mut variables = Vec::new();
65
66        for (key, v) in state.variables {
67            let var = into_variable(key.into(), String::from("*"), v, &state.defaults);
68            variables.push(var);
69        }
70
71        for (env, vars) in state.environments {
72            for (key, v) in vars {
73                let var = into_variable(key.into(), env.clone(), v, &state.defaults);
74                variables.push(var);
75            }
76        }
77        variables
78    }
79}
80
81impl From<Vec<gitlab::Variable>> for State {
82    fn from(variables: Vec<gitlab::Variable>) -> Self {
83        let mut s = Self::default();
84        for v in variables {
85            let env = v.environment_scope.clone();
86            let key = VariableKey(v.key.clone());
87            let value = VariableValue::from(v);
88
89            match env.as_str() {
90                "*" => {
91                    s.variables.insert(key, value);
92                }
93                _ => {
94                    s.environments.entry(env).or_default().insert(key, value);
95                }
96            }
97        }
98        s
99    }
100}
101
102fn into_variable(
103    key: String,
104    environment_scope: String,
105    value: VariableValue,
106    defaults: &Settings,
107) -> Variable {
108    let VariableValue {
109        value,
110        description,
111        settings: Settings {
112            masked,
113            protected,
114            raw,
115        },
116        variable_type,
117    } = value;
118
119    // Apply custom or GitLab defaults
120    let masked = masked.unwrap_or(defaults.masked.unwrap_or(false));
121    let protected = protected.unwrap_or(defaults.protected.unwrap_or(false));
122    let raw = raw.unwrap_or(defaults.raw.unwrap_or(false));
123
124    Variable {
125        key,
126        value,
127        description,
128        environment_scope,
129        masked,
130        protected,
131        raw,
132        variable_type,
133    }
134}
135
136// impl AsRef<BTreeMap<VariableKey, VariableValue>
137
138/// Key of a variable
139///
140/// Can only contain letters, numbers, and '_'.
141///
142/// TODO: validate this ^
143#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
144pub struct VariableKey(String);
145
146impl From<VariableKey> for String {
147    fn from(key: VariableKey) -> Self {
148        key.0
149    }
150}
151
152/// The value of a variable.
153#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
154pub struct VariableValue {
155    /// The actual value
156    ///
157    /// If `masked` is `true`, this value must
158    /// - be at least 8 characters long
159    /// - not contain whitespace characters
160    /// - not contain backslashes (`\`)
161    ///
162    /// TODO: validate this ^
163    pub value: String,
164
165    /// The description of the variable's value or usage.
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub description: Option<String>,
168
169    #[serde(
170        rename = "type",
171        default,
172        skip_serializing_if = "VariableType::is_default"
173    )]
174    pub variable_type: VariableType,
175
176    #[serde(flatten)]
177    pub settings: Settings,
178}
179
180impl From<gitlab::Variable> for VariableValue {
181    fn from(value: gitlab::Variable) -> Self {
182        let gitlab::Variable {
183            key: _,
184            value,
185            description,
186            environment_scope: _,
187            masked,
188            protected,
189            raw,
190            variable_type,
191        } = value;
192
193        Self {
194            value,
195            description,
196            settings: Settings {
197                masked: Some(masked),
198                protected: Some(protected),
199                raw: Some(raw),
200            },
201            variable_type,
202        }
203    }
204}