gitlab_manager/api/variables.rs
1//! Project CI/CD variables API operations.
2//!
3//! This module provides operations for managing CI/CD variables
4//! at the project level.
5
6use crate::error::{GitLabError, Result};
7use crate::models::{Variable, VariableOptions, VariableType};
8use gitlab::api::{projects::variables, ApiError, Query};
9use gitlab::Gitlab;
10
11/// Builder for managing project CI/CD variables.
12///
13/// Provides operations to create, update, list, and delete
14/// CI/CD variables for a GitLab project.
15///
16/// # Examples
17///
18/// ```no_run
19/// use gitlab_manager::{GitLabClient, models::VariableOptions};
20///
21/// # async fn example() -> Result<(), gitlab_manager::GitLabError> {
22/// let client = GitLabClient::new("https://gitlab.com", "token")?;
23///
24/// // Create a new variable
25/// let opts = VariableOptions::new()
26/// .protected(true)
27/// .masked(true);
28///
29/// client.variables("myorg/myproject")
30/// .create("API_KEY", "secret-value", opts)
31/// .await?;
32/// # Ok(())
33/// # }
34/// ```
35pub struct VariableBuilder<'a> {
36 client: &'a Gitlab,
37 project: String,
38}
39
40impl<'a> VariableBuilder<'a> {
41 /// Creates a new variable builder.
42 pub(crate) fn new(client: &'a Gitlab, project: impl Into<String>) -> Self {
43 Self {
44 client,
45 project: project.into(),
46 }
47 }
48
49 /// Create a new CI/CD variable.
50 ///
51 /// # Errors
52 ///
53 /// Returns an error if the variable already exists or if the request fails.
54 ///
55 /// # Examples
56 ///
57 /// ```no_run
58 /// # use gitlab_manager::{GitLabClient, models::VariableOptions};
59 /// # async fn example() -> Result<(), gitlab_manager::GitLabError> {
60 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
61 /// let opts = VariableOptions::new()
62 /// .protected(true)
63 /// .masked(true);
64 ///
65 /// client.variables("myproject")
66 /// .create("DATABASE_URL", "postgres://...", opts)
67 /// .await?;
68 /// # Ok(())
69 /// # }
70 /// ```
71 pub async fn create(
72 &self,
73 key: impl AsRef<str>,
74 value: impl AsRef<str>,
75 opts: VariableOptions,
76 ) -> Result<Variable> {
77 let key = key.as_ref();
78 let value = value.as_ref();
79
80 // Build the request using the gitlab crate's API
81 let mut builder = variables::CreateProjectVariable::builder();
82 builder
83 .project(&self.project)
84 .key(key)
85 .value(value)
86 .variable_type(match opts.variable_type {
87 VariableType::EnvVar => variables::ProjectVariableType::EnvVar,
88 VariableType::File => variables::ProjectVariableType::File,
89 })
90 .protected(opts.protected)
91 .masked(opts.masked)
92 .raw(opts.raw);
93
94 if let Some(scope) = opts.environment_scope {
95 builder.environment_scope(scope);
96 }
97
98 if let Some(desc) = opts.description {
99 builder.description(desc);
100 }
101
102 let endpoint = builder
103 .build()
104 .map_err(|e| GitLabError::api(format!("Failed to build create variable request: {}", e)))?;
105
106 endpoint
107 .query(self.client)
108 .map_err(|e| match e {
109 ApiError::GitlabService { status, .. } if status.as_u16() == 400 => {
110 GitLabError::conflict(format!("Variable '{}' already exists", key))
111 }
112 _ => GitLabError::api(format!("Failed to create variable '{}': {}", key, e)),
113 })
114 }
115
116 /// Update an existing CI/CD variable.
117 ///
118 /// # Errors
119 ///
120 /// Returns an error if the variable doesn't exist or if the request fails.
121 ///
122 /// # Examples
123 ///
124 /// ```no_run
125 /// # use gitlab_manager::{GitLabClient, models::VariableOptions};
126 /// # async fn example() -> Result<(), gitlab_manager::GitLabError> {
127 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
128 /// let opts = VariableOptions::new()
129 /// .protected(true);
130 ///
131 /// client.variables("myproject")
132 /// .update("API_KEY", "new-secret-value", opts)
133 /// .await?;
134 /// # Ok(())
135 /// # }
136 /// ```
137 pub async fn update(
138 &self,
139 key: impl AsRef<str>,
140 value: impl AsRef<str>,
141 opts: VariableOptions,
142 ) -> Result<Variable> {
143 let key = key.as_ref();
144 let value = value.as_ref();
145
146 let mut builder = variables::UpdateProjectVariable::builder();
147 builder
148 .project(&self.project)
149 .key(key)
150 .value(value)
151 .variable_type(match opts.variable_type {
152 VariableType::EnvVar => variables::ProjectVariableType::EnvVar,
153 VariableType::File => variables::ProjectVariableType::File,
154 })
155 .protected(opts.protected)
156 .masked(opts.masked)
157 .raw(opts.raw);
158
159 if let Some(scope) = opts.environment_scope {
160 builder.environment_scope(scope);
161 }
162
163 if let Some(desc) = opts.description {
164 builder.description(desc);
165 }
166
167 let endpoint = builder
168 .build()
169 .map_err(|e| GitLabError::api(format!("Failed to build update variable request: {}", e)))?;
170
171 endpoint.query(self.client).map_err(|e| match e {
172 ApiError::GitlabService { status, .. } if status.as_u16() == 404 => {
173 GitLabError::not_found("Variable", key)
174 }
175 _ => GitLabError::api(format!("Failed to update variable '{}': {}", key, e)),
176 })
177 }
178
179 /// Create or update a CI/CD variable (upsert).
180 ///
181 /// If the variable exists, it will be updated. Otherwise, it will be created.
182 ///
183 /// # Examples
184 ///
185 /// ```no_run
186 /// # use gitlab_manager::{GitLabClient, models::VariableOptions};
187 /// # async fn example() -> Result<(), gitlab_manager::GitLabError> {
188 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
189 /// let opts = VariableOptions::new()
190 /// .protected(true)
191 /// .masked(true);
192 ///
193 /// // Will create if doesn't exist, update if it does
194 /// client.variables("myproject")
195 /// .set("API_KEY", "secret-value", opts)
196 /// .await?;
197 /// # Ok(())
198 /// # }
199 /// ```
200 pub async fn set(
201 &self,
202 key: impl AsRef<str>,
203 value: impl AsRef<str>,
204 opts: VariableOptions,
205 ) -> Result<Variable> {
206 let key = key.as_ref();
207
208 // Try to update first
209 match self.update(key, value.as_ref(), opts.clone()).await {
210 Ok(var) => Ok(var),
211 Err(GitLabError::NotFound { .. }) => {
212 // Variable doesn't exist, create it
213 self.create(key, value.as_ref(), opts).await
214 }
215 Err(e) => Err(e),
216 }
217 }
218
219 /// Get a CI/CD variable.
220 ///
221 /// # Errors
222 ///
223 /// Returns an error if the variable doesn't exist or if the request fails.
224 ///
225 /// # Examples
226 ///
227 /// ```no_run
228 /// # use gitlab_manager::GitLabClient;
229 /// # async fn example() -> Result<(), gitlab_manager::GitLabError> {
230 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
231 /// let var = client.variables("myproject")
232 /// .get("API_KEY")
233 /// .await?;
234 ///
235 /// println!("Value: {}", var.value);
236 /// # Ok(())
237 /// # }
238 /// ```
239 pub async fn get(&self, key: impl AsRef<str>) -> Result<Variable> {
240 let key = key.as_ref();
241
242 let endpoint = variables::ProjectVariable::builder()
243 .project(&self.project)
244 .key(key)
245 .build()
246 .map_err(|e| GitLabError::api(format!("Failed to build get variable request: {}", e)))?;
247
248 endpoint.query(self.client).map_err(|e| match e {
249 ApiError::GitlabService { status, .. } if status.as_u16() == 404 => {
250 GitLabError::not_found("Variable", key)
251 }
252 _ => GitLabError::api(format!("Failed to get variable '{}': {}", key, e)),
253 })
254 }
255
256 /// List all CI/CD variables for the project.
257 ///
258 /// # Examples
259 ///
260 /// ```no_run
261 /// # use gitlab_manager::GitLabClient;
262 /// # async fn example() -> Result<(), gitlab_manager::GitLabError> {
263 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
264 /// let vars = client.variables("myproject")
265 /// .list()
266 /// .await?;
267 ///
268 /// for var in vars {
269 /// println!("{}: {} (protected={})", var.key, var.value, var.protected);
270 /// }
271 /// # Ok(())
272 /// # }
273 /// ```
274 pub async fn list(&self) -> Result<Vec<Variable>> {
275 let endpoint = variables::ProjectVariables::builder()
276 .project(&self.project)
277 .build()
278 .map_err(|e| GitLabError::api(format!("Failed to build list variables request: {}", e)))?;
279
280 endpoint
281 .query(self.client)
282 .map_err(|e| GitLabError::api(format!("Failed to list variables: {}", e)))
283 }
284
285 /// Delete a CI/CD variable.
286 ///
287 /// # Errors
288 ///
289 /// Returns an error if the variable doesn't exist or if the request fails.
290 ///
291 /// # Examples
292 ///
293 /// ```no_run
294 /// # use gitlab_manager::GitLabClient;
295 /// # async fn example() -> Result<(), gitlab_manager::GitLabError> {
296 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
297 /// client.variables("myproject")
298 /// .delete("OLD_API_KEY")
299 /// .await?;
300 /// # Ok(())
301 /// # }
302 /// ```
303 pub async fn delete(&self, key: impl AsRef<str>) -> Result<()> {
304 let key = key.as_ref();
305
306 let endpoint = variables::DeleteProjectVariable::builder()
307 .project(&self.project)
308 .key(key)
309 .build()
310 .map_err(|e| GitLabError::api(format!("Failed to build delete variable request: {}", e)))?;
311
312 // Delete endpoints don't return a value, use ignore() method
313 gitlab::api::ignore(endpoint)
314 .query(self.client)
315 .map_err(|e| match e {
316 ApiError::GitlabService { status, .. } if status.as_u16() == 404 => {
317 GitLabError::not_found("Variable", key)
318 }
319 _ => GitLabError::api(format!("Failed to delete variable '{}': {}", key, e)),
320 })?;
321
322 Ok(())
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 #[test]
331 fn test_variable_builder() {
332 fn _compile_test(client: &Gitlab) {
333 let _builder = VariableBuilder::new(client, "project");
334 }
335 }
336}