1use crate::{
2 build_request,
3 error::{surf_to_tool_error, ToolError},
4 set_page_number,
5 settings::Core,
6 variable::{Variable, VariablesOuter},
7 workspace::Workspace,
8 Meta, BASE_URL,
9};
10
11use log::{error, info};
12use serde::{Deserialize, Serialize};
13use serde_json::json;
14use surf::{http::Method, Client};
15use url::Url;
16
17#[derive(Clone, Debug, Deserialize, Serialize)]
18pub struct Attributes {
19 pub name: String,
20 pub description: String,
21 #[serde(skip_serializing_if = "Option::is_none")]
22 pub global: Option<bool>,
23}
24
25#[derive(Clone, Debug, Deserialize, Serialize)]
26pub struct Workspaces {
27 #[serde(rename = "type")]
28 pub relationship_type: String,
29 pub id: String,
30}
31
32#[derive(Clone, Debug, Deserialize, Serialize)]
33pub struct WorkspacesOuter {
34 pub data: Vec<Workspaces>,
35}
36
37impl From<Vec<Workspace>> for WorkspacesOuter {
38 fn from(workspaces: Vec<Workspace>) -> Self {
39 Self {
40 data: workspaces
41 .into_iter()
42 .map(|ws| Workspaces {
43 relationship_type: "workspaces".to_string(),
44 id: ws.id,
45 })
46 .collect(),
47 }
48 }
49}
50
51#[derive(Clone, Debug, Deserialize, Serialize)]
52pub struct Projects {
53 #[serde(rename = "type")]
54 pub relationship_type: String,
55 pub id: String,
56}
57
58#[derive(Clone, Debug, Deserialize, Serialize)]
59pub struct ProjectsOuter {
60 pub data: Vec<Projects>,
61}
62
63#[derive(Clone, Debug, Deserialize, Serialize)]
64pub struct Relationships {
65 #[serde(skip_serializing_if = "Option::is_none")]
66 pub workspaces: Option<WorkspacesOuter>,
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub projects: Option<ProjectsOuter>,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub vars: Option<VariablesOuter>,
71}
72
73#[derive(Clone, Debug, Deserialize, Serialize)]
74pub struct VarSet {
75 #[serde(rename = "type")]
76 pub relationship_type: String,
77 pub attributes: Attributes,
78 pub relationships: Relationships,
79}
80
81#[derive(Clone, Debug, Serialize, Deserialize)]
82pub struct VarSets {
83 pub data: Vec<VarSet>,
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub meta: Option<Meta>,
86}
87
88impl VarSets {
89 pub fn new(var_sets: Vec<VarSet>) -> Self {
90 VarSets { data: var_sets, meta: None }
91 }
92}
93
94#[derive(Clone, Debug, Serialize, Deserialize)]
95struct VarSetOuter {
96 pub data: VarSet,
97}
98
99#[derive(Clone, Debug, Serialize, Deserialize)]
100pub struct VarSetOptions {
101 pub name: String,
102 pub description: String,
103 pub global: Option<bool>,
104 pub workspaces: Option<Vec<Workspace>>,
105 pub projects: Option<Vec<String>>,
106 pub vars: Option<Vec<Variable>>,
107}
108
109impl VarSetOuter {
110 pub fn new(options: VarSetOptions) -> Self {
111 Self {
112 data: VarSet {
113 relationship_type: "vars".to_string(),
114 attributes: Attributes {
115 name: options.name.to_string(),
116 description: options.description.to_string(),
117 global: options.global,
118 },
119 relationships: Relationships {
120 workspaces: options.workspaces.map(|ws| WorkspacesOuter {
121 data: ws
122 .into_iter()
123 .map(|ws| Workspaces {
124 relationship_type: "workspaces".to_string(),
125 id: ws.id,
126 })
127 .collect(),
128 }),
129 projects: options.projects.map(|p| ProjectsOuter {
130 data: p
131 .into_iter()
132 .map(|p| Projects {
133 relationship_type: "projects".to_string(),
134 id: p,
135 })
136 .collect(),
137 }),
138 vars: options.vars.map(|v| VariablesOuter { data: v }),
139 },
140 },
141 }
142 }
143}
144
145#[derive(Clone, Debug, Serialize)]
146pub struct ApplyVarSet {
147 #[serde(rename = "type")]
148 pub relationship_type: String,
149 pub id: String,
150}
151
152#[derive(Clone, Debug, Serialize)]
153struct ApplyVarSetOuter {
154 pub data: Vec<ApplyVarSet>,
155}
156
157impl From<Vec<Workspace>> for ApplyVarSetOuter {
158 fn from(workspaces: Vec<Workspace>) -> Self {
159 Self {
160 data: workspaces
161 .into_iter()
162 .map(|ws| ApplyVarSet {
163 relationship_type: "workspaces".to_string(),
164 id: ws.id,
165 })
166 .collect(),
167 }
168 }
169}
170
171async fn check_pagination(
172 meta: Meta,
173 var_set_list: &mut VarSets,
174 url: Url,
175 config: &Core,
176 client: Client,
177) -> Result<(), ToolError> {
178 let max_depth = config.pagination.max_depth.parse::<u32>()?;
179 if max_depth > 1 || max_depth == 0 {
180 let current_depth: u32 = 1;
181 if let Some(next_page) = meta.pagination.next_page {
182 if max_depth == 0 || current_depth < max_depth {
183 let num_pages: u32 = if max_depth >= meta.pagination.total_pages
184 || max_depth == 0
185 {
186 meta.pagination.total_pages
187 } else {
188 max_depth
189 };
190
191 for n in next_page..=num_pages {
193 let u = url.clone();
194 info!("Retrieving variable set page {}.", &n);
195 let u = match set_page_number(n, u) {
196 Some(u) => u,
197 None => {
198 error!("Failed to set page number.");
199 return Err(ToolError::Pagination(
200 "Failed to set page number.".to_string(),
201 ));
202 }
203 };
204 let req =
205 build_request(Method::Get, u.clone(), config, None);
206 let mut response =
207 client.send(req).await.map_err(surf_to_tool_error)?;
208 if response.status().is_success() {
209 let var_set_pages: Result<VarSets, ToolError> =
210 response
211 .body_json()
212 .await
213 .map_err(surf_to_tool_error);
214 match var_set_pages {
215 Ok(mut t) => {
216 var_set_list.data.append(&mut t.data);
217 }
218 Err(e) => {
219 error!("{:#?}", e);
220 return Err(e);
221 }
222 }
223 }
224 }
225 }
226 }
227 }
228 Ok(())
229}
230
231pub async fn show(
232 variable_set_id: &str,
233 config: &Core,
234 client: Client,
235) -> Result<VarSet, ToolError> {
236 let url = Url::parse(&format!("{}/varsets/{}", BASE_URL, variable_set_id))?;
237 let req = build_request(Method::Get, url, config, None);
238 let mut res = client.send(req).await.map_err(surf_to_tool_error)?;
239 if res.status().is_success() {
240 let var_set: VarSetOuter =
241 res.body_json().await.map_err(surf_to_tool_error)?;
242 Ok(var_set.data)
243 } else {
244 error!("Failed to show variable set");
245 let error = res.body_string().await.map_err(surf_to_tool_error)?;
246 Err(ToolError::General(anyhow::anyhow!(error)))
247 }
248}
249
250pub async fn list_by_org(
251 config: &Core,
252 client: Client,
253) -> Result<VarSets, ToolError> {
254 info!(
255 "Retrieving the initial list of variable sets for org {}.",
256 config.org
257 );
258 let params = vec![
259 ("page[size]", config.pagination.page_size.clone()),
260 ("page[number]", config.pagination.start_page.clone()),
261 ];
262 let url = Url::parse_with_params(
263 &format!("{}/organizations/{}/varsets", BASE_URL, config.org),
264 ¶ms,
265 )?;
266 let req = build_request(Method::Get, url.clone(), config, None);
267 let mut var_set_list: VarSets = match client.send(req).await {
268 Ok(mut res) => {
269 if res.status().is_success() {
270 info!("Variable sets for org {} retrieved.", config.org);
271 match res.body_json().await {
272 Ok(t) => t,
273 Err(e) => {
274 error!("{:#?}", e);
275 return Err(ToolError::General(anyhow::anyhow!(e)));
276 }
277 }
278 } else {
279 error!("Failed to fetch variable sets for org {}.", config.org);
280 let error =
281 res.body_string().await.map_err(surf_to_tool_error)?;
282 return Err(ToolError::General(anyhow::anyhow!(error)));
283 }
284 }
285 Err(e) => {
286 return Err(ToolError::General(anyhow::anyhow!(e)));
287 }
288 };
289 if let Some(meta) = var_set_list.meta.clone() {
291 check_pagination(meta, &mut var_set_list, url, config, client).await?;
292 }
293 info!("Finished retrieving variable sets.");
294 Ok(var_set_list)
295}
296
297pub async fn list_by_project(
298 config: &Core,
299 client: Client,
300) -> Result<VarSets, ToolError> {
301 if config.project.clone().is_none() {
302 return Err(ToolError::General(anyhow::anyhow!(
303 "No project specified in config"
304 )));
305 }
306 let project_id = config.project.clone().unwrap();
307 info!(
308 "Retrieving the initial list of variable sets for project {}.",
309 project_id
310 );
311 let params = vec![
312 ("page[size]", config.pagination.page_size.clone()),
313 ("page[number]", config.pagination.start_page.clone()),
314 ];
315 let url = Url::parse_with_params(
316 &format!("{}/projects/{}/varsets", BASE_URL, project_id),
317 ¶ms,
318 )?;
319 let req = build_request(Method::Get, url.clone(), config, None);
320 let mut var_set_list: VarSets = match client.send(req).await {
321 Ok(mut res) => {
322 if res.status().is_success() {
323 info!("Variable sets for project {} retrieved.", project_id);
324 match res.body_json().await {
325 Ok(t) => t,
326 Err(e) => {
327 error!("{:#?}", e);
328 return Err(ToolError::General(anyhow::anyhow!(e)));
329 }
330 }
331 } else {
332 error!(
333 "Failed to fetch variable sets for project {}.",
334 project_id
335 );
336 let error =
337 res.body_string().await.map_err(surf_to_tool_error)?;
338 return Err(ToolError::General(anyhow::anyhow!(error)));
339 }
340 }
341 Err(e) => {
342 return Err(ToolError::General(anyhow::anyhow!(e)));
343 }
344 };
345 if let Some(meta) = var_set_list.meta.clone() {
347 check_pagination(meta, &mut var_set_list, url, config, client).await?;
348 }
349 info!("Finished retrieving variable sets.");
350 Ok(var_set_list)
351}
352
353pub async fn create(
354 options: VarSetOptions,
355 config: &Core,
356 client: Client,
357) -> Result<(), ToolError> {
358 let url = Url::parse(&format!(
359 "{}/organizations/{}/varsets",
360 BASE_URL, config.org
361 ))?;
362 let req = build_request(
363 Method::Post,
364 url,
365 config,
366 Some(json!(VarSetOuter::new(options))),
367 );
368 let mut res = client.send(req).await.map_err(surf_to_tool_error)?;
369 if res.status().is_success() {
370 info!("Successfully created variable set");
371 } else {
372 error!("Failed to create variable set");
373 let error = res.body_string().await.map_err(surf_to_tool_error)?;
374 return Err(ToolError::General(anyhow::anyhow!(error)));
375 }
376 Ok(())
377}
378
379pub async fn apply_workspace(
380 variable_set_id: &str,
381 workspaces: Vec<Workspace>,
382 config: &Core,
383 client: Client,
384) -> Result<(), ToolError> {
385 let url = Url::parse(&format!(
386 "{}/varsets/{}/relationships/workspaces",
387 BASE_URL, variable_set_id
388 ))?;
389 let req = build_request(
390 Method::Post,
391 url,
392 config,
393 Some(json!(ApplyVarSetOuter::from(workspaces))),
394 );
395 let mut res = client.send(req).await.map_err(surf_to_tool_error)?;
396 if res.status().is_success() {
397 info!("Successfully applied workspaces to variable set");
398 } else {
399 error!("Failed to apply workspaces to variable set");
400 let error = res.body_string().await.map_err(surf_to_tool_error)?;
401 return Err(ToolError::General(anyhow::anyhow!(error)));
402 }
403 Ok(())
404}
405
406pub async fn remove_workspace(
407 variable_set_id: &str,
408 workspaces: Vec<Workspace>,
409 config: &Core,
410 client: Client,
411) -> Result<(), ToolError> {
412 let url = Url::parse(&format!(
413 "{}/varsets/{}/relationships/workspaces",
414 BASE_URL, variable_set_id
415 ))?;
416 let req = build_request(
417 Method::Delete,
418 url,
419 config,
420 Some(json!(WorkspacesOuter::from(workspaces))),
421 );
422 let mut res = client.send(req).await.map_err(surf_to_tool_error)?;
423 if res.status().is_success() {
424 info!("Successfully removed workspace from variable set");
425 } else {
426 error!("Failed to remove workspace from variable set");
427 let error = res.body_string().await.map_err(surf_to_tool_error)?;
428 return Err(ToolError::General(anyhow::anyhow!(error)));
429 }
430 Ok(())
431}