Skip to main content

alien_aws_clients/aws/
codebuild.rs

1use crate::aws::aws_request_utils::{AwsRequestBuilderExt, AwsSignConfig};
2use crate::aws::credential_provider::AwsCredentialProvider;
3use alien_client_core::{ErrorData, Result};
4use alien_error::{Context, ContextError, IntoAlienError};
5use bon::Builder;
6#[cfg(feature = "test-utils")]
7use mockall::automock;
8use reqwest::{Client, StatusCode};
9use serde::de::DeserializeOwned;
10use serde::{Deserialize, Serialize};
11
12#[cfg_attr(feature = "test-utils", automock)]
13#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
14#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
15pub trait CodeBuildApi: Send + Sync + std::fmt::Debug {
16    async fn create_project(&self, request: CreateProjectRequest) -> Result<CreateProjectResponse>;
17    async fn delete_project(&self, request: DeleteProjectRequest) -> Result<DeleteProjectResponse>;
18    async fn update_project(&self, request: UpdateProjectRequest) -> Result<UpdateProjectResponse>;
19    async fn batch_get_projects(
20        &self,
21        request: BatchGetProjectsRequest,
22    ) -> Result<BatchGetProjectsResponse>;
23    async fn start_build(&self, request: StartBuildRequest) -> Result<StartBuildResponse>;
24    async fn stop_build(&self, request: StopBuildRequest) -> Result<StopBuildResponse>;
25    async fn batch_get_builds(
26        &self,
27        request: BatchGetBuildsRequest,
28    ) -> Result<BatchGetBuildsResponse>;
29    async fn batch_delete_builds(
30        &self,
31        request: BatchDeleteBuildsRequest,
32    ) -> Result<BatchDeleteBuildsResponse>;
33    async fn retry_build(&self, request: RetryBuildRequest) -> Result<RetryBuildResponse>;
34}
35
36// ---------------------------------------------------------------------------
37// CodeBuild client
38// ---------------------------------------------------------------------------
39#[derive(Debug, Clone)]
40pub struct CodeBuildClient {
41    client: Client,
42    credentials: AwsCredentialProvider,
43}
44
45impl CodeBuildClient {
46    pub fn new(client: Client, credentials: AwsCredentialProvider) -> Self {
47        Self {
48            client,
49            credentials,
50        }
51    }
52
53    /// Get the region for this CodeBuild client
54    pub fn region(&self) -> &str {
55        self.credentials.region()
56    }
57
58    fn sign_config(&self) -> AwsSignConfig {
59        AwsSignConfig {
60            service_name: "codebuild".into(),
61            region: self.credentials.region().to_string(),
62            credentials: self.credentials.get_credentials(),
63            signing_region: None,
64        }
65    }
66
67    fn get_base_url(&self) -> String {
68        if let Some(override_url) = self.credentials.get_service_endpoint_option("codebuild") {
69            override_url.to_string()
70        } else {
71            format!(
72                "https://codebuild.{}.amazonaws.com",
73                self.credentials.region()
74            )
75        }
76    }
77
78    // ------------------------- internal helpers -------------------------
79
80    async fn post_json<T: DeserializeOwned + Send + 'static>(
81        &self,
82        action: &str,
83        body: String,
84        resource: &str,
85    ) -> Result<T> {
86        self.credentials.ensure_fresh().await?;
87        let base_url = self.get_base_url();
88        let url = format!("{}/", base_url.trim_end_matches('/'));
89
90        let builder = self
91            .client
92            .post(&url)
93            .host(&format!(
94                "codebuild.{}.amazonaws.com",
95                self.credentials.region()
96            ))
97            .header("X-Amz-Target", format!("CodeBuild_20161006.{}", action))
98            .header("Content-Type", "application/x-amz-json-1.1")
99            .body(body.clone());
100
101        let result =
102            crate::aws::aws_request_utils::sign_send_json(builder, &self.sign_config()).await;
103
104        Self::map_result(result, action, resource, Some(&body))
105    }
106
107    async fn post_empty_response(&self, action: &str, body: String, resource: &str) -> Result<()> {
108        self.credentials.ensure_fresh().await?;
109        let base_url = self.get_base_url();
110        let url = format!("{}/", base_url.trim_end_matches('/'));
111
112        let builder = self
113            .client
114            .post(&url)
115            .host(&format!(
116                "codebuild.{}.amazonaws.com",
117                self.credentials.region()
118            ))
119            .header("X-Amz-Target", format!("CodeBuild_20161006.{}", action))
120            .header("Content-Type", "application/x-amz-json-1.1")
121            .body(body.clone());
122
123        let result =
124            crate::aws::aws_request_utils::sign_send_no_response(builder, &self.sign_config())
125                .await;
126
127        Self::map_result(result, action, resource, Some(&body))
128    }
129
130    fn map_result<T>(
131        result: Result<T>,
132        operation: &str,
133        resource: &str,
134        request_body: Option<&str>,
135    ) -> Result<T> {
136        match result {
137            Ok(v) => Ok(v),
138            Err(e) => {
139                if let Some(ErrorData::HttpResponseError {
140                    http_status,
141                    http_response_text: Some(ref text),
142                    ..
143                }) = &e.error
144                {
145                    let status = StatusCode::from_u16(*http_status)
146                        .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
147                    if let Some(mapped) =
148                        Self::map_codebuild_error(status, text, operation, resource, request_body)
149                    {
150                        Err(e.context(mapped))
151                    } else {
152                        // Couldn't parse CodeBuild error, use original error
153                        Err(e)
154                    }
155                } else {
156                    Err(e)
157                }
158            }
159        }
160    }
161
162    fn map_codebuild_error(
163        status: StatusCode,
164        body: &str,
165        _operation: &str,
166        resource: &str,
167        request_body: Option<&str>,
168    ) -> Option<ErrorData> {
169        let parsed: std::result::Result<CodeBuildErrorResponse, _> = serde_json::from_str(body);
170        let (code, message) = match parsed {
171            Ok(e) => {
172                let c = e.type_field.unwrap_or_else(|| "UnknownErrorCode".into());
173                let m = e.message.unwrap_or_else(|| "Unknown error".into());
174                (c, m)
175            }
176            Err(_) => {
177                // If we can't parse the response, return None to use original error
178                return None;
179            }
180        };
181
182        Some(match code.as_str() {
183            "AccessDeniedException" => ErrorData::RemoteAccessDenied {
184                resource_type: "Project".into(),
185                resource_name: resource.into(),
186            },
187            "AccountLimitExceededException" => ErrorData::QuotaExceeded { message },
188            "ThrottlingException" | "TooManyRequestsException" => {
189                ErrorData::RateLimitExceeded { message }
190            }
191            "ServiceUnavailable" | "InternalFailure" | "ServiceException" => {
192                ErrorData::RemoteServiceUnavailable { message }
193            }
194            "RequestTimeoutException" => ErrorData::Timeout { message },
195            "ResourceNotFoundException" => ErrorData::RemoteResourceNotFound {
196                resource_type: "Project".into(),
197                resource_name: resource.into(),
198            },
199            "ResourceAlreadyExistsException" => ErrorData::RemoteResourceConflict {
200                message,
201                resource_type: "Project".into(),
202                resource_name: resource.into(),
203            },
204            "InvalidInputException" | "NotAuthorized" | "ValidationError" => {
205                // IAM eventual consistency: CodeBuild may return InvalidInputException
206                // when it cannot yet assume a just-created service role. Treat these as
207                // transient so the executor retries with backoff.
208                if message.contains("sts:AssumeRole") || message.contains("not authorized") {
209                    ErrorData::RemoteServiceUnavailable { message }
210                } else {
211                    ErrorData::InvalidInput {
212                        message,
213                        field_name: None,
214                    }
215                }
216            }
217            _ => match status {
218                StatusCode::NOT_FOUND => ErrorData::RemoteResourceNotFound {
219                    resource_type: "Project".into(),
220                    resource_name: resource.into(),
221                },
222                StatusCode::CONFLICT => ErrorData::RemoteResourceConflict {
223                    message,
224                    resource_type: "Project".into(),
225                    resource_name: resource.into(),
226                },
227                StatusCode::FORBIDDEN | StatusCode::UNAUTHORIZED => ErrorData::RemoteAccessDenied {
228                    resource_type: "Project".into(),
229                    resource_name: resource.into(),
230                },
231                StatusCode::TOO_MANY_REQUESTS => ErrorData::RateLimitExceeded { message },
232                StatusCode::SERVICE_UNAVAILABLE
233                | StatusCode::BAD_GATEWAY
234                | StatusCode::GATEWAY_TIMEOUT => ErrorData::RemoteServiceUnavailable { message },
235                _ => ErrorData::HttpResponseError {
236                    message: format!("CodeBuild operation failed: {}", message),
237                    url: format!("codebuild.amazonaws.com"),
238                    http_status: status.as_u16(),
239                    http_response_text: Some(body.into()),
240                    http_request_text: request_body.map(|s| s.to_string()),
241                },
242            },
243        })
244    }
245}
246
247#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
248#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
249impl CodeBuildApi for CodeBuildClient {
250    async fn create_project(&self, request: CreateProjectRequest) -> Result<CreateProjectResponse> {
251        let body = serde_json::to_string(&request).into_alien_error().context(
252            ErrorData::SerializationError {
253                message: format!(
254                    "Failed to serialize CreateProjectRequest for project '{}'",
255                    request.name
256                ),
257            },
258        )?;
259        self.post_json("CreateProject", body, &request.name).await
260    }
261
262    async fn delete_project(&self, request: DeleteProjectRequest) -> Result<DeleteProjectResponse> {
263        let body = serde_json::to_string(&request).into_alien_error().context(
264            ErrorData::SerializationError {
265                message: format!(
266                    "Failed to serialize DeleteProjectRequest for project '{}'",
267                    request.name
268                ),
269            },
270        )?;
271        self.post_empty_response("DeleteProject", body, &request.name)
272            .await?;
273        Ok(DeleteProjectResponse {})
274    }
275
276    async fn update_project(&self, request: UpdateProjectRequest) -> Result<UpdateProjectResponse> {
277        let body = serde_json::to_string(&request).into_alien_error().context(
278            ErrorData::SerializationError {
279                message: format!(
280                    "Failed to serialize UpdateProjectRequest for project '{}'",
281                    request.name
282                ),
283            },
284        )?;
285        self.post_json("UpdateProject", body, &request.name).await
286    }
287
288    async fn batch_get_projects(
289        &self,
290        request: BatchGetProjectsRequest,
291    ) -> Result<BatchGetProjectsResponse> {
292        let body = serde_json::to_string(&request).into_alien_error().context(
293            ErrorData::SerializationError {
294                message: format!("Failed to serialize BatchGetProjectsRequest"),
295            },
296        )?;
297        self.post_json("BatchGetProjects", body, "").await
298    }
299
300    async fn start_build(&self, request: StartBuildRequest) -> Result<StartBuildResponse> {
301        let body = serde_json::to_string(&request).into_alien_error().context(
302            ErrorData::SerializationError {
303                message: format!(
304                    "Failed to serialize StartBuildRequest for project '{}'",
305                    request.project_name
306                ),
307            },
308        )?;
309        self.post_json("StartBuild", body, &request.project_name)
310            .await
311    }
312
313    async fn stop_build(&self, request: StopBuildRequest) -> Result<StopBuildResponse> {
314        let body = serde_json::to_string(&request).into_alien_error().context(
315            ErrorData::SerializationError {
316                message: format!(
317                    "Failed to serialize StopBuildRequest for build '{}'",
318                    request.id
319                ),
320            },
321        )?;
322        self.post_json("StopBuild", body, &request.id).await
323    }
324
325    async fn batch_get_builds(
326        &self,
327        request: BatchGetBuildsRequest,
328    ) -> Result<BatchGetBuildsResponse> {
329        let body = serde_json::to_string(&request).into_alien_error().context(
330            ErrorData::SerializationError {
331                message: format!("Failed to serialize BatchGetBuildsRequest"),
332            },
333        )?;
334        self.post_json("BatchGetBuilds", body, "").await
335    }
336
337    async fn batch_delete_builds(
338        &self,
339        request: BatchDeleteBuildsRequest,
340    ) -> Result<BatchDeleteBuildsResponse> {
341        let body = serde_json::to_string(&request).into_alien_error().context(
342            ErrorData::SerializationError {
343                message: format!("Failed to serialize BatchDeleteBuildsRequest"),
344            },
345        )?;
346        self.post_json("BatchDeleteBuilds", body, "").await
347    }
348
349    async fn retry_build(&self, request: RetryBuildRequest) -> Result<RetryBuildResponse> {
350        let body = serde_json::to_string(&request).into_alien_error().context(
351            ErrorData::SerializationError {
352                message: format!("Failed to serialize RetryBuildRequest"),
353            },
354        )?;
355        self.post_json("RetryBuild", body, &request.id.unwrap_or_default())
356            .await
357    }
358}
359
360// ---------------------------------------------------------------------------
361// Request / response payloads
362// ---------------------------------------------------------------------------
363
364#[derive(Debug, Deserialize)]
365struct CodeBuildErrorResponse {
366    #[serde(rename = "__type")]
367    type_field: Option<String>,
368    #[serde(rename = "message")]
369    message: Option<String>,
370}
371
372#[derive(Debug, Clone, Serialize, Builder)]
373#[serde(rename_all = "camelCase")]
374pub struct CreateProjectRequest {
375    pub name: String,
376    pub source: ProjectSource,
377    pub artifacts: ProjectArtifacts,
378    pub environment: ProjectEnvironment,
379    pub service_role: String,
380    #[serde(skip_serializing_if = "Option::is_none")]
381    pub description: Option<String>,
382    #[serde(skip_serializing_if = "Option::is_none")]
383    pub logs_config: Option<LogsConfig>,
384    #[serde(skip_serializing_if = "Option::is_none")]
385    pub tags: Option<Vec<Tag>>,
386}
387
388#[derive(Debug, Clone, Serialize, Builder, Deserialize)]
389#[serde(rename_all = "camelCase")]
390pub struct ProjectSource {
391    pub r#type: String,
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub location: Option<String>,
394    #[serde(skip_serializing_if = "Option::is_none")]
395    pub buildspec: Option<String>,
396    #[serde(skip_serializing_if = "Option::is_none")]
397    pub git_clone_depth: Option<i32>,
398    #[serde(skip_serializing_if = "Option::is_none")]
399    pub insecure_ssl: Option<bool>,
400}
401
402#[derive(Debug, Clone, Serialize, Builder, Deserialize)]
403#[serde(rename_all = "camelCase")]
404pub struct ProjectArtifacts {
405    pub r#type: String,
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub location: Option<String>,
408    #[serde(skip_serializing_if = "Option::is_none")]
409    pub path: Option<String>,
410    #[serde(skip_serializing_if = "Option::is_none")]
411    pub namespace_type: Option<String>,
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub name: Option<String>,
414    #[serde(skip_serializing_if = "Option::is_none")]
415    pub packaging: Option<String>,
416    #[serde(skip_serializing_if = "Option::is_none")]
417    pub encryption_disabled: Option<bool>,
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub artifact_identifier: Option<String>,
420    #[serde(skip_serializing_if = "Option::is_none")]
421    pub bucket_owner_access: Option<String>,
422}
423
424#[derive(Debug, Clone, Serialize, Builder, Deserialize)]
425#[serde(rename_all = "camelCase")]
426pub struct ProjectEnvironment {
427    pub r#type: String,
428    pub image: String,
429    pub compute_type: String,
430    #[serde(skip_serializing_if = "Option::is_none")]
431    pub environment_variables: Option<Vec<EnvironmentVariable>>,
432    #[serde(skip_serializing_if = "Option::is_none")]
433    pub privileged_mode: Option<bool>,
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub image_pull_credentials_type: Option<String>,
436}
437
438#[derive(Debug, Clone, Serialize, Builder, Deserialize)]
439#[serde(rename_all = "camelCase")]
440pub struct EnvironmentVariable {
441    pub name: String,
442    pub value: String,
443    pub r#type: Option<String>,
444}
445
446#[derive(Debug, Clone, Serialize, Builder, Deserialize)]
447#[serde(rename_all = "camelCase")]
448pub struct Tag {
449    pub key: String,
450    pub value: String,
451}
452
453#[derive(Debug, Deserialize)]
454#[serde(rename_all = "camelCase")]
455pub struct CreateProjectResponse {
456    pub project: Project,
457}
458
459#[derive(Debug, Deserialize, Clone, bon::Builder)]
460#[serde(rename_all = "camelCase")]
461pub struct Project {
462    pub name: Option<String>,
463    pub arn: Option<String>,
464    #[serde(skip_serializing_if = "Option::is_none")]
465    pub description: Option<String>,
466    #[serde(skip_serializing_if = "Option::is_none")]
467    pub source: Option<ProjectSource>,
468    #[serde(skip_serializing_if = "Option::is_none")]
469    pub artifacts: Option<ProjectArtifacts>,
470    #[serde(skip_serializing_if = "Option::is_none")]
471    pub environment: Option<ProjectEnvironment>,
472    #[serde(skip_serializing_if = "Option::is_none")]
473    pub service_role: Option<String>,
474    #[serde(skip_serializing_if = "Option::is_none")]
475    pub tags: Option<Vec<Tag>>,
476    #[serde(skip_serializing_if = "Option::is_none")]
477    pub vpc_config: Option<VpcConfig>,
478    #[serde(skip_serializing_if = "Option::is_none")]
479    pub webhook: Option<Webhook>,
480    #[serde(skip_serializing_if = "Option::is_none")]
481    pub logs_config: Option<LogsConfig>,
482    #[serde(skip_serializing_if = "Option::is_none")]
483    pub cache: Option<ProjectCache>,
484    #[serde(skip_serializing_if = "Option::is_none")]
485    pub timeout_in_minutes: Option<i32>,
486    #[serde(skip_serializing_if = "Option::is_none")]
487    pub queued_timeout_in_minutes: Option<i32>,
488    #[serde(skip_serializing_if = "Option::is_none")]
489    pub encryption_key: Option<String>,
490    #[serde(skip_serializing_if = "Option::is_none")]
491    pub secondary_sources: Option<Vec<ProjectSource>>,
492    #[serde(skip_serializing_if = "Option::is_none")]
493    pub secondary_artifacts: Option<Vec<ProjectArtifacts>>,
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub created: Option<f64>,
496    #[serde(skip_serializing_if = "Option::is_none")]
497    pub last_modified: Option<f64>,
498}
499
500#[derive(Debug, Clone, Serialize, Builder)]
501#[serde(rename_all = "camelCase")]
502pub struct DeleteProjectRequest {
503    pub name: String,
504}
505
506#[derive(Debug, Deserialize)]
507pub struct DeleteProjectResponse {}
508
509#[derive(Debug, Clone, Serialize, Builder)]
510#[serde(rename_all = "camelCase")]
511pub struct UpdateProjectRequest {
512    pub name: String,
513    #[serde(skip_serializing_if = "Option::is_none")]
514    pub description: Option<String>,
515    #[serde(skip_serializing_if = "Option::is_none")]
516    pub source: Option<ProjectSource>,
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub artifacts: Option<ProjectArtifacts>,
519    #[serde(skip_serializing_if = "Option::is_none")]
520    pub environment: Option<ProjectEnvironment>,
521    #[serde(skip_serializing_if = "Option::is_none")]
522    pub service_role: Option<String>,
523    #[serde(skip_serializing_if = "Option::is_none")]
524    pub tags: Option<Vec<Tag>>,
525    #[serde(skip_serializing_if = "Option::is_none")]
526    pub vpc_config: Option<VpcConfig>,
527    #[serde(skip_serializing_if = "Option::is_none")]
528    pub webhook: Option<Webhook>,
529    #[serde(skip_serializing_if = "Option::is_none")]
530    pub logs_config: Option<LogsConfig>,
531    #[serde(skip_serializing_if = "Option::is_none")]
532    pub cache: Option<ProjectCache>,
533    #[serde(skip_serializing_if = "Option::is_none")]
534    pub timeout_in_minutes: Option<i32>,
535    #[serde(skip_serializing_if = "Option::is_none")]
536    pub queued_timeout_in_minutes: Option<i32>,
537    #[serde(skip_serializing_if = "Option::is_none")]
538    pub encryption_key: Option<String>,
539}
540
541#[derive(Debug, Deserialize)]
542#[serde(rename_all = "camelCase")]
543pub struct UpdateProjectResponse {
544    pub project: Project,
545}
546
547#[derive(Debug, Clone, Serialize, Builder)]
548#[serde(rename_all = "camelCase")]
549pub struct StartBuildRequest {
550    pub project_name: String,
551    #[serde(skip_serializing_if = "Option::is_none")]
552    pub source_version: Option<String>,
553    #[serde(skip_serializing_if = "Option::is_none")]
554    pub buildspec_override: Option<String>,
555    #[serde(skip_serializing_if = "Option::is_none")]
556    pub environment_variables_override: Option<Vec<EnvironmentVariable>>,
557    #[serde(skip_serializing_if = "Option::is_none")]
558    pub compute_type_override: Option<String>,
559    #[serde(skip_serializing_if = "Option::is_none")]
560    pub environment_type_override: Option<String>,
561    #[serde(skip_serializing_if = "Option::is_none")]
562    pub image_override: Option<String>,
563    #[serde(skip_serializing_if = "Option::is_none")]
564    pub timeout_in_minutes_override: Option<i32>,
565}
566
567#[derive(Debug, Deserialize)]
568#[serde(rename_all = "camelCase")]
569pub struct StartBuildResponse {
570    pub build: Build,
571}
572
573#[derive(Debug, Deserialize, Clone)]
574#[serde(rename_all = "camelCase")]
575pub struct Build {
576    pub id: Option<String>,
577    pub arn: Option<String>,
578    pub build_number: Option<i64>,
579    pub build_status: Option<String>,
580    pub start_time: Option<f64>,
581    pub end_time: Option<f64>,
582    pub current_phase: Option<String>,
583    pub build_complete: bool,
584    pub initiator: Option<String>,
585    pub source_version: Option<String>,
586    pub project_name: Option<String>,
587    pub phases: Option<Vec<BuildPhase>>,
588    pub source: Option<ProjectSource>,
589    pub secondary_sources: Option<Vec<ProjectSource>>,
590    pub secondary_artifacts: Option<Vec<BuildArtifacts>>,
591    pub artifacts: Option<BuildArtifacts>,
592    pub logs: Option<LogsLocation>,
593    pub vpc_config: Option<VpcConfig>,
594    pub environment: Option<ProjectEnvironment>,
595}
596
597#[derive(Debug, Clone, Serialize, Builder)]
598#[serde(rename_all = "camelCase")]
599pub struct StopBuildRequest {
600    pub id: String,
601}
602
603#[derive(Debug, Deserialize, Clone)]
604#[serde(rename_all = "camelCase")]
605pub struct StopBuildResponse {
606    pub build: Build,
607}
608
609#[derive(Debug, Clone, Serialize, Builder)]
610#[serde(rename_all = "camelCase")]
611pub struct BatchGetBuildsRequest {
612    pub ids: Vec<String>,
613}
614
615#[derive(Debug, Deserialize)]
616#[serde(rename_all = "camelCase")]
617pub struct BatchGetBuildsResponse {
618    pub builds: Option<Vec<Build>>,
619    pub builds_not_found: Option<Vec<String>>,
620}
621
622#[derive(Debug, Clone, Serialize, Builder)]
623#[serde(rename_all = "camelCase")]
624pub struct BatchDeleteBuildsRequest {
625    pub ids: Vec<String>,
626}
627
628#[derive(Debug, Deserialize)]
629#[serde(rename_all = "camelCase")]
630pub struct BatchDeleteBuildsResponse {
631    pub builds_deleted: Option<Vec<String>>,
632    pub builds_not_deleted: Option<Vec<BuildNotDeleted>>,
633}
634
635#[derive(Debug, Deserialize)]
636#[serde(rename_all = "camelCase")]
637pub struct BuildNotDeleted {
638    pub id: Option<String>,
639    pub status_code: Option<String>,
640}
641
642#[derive(Debug, Clone, Serialize, Builder)]
643#[serde(rename_all = "camelCase")]
644pub struct RetryBuildRequest {
645    #[serde(skip_serializing_if = "Option::is_none")]
646    pub id: Option<String>,
647    #[serde(skip_serializing_if = "Option::is_none")]
648    pub idempotency_token: Option<String>,
649}
650
651#[derive(Debug, Deserialize)]
652#[serde(rename_all = "camelCase")]
653pub struct RetryBuildResponse {
654    pub build: Build,
655}
656
657#[derive(Debug, Clone, Serialize, Builder, Deserialize)]
658#[serde(rename_all = "camelCase")]
659pub struct VpcConfig {
660    #[serde(skip_serializing_if = "Option::is_none")]
661    pub vpc_id: Option<String>,
662    #[serde(skip_serializing_if = "Option::is_none")]
663    pub subnets: Option<Vec<String>>,
664    #[serde(skip_serializing_if = "Option::is_none")]
665    pub security_group_ids: Option<Vec<String>>,
666}
667
668#[derive(Debug, Clone, Serialize, Builder, Deserialize)]
669#[serde(rename_all = "camelCase")]
670pub struct Webhook {
671    #[serde(skip_serializing_if = "Option::is_none")]
672    pub url: Option<String>,
673    #[serde(skip_serializing_if = "Option::is_none")]
674    pub payload_url: Option<String>,
675    #[serde(skip_serializing_if = "Option::is_none")]
676    pub secret: Option<String>,
677    #[serde(skip_serializing_if = "Option::is_none")]
678    pub branch_filter: Option<String>,
679    #[serde(skip_serializing_if = "Option::is_none")]
680    pub filter_groups: Option<Vec<Vec<WebhookFilter>>>,
681    #[serde(skip_serializing_if = "Option::is_none")]
682    pub build_type: Option<String>,
683}
684
685#[derive(Debug, Clone, Serialize, Builder, Deserialize)]
686#[serde(rename_all = "camelCase")]
687pub struct WebhookFilter {
688    pub r#type: String,
689    pub pattern: String,
690    #[serde(skip_serializing_if = "Option::is_none")]
691    pub exclude_matched_pattern: Option<bool>,
692}
693
694#[derive(Debug, Clone, Serialize, Builder, Deserialize)]
695#[serde(rename_all = "camelCase")]
696pub struct LogsConfig {
697    #[serde(skip_serializing_if = "Option::is_none")]
698    pub cloud_watch_logs: Option<CloudWatchLogsConfig>,
699    #[serde(skip_serializing_if = "Option::is_none")]
700    pub s3_logs: Option<S3LogsConfig>,
701}
702
703#[derive(Debug, Clone, Serialize, Builder, Deserialize)]
704#[serde(rename_all = "camelCase")]
705pub struct CloudWatchLogsConfig {
706    pub status: String,
707    #[serde(skip_serializing_if = "Option::is_none")]
708    pub group_name: Option<String>,
709    #[serde(skip_serializing_if = "Option::is_none")]
710    pub stream_name: Option<String>,
711}
712
713#[derive(Debug, Clone, Serialize, Builder, Deserialize)]
714#[serde(rename_all = "camelCase")]
715pub struct S3LogsConfig {
716    pub status: String,
717    #[serde(skip_serializing_if = "Option::is_none")]
718    pub location: Option<String>,
719    #[serde(skip_serializing_if = "Option::is_none")]
720    pub encryption_disabled: Option<bool>,
721    #[serde(skip_serializing_if = "Option::is_none")]
722    pub bucket_owner_access: Option<String>,
723}
724
725#[derive(Debug, Clone, Serialize, Builder, Deserialize)]
726#[serde(rename_all = "camelCase")]
727pub struct ProjectCache {
728    pub r#type: String,
729    #[serde(skip_serializing_if = "Option::is_none")]
730    pub location: Option<String>,
731    #[serde(skip_serializing_if = "Option::is_none")]
732    pub modes: Option<Vec<String>>,
733}
734
735#[derive(Debug, Deserialize, Clone)]
736#[serde(rename_all = "camelCase")]
737pub struct BuildPhase {
738    pub phase_type: Option<String>,
739    pub phase_status: Option<String>,
740    pub start_time: Option<f64>,
741    pub end_time: Option<f64>,
742    pub duration_in_seconds: Option<i64>,
743}
744
745#[derive(Debug, Deserialize, Clone)]
746#[serde(rename_all = "camelCase")]
747pub struct BuildArtifacts {
748    #[serde(skip_serializing_if = "Option::is_none")]
749    pub location: Option<String>,
750    #[serde(skip_serializing_if = "Option::is_none")]
751    pub sha256sum: Option<String>,
752    #[serde(skip_serializing_if = "Option::is_none")]
753    pub md5sum: Option<String>,
754    #[serde(skip_serializing_if = "Option::is_none")]
755    pub override_artifact_name: Option<bool>,
756    #[serde(skip_serializing_if = "Option::is_none")]
757    pub encryption_disabled: Option<bool>,
758    #[serde(skip_serializing_if = "Option::is_none")]
759    pub artifact_identifier: Option<String>,
760}
761
762#[derive(Debug, Deserialize, Clone)]
763#[serde(rename_all = "camelCase")]
764pub struct LogsLocation {
765    #[serde(skip_serializing_if = "Option::is_none")]
766    pub group_name: Option<String>,
767    #[serde(skip_serializing_if = "Option::is_none")]
768    pub stream_name: Option<String>,
769    #[serde(skip_serializing_if = "Option::is_none")]
770    pub deep_link: Option<String>,
771}
772
773#[derive(Debug, Clone, Serialize, Builder)]
774#[serde(rename_all = "camelCase")]
775pub struct BatchGetProjectsRequest {
776    pub names: Vec<String>,
777}
778
779#[derive(Debug, Deserialize)]
780#[serde(rename_all = "camelCase")]
781pub struct BatchGetProjectsResponse {
782    #[serde(skip_serializing_if = "Option::is_none")]
783    pub projects: Option<Vec<Project>>,
784    #[serde(skip_serializing_if = "Option::is_none")]
785    pub projects_not_found: Option<Vec<String>>,
786}