grafbase_local_backend/api/
deploy.rs1use super::client::create_client;
2use super::consts::{API_URL, PACKAGE_JSON, PROJECT_METADATA_FILE, TAR_CONTENT_TYPE};
3use super::errors::{ApiError, DeployError};
4use super::graphql::mutations::{
5 ArchiveFileSizeLimitExceededError, DailyDeploymentCountLimitExceededError, DeploymentCreate,
6 DeploymentCreateArguments, DeploymentCreateInput, DeploymentCreatePayload,
7};
8use super::types::ProjectMetadata;
9use crate::consts::USER_AGENT;
10use common::consts::GRAFBASE_DIRECTORY_NAME;
11use common::environment::Environment;
12use cynic::http::ReqwestExt;
13use cynic::{Id, MutationBuilder};
14use reqwest::{header, Body, Client};
15use tokio::fs::read_to_string;
16use tokio_util::codec::{BytesCodec, FramedRead};
17use tokio_util::compat::TokioAsyncReadCompatExt;
18
19pub async fn deploy() -> Result<(), ApiError> {
23 let environment = Environment::get();
24
25 let project_metadata_file_path = environment.project_dot_grafbase_path.join(PROJECT_METADATA_FILE);
26
27 match project_metadata_file_path.try_exists() {
28 Ok(true) => {}
29 Ok(false) => return Err(ApiError::UnlinkedProject),
30 Err(error) => return Err(ApiError::ReadProjectMetadataFile(error)),
31 }
32
33 let project_metadata_file = read_to_string(project_metadata_file_path)
34 .await
35 .map_err(ApiError::ReadProjectMetadataFile)?;
36
37 let project_metadata: ProjectMetadata =
38 serde_json::from_str(&project_metadata_file).map_err(|_| ApiError::CorruptProjectMetadataFile)?;
39
40 let (tar_file, tar_file_path) = tempfile::NamedTempFile::new()
41 .map_err(ApiError::CreateTempFile)?
42 .into_parts();
43
44 let tar_file: tokio::fs::File = tar_file.into();
45 let tar_file = tar_file.compat();
46
47 let mut tar = async_tar::Builder::new(tar_file);
48 tar.mode(async_tar::HeaderMode::Deterministic);
49
50 if environment.project_path.join(PACKAGE_JSON).exists() {
51 tar.append_path_with_name(environment.project_path.join(PACKAGE_JSON), PACKAGE_JSON)
52 .await
53 .map_err(ApiError::AppendToArchive)?;
54 }
55
56 tar.append_dir_all(GRAFBASE_DIRECTORY_NAME, &environment.project_grafbase_path)
57 .await
58 .map_err(ApiError::AppendToArchive)?;
59
60 let tar_file = tokio::fs::File::open(&tar_file_path)
61 .await
62 .map_err(ApiError::ReadArchive)?;
63
64 let client = create_client().await?;
65
66 #[allow(clippy::cast_possible_truncation)] let archive_file_size = tar_file.metadata().await.map_err(ApiError::ReadArchiveMetadata)?.len() as i32;
68
69 let operation = DeploymentCreate::build(DeploymentCreateArguments {
70 input: DeploymentCreateInput {
71 archive_file_size,
72 branch: None,
73 project_id: Id::new(project_metadata.project_id),
74 },
75 });
76
77 let response = client.post(API_URL).run_graphql(operation).await?;
78
79 let payload = response
80 .data
81 .ok_or(ApiError::UnauthorizedOrDeletedUser)?
82 .deployment_create;
83
84 match payload {
85 DeploymentCreatePayload::DeploymentCreateSuccess(payload) => {
86 let framed_tar = FramedRead::new(tar_file, BytesCodec::new());
87 let response = Client::new()
88 .put(payload.presigned_url)
89 .header(header::CONTENT_LENGTH, archive_file_size)
90 .header(header::CONTENT_TYPE, TAR_CONTENT_TYPE)
91 .header(header::USER_AGENT, USER_AGENT)
92 .body(Body::wrap_stream(framed_tar))
93 .send()
94 .await
95 .map_err(|_| ApiError::UploadError)?;
96
97 if !response.status().is_success() {
98 return Err(ApiError::UploadError);
99 }
100
101 Ok(())
102 }
103 DeploymentCreatePayload::ProjectDoesNotExistError(_) => Err(DeployError::ProjectDoesNotExist.into()),
104 DeploymentCreatePayload::ArchiveFileSizeLimitExceededError(ArchiveFileSizeLimitExceededError {
105 limit, ..
106 }) => Err(DeployError::ArchiveFileSizeLimitExceeded { limit }.into()),
107 DeploymentCreatePayload::DailyDeploymentCountLimitExceededError(DailyDeploymentCountLimitExceededError {
108 limit,
109 ..
110 }) => Err(DeployError::DailyDeploymentCountLimitExceeded { limit }.into()),
111 DeploymentCreatePayload::Unknown => Err(DeployError::Unknown.into()),
112 }
113}