1use std::{collections::HashSet, time::Duration};
2
3use anyhow::{Context, bail};
4use cynic::{MutationBuilder, QueryBuilder};
5use futures::StreamExt;
6use merge_streams::MergeStreams;
7use time::OffsetDateTime;
8use tracing::Instrument;
9use url::Url;
10use wasmer_config::package::PackageIdent;
11use wasmer_package::utils::from_bytes;
12use webc::Container;
13
14use crate::{
15 GraphQLApiFailure, WasmerClient,
16 types::{self, *},
17};
18
19pub async fn rotate_s3_secrets(
21 client: &WasmerClient,
22 app_id: types::Id,
23) -> Result<(), anyhow::Error> {
24 client
25 .run_graphql_strict(types::RotateS3SecretsForApp::build(
26 RotateS3SecretsForAppVariables { id: app_id },
27 ))
28 .await?;
29
30 Ok(())
31}
32
33pub async fn viewer_can_deploy_to_namespace(
34 client: &WasmerClient,
35 owner_name: &str,
36) -> Result<bool, anyhow::Error> {
37 client
38 .run_graphql_strict(types::ViewerCan::build(ViewerCanVariables {
39 action: OwnerAction::DeployApp,
40 owner_name,
41 }))
42 .await
43 .map(|v| v.viewer_can)
44}
45
46pub async fn redeploy_app_by_id(
47 client: &WasmerClient,
48 app_id: impl Into<String>,
49) -> Result<Option<DeployApp>, anyhow::Error> {
50 client
51 .run_graphql_strict(types::RedeployActiveApp::build(
52 RedeployActiveAppVariables {
53 id: types::Id::from(app_id),
54 },
55 ))
56 .await
57 .map(|v| v.redeploy_active_version.map(|v| v.app))
58}
59
60pub async fn list_bindings(
65 client: &WasmerClient,
66 name: &str,
67 version: Option<&str>,
68) -> Result<Vec<Bindings>, anyhow::Error> {
69 client
70 .run_graphql_strict(types::GetBindingsQuery::build(GetBindingsQueryVariables {
71 name,
72 version,
73 }))
74 .await
75 .and_then(|b| {
76 b.package_version
77 .ok_or(anyhow::anyhow!("No bindings found!"))
78 })
79 .map(|v| {
80 let mut bindings_packages = Vec::new();
81
82 for b in v.bindings.into_iter().flatten() {
83 let pkg = Bindings {
84 id: b.id.into_inner(),
85 url: b.url,
86 language: b.language,
87 generator: b.generator,
88 };
89 bindings_packages.push(pkg);
90 }
91
92 bindings_packages
93 })
94}
95
96pub async fn revoke_token(
98 client: &WasmerClient,
99 token: String,
100) -> Result<Option<bool>, anyhow::Error> {
101 client
102 .run_graphql_strict(types::RevokeToken::build(RevokeTokenVariables { token }))
103 .await
104 .map(|v| v.revoke_api_token.and_then(|v| v.success))
105}
106
107pub async fn create_nonce(
111 client: &WasmerClient,
112 name: String,
113 callback_url: String,
114) -> Result<Option<Nonce>, anyhow::Error> {
115 client
116 .run_graphql_strict(types::CreateNewNonce::build(CreateNewNonceVariables {
117 callback_url,
118 name,
119 }))
120 .await
121 .map(|v| v.new_nonce.map(|v| v.nonce))
122}
123
124pub async fn get_app_secret_value_by_id(
125 client: &WasmerClient,
126 secret_id: impl Into<String>,
127) -> Result<Option<String>, anyhow::Error> {
128 client
129 .run_graphql_strict(types::GetAppSecretValue::build(
130 GetAppSecretValueVariables {
131 id: types::Id::from(secret_id),
132 },
133 ))
134 .await
135 .map(|v| v.get_secret_value)
136}
137
138pub async fn get_app_secret_by_name(
139 client: &WasmerClient,
140 app_id: impl Into<String>,
141 name: impl Into<String>,
142) -> Result<Option<Secret>, anyhow::Error> {
143 client
144 .run_graphql_strict(types::GetAppSecret::build(GetAppSecretVariables {
145 app_id: types::Id::from(app_id),
146 secret_name: name.into(),
147 }))
148 .await
149 .map(|v| v.get_app_secret)
150}
151
152pub async fn upsert_app_secret(
154 client: &WasmerClient,
155 app_id: impl Into<String>,
156 name: impl Into<String>,
157 value: impl Into<String>,
158) -> Result<Option<UpsertAppSecretPayload>, anyhow::Error> {
159 client
160 .run_graphql_strict(types::UpsertAppSecret::build(UpsertAppSecretVariables {
161 app_id: cynic::Id::from(app_id.into()),
162 name: name.into().as_str(),
163 value: value.into().as_str(),
164 }))
165 .await
166 .map(|v| v.upsert_app_secret)
167}
168
169pub async fn upsert_app_secrets(
171 client: &WasmerClient,
172 app_id: impl Into<String>,
173 secrets: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
174) -> Result<Option<UpsertAppSecretsPayload>, anyhow::Error> {
175 client
176 .run_graphql_strict(types::UpsertAppSecrets::build(UpsertAppSecretsVariables {
177 app_id: cynic::Id::from(app_id.into()),
178 secrets: Some(
179 secrets
180 .into_iter()
181 .map(|(name, value)| SecretInput {
182 name: name.into(),
183 value: value.into(),
184 })
185 .collect(),
186 ),
187 }))
188 .await
189 .map(|v| v.upsert_app_secrets)
190}
191
192pub async fn get_all_app_secrets_filtered(
196 client: &WasmerClient,
197 app_id: impl Into<String>,
198 names: impl IntoIterator<Item = impl Into<String>>,
199) -> Result<Vec<Secret>, anyhow::Error> {
200 let mut vars = GetAllAppSecretsVariables {
201 after: None,
202 app_id: types::Id::from(app_id),
203 before: None,
204 first: None,
205 last: None,
206 offset: None,
207 names: Some(names.into_iter().map(|s| s.into()).collect()),
208 };
209
210 let mut all_secrets = Vec::<Secret>::new();
211
212 loop {
213 let page = get_app_secrets(client, vars.clone()).await?;
214 if page.edges.is_empty() {
215 break;
216 }
217
218 for edge in page.edges {
219 let edge = match edge {
220 Some(edge) => edge,
221 None => continue,
222 };
223 let version = match edge.node {
224 Some(item) => item,
225 None => continue,
226 };
227
228 all_secrets.push(version);
229
230 vars.after = Some(edge.cursor);
232 }
233 }
234
235 Ok(all_secrets)
236}
237
238pub async fn get_app_volumes(
240 client: &WasmerClient,
241 owner: impl Into<String>,
242 name: impl Into<String>,
243) -> Result<Vec<types::AppVersionVolume>, anyhow::Error> {
244 let vars = types::GetAppVolumesVars {
245 owner: owner.into(),
246 name: name.into(),
247 };
248 let res = client
249 .run_graphql_strict(types::GetAppVolumes::build(vars))
250 .await?;
251 let volumes = res
252 .get_deploy_app
253 .context("app not found")?
254 .active_version
255 .and_then(|v| v.volumes)
256 .unwrap_or_default()
257 .into_iter()
258 .flatten()
259 .collect();
260 Ok(volumes)
261}
262
263pub async fn get_app_databases(
265 client: &WasmerClient,
266 owner: impl Into<String>,
267 name: impl Into<String>,
268) -> Result<Vec<types::AppDatabase>, anyhow::Error> {
269 let vars = types::GetAppDatabasesVars {
270 owner: owner.into(),
271 name: name.into(),
272 after: None,
273 };
274 let res = client
275 .run_graphql_strict(types::GetAppDatabases::build(vars))
276 .await?;
277
278 let app = res.get_deploy_app.context("app not found")?;
279 let dbs = app.databases;
280 let _ = dbs.page_info;
281
282 let dbs = dbs
283 .edges
284 .into_iter()
285 .flatten()
286 .flat_map(|edge| edge.node)
287 .collect::<Vec<_>>();
288 Ok(dbs)
289}
290
291pub async fn get_app_s3_credentials(
295 client: &WasmerClient,
296 app_id: impl Into<String>,
297) -> Result<types::S3Credentials, anyhow::Error> {
298 let app_id = app_id.into();
299
300 let app1 = get_app_by_id(client, app_id.clone()).await?;
302
303 let vars = types::GetDeployAppVars {
304 owner: app1.owner.global_name,
305 name: app1.name,
306 };
307 client
308 .run_graphql_strict(types::GetDeployAppS3Credentials::build(vars))
309 .await?
310 .get_deploy_app
311 .context("app not found")?
312 .s3_credentials
313 .context("app does not have S3 credentials")
314}
315
316pub async fn get_all_app_regions(client: &WasmerClient) -> Result<Vec<AppRegion>, anyhow::Error> {
320 let mut vars = GetAllAppRegionsVariables {
321 after: None,
322 before: None,
323 first: None,
324 last: None,
325 offset: None,
326 };
327
328 let mut all_regions = Vec::<AppRegion>::new();
329
330 loop {
331 let page = get_regions(client, vars.clone()).await?;
332 if page.edges.is_empty() {
333 break;
334 }
335
336 for edge in page.edges {
337 let edge = match edge {
338 Some(edge) => edge,
339 None => continue,
340 };
341 let version = match edge.node {
342 Some(item) => item,
343 None => continue,
344 };
345
346 all_regions.push(version);
347
348 vars.after = Some(edge.cursor);
350 }
351 }
352
353 Ok(all_regions)
354}
355
356pub async fn get_regions(
358 client: &WasmerClient,
359 vars: GetAllAppRegionsVariables,
360) -> Result<AppRegionConnection, anyhow::Error> {
361 let res = client
362 .run_graphql_strict(types::GetAllAppRegions::build(vars))
363 .await?;
364 Ok(res.get_app_regions)
365}
366
367pub async fn get_all_app_secrets(
371 client: &WasmerClient,
372 app_id: impl Into<String>,
373) -> Result<Vec<Secret>, anyhow::Error> {
374 let mut vars = GetAllAppSecretsVariables {
375 after: None,
376 app_id: types::Id::from(app_id),
377 before: None,
378 first: None,
379 last: None,
380 offset: None,
381 names: None,
382 };
383
384 let mut all_secrets = Vec::<Secret>::new();
385
386 loop {
387 let page = get_app_secrets(client, vars.clone()).await?;
388 if page.edges.is_empty() {
389 break;
390 }
391
392 for edge in page.edges {
393 let edge = match edge {
394 Some(edge) => edge,
395 None => continue,
396 };
397 let version = match edge.node {
398 Some(item) => item,
399 None => continue,
400 };
401
402 all_secrets.push(version);
403
404 vars.after = Some(edge.cursor);
406 }
407 }
408
409 Ok(all_secrets)
410}
411
412pub async fn get_app_secrets(
414 client: &WasmerClient,
415 vars: GetAllAppSecretsVariables,
416) -> Result<SecretConnection, anyhow::Error> {
417 let res = client
418 .run_graphql_strict(types::GetAllAppSecrets::build(vars))
419 .await?;
420 res.get_app_secrets.context("app not found")
421}
422
423pub async fn delete_app_secret(
424 client: &WasmerClient,
425 secret_id: impl Into<String>,
426) -> Result<Option<DeleteAppSecretPayload>, anyhow::Error> {
427 client
428 .run_graphql_strict(types::DeleteAppSecret::build(DeleteAppSecretVariables {
429 id: types::Id::from(secret_id.into()),
430 }))
431 .await
432 .map(|v| v.delete_app_secret)
433}
434
435pub async fn fetch_webc_package(
440 client: &WasmerClient,
441 ident: &PackageIdent,
442 default_registry: &Url,
443) -> Result<Container, anyhow::Error> {
444 let url = match ident {
445 PackageIdent::Named(n) => Url::parse(&format!(
446 "{default_registry}/{}:{}",
447 n.full_name(),
448 n.version_or_default()
449 ))?,
450 PackageIdent::Hash(h) => match get_package_release(client, &h.to_string()).await? {
451 Some(webc) => Url::parse(&webc.webc_url)?,
452 None => anyhow::bail!("Could not find package with hash '{h}'"),
453 },
454 };
455
456 let data = client
457 .client
458 .get(url)
459 .header(reqwest::header::USER_AGENT, &client.user_agent)
460 .header(reqwest::header::ACCEPT, "application/webc")
461 .send()
462 .await?
463 .error_for_status()?
464 .bytes()
465 .await?;
466
467 from_bytes(data).context("failed to parse webc package")
468}
469
470pub async fn fetch_app_template_from_slug(
472 client: &WasmerClient,
473 slug: String,
474) -> Result<Option<types::AppTemplate>, anyhow::Error> {
475 client
476 .run_graphql_strict(types::GetAppTemplateFromSlug::build(
477 GetAppTemplateFromSlugVariables { slug },
478 ))
479 .await
480 .map(|v| v.get_app_template)
481}
482
483pub async fn fetch_app_templates_from_framework(
485 client: &WasmerClient,
486 framework_slug: String,
487 first: i32,
488 after: Option<String>,
489 sort_by: Option<types::AppTemplatesSortBy>,
490) -> Result<Option<types::AppTemplateConnection>, anyhow::Error> {
491 client
492 .run_graphql_strict(types::GetAppTemplatesFromFramework::build(
493 GetAppTemplatesFromFrameworkVars {
494 framework_slug,
495 first,
496 after,
497 sort_by,
498 },
499 ))
500 .await
501 .map(|r| r.get_app_templates)
502}
503
504pub async fn fetch_app_templates(
506 client: &WasmerClient,
507 category_slug: String,
508 first: i32,
509 after: Option<String>,
510 sort_by: Option<types::AppTemplatesSortBy>,
511) -> Result<Option<types::AppTemplateConnection>, anyhow::Error> {
512 client
513 .run_graphql_strict(types::GetAppTemplates::build(GetAppTemplatesVars {
514 category_slug,
515 first,
516 after,
517 sort_by,
518 }))
519 .await
520 .map(|r| r.get_app_templates)
521}
522
523pub fn fetch_all_app_templates(
527 client: &WasmerClient,
528 page_size: i32,
529 sort_by: Option<types::AppTemplatesSortBy>,
530) -> impl futures::Stream<Item = Result<Vec<types::AppTemplate>, anyhow::Error>> + '_ {
531 let vars = GetAppTemplatesVars {
532 category_slug: String::new(),
533 first: page_size,
534 sort_by,
535 after: None,
536 };
537
538 futures::stream::try_unfold(
539 Some(vars),
540 move |vars: Option<types::GetAppTemplatesVars>| async move {
541 let vars = match vars {
542 Some(vars) => vars,
543 None => return Ok(None),
544 };
545
546 let con = client
547 .run_graphql_strict(types::GetAppTemplates::build(vars.clone()))
548 .await?
549 .get_app_templates
550 .context("backend did not return any data")?;
551
552 let items = con
553 .edges
554 .into_iter()
555 .flatten()
556 .filter_map(|edge| edge.node)
557 .collect::<Vec<_>>();
558
559 let next_cursor = con
560 .page_info
561 .end_cursor
562 .filter(|_| con.page_info.has_next_page);
563
564 let next_vars = next_cursor.map(|after| types::GetAppTemplatesVars {
565 after: Some(after),
566 ..vars
567 });
568
569 #[allow(clippy::type_complexity)]
570 let res: Result<
571 Option<(Vec<types::AppTemplate>, Option<types::GetAppTemplatesVars>)>,
572 anyhow::Error,
573 > = Ok(Some((items, next_vars)));
574
575 res
576 },
577 )
578}
579
580pub fn fetch_all_app_templates_from_language(
584 client: &WasmerClient,
585 page_size: i32,
586 sort_by: Option<types::AppTemplatesSortBy>,
587 language: String,
588) -> impl futures::Stream<Item = Result<Vec<types::AppTemplate>, anyhow::Error>> + '_ {
589 let vars = GetAppTemplatesFromLanguageVars {
590 language_slug: language.clone().to_string(),
591 first: page_size,
592 sort_by,
593 after: None,
594 };
595
596 futures::stream::try_unfold(
597 Some(vars),
598 move |vars: Option<types::GetAppTemplatesFromLanguageVars>| async move {
599 let vars = match vars {
600 Some(vars) => vars,
601 None => return Ok(None),
602 };
603
604 let con = client
605 .run_graphql_strict(types::GetAppTemplatesFromLanguage::build(vars.clone()))
606 .await?
607 .get_app_templates
608 .context("backend did not return any data")?;
609
610 let items = con
611 .edges
612 .into_iter()
613 .flatten()
614 .filter_map(|edge| edge.node)
615 .collect::<Vec<_>>();
616
617 let next_cursor = con
618 .page_info
619 .end_cursor
620 .filter(|_| con.page_info.has_next_page);
621
622 let next_vars = next_cursor.map(|after| types::GetAppTemplatesFromLanguageVars {
623 after: Some(after),
624 ..vars
625 });
626
627 #[allow(clippy::type_complexity)]
628 let res: Result<
629 Option<(
630 Vec<types::AppTemplate>,
631 Option<types::GetAppTemplatesFromLanguageVars>,
632 )>,
633 anyhow::Error,
634 > = Ok(Some((items, next_vars)));
635
636 res
637 },
638 )
639}
640
641pub async fn fetch_app_template_languages(
643 client: &WasmerClient,
644 after: Option<String>,
645 first: Option<i32>,
646) -> Result<Option<types::TemplateLanguageConnection>, anyhow::Error> {
647 client
648 .run_graphql_strict(types::GetTemplateLanguages::build(
649 GetTemplateLanguagesVars { after, first },
650 ))
651 .await
652 .map(|r| r.get_template_languages)
653}
654
655pub fn fetch_all_app_template_languages(
659 client: &WasmerClient,
660 page_size: Option<i32>,
661) -> impl futures::Stream<Item = Result<Vec<types::TemplateLanguage>, anyhow::Error>> + '_ {
662 let vars = GetTemplateLanguagesVars {
663 after: None,
664 first: page_size,
665 };
666
667 futures::stream::try_unfold(
668 Some(vars),
669 move |vars: Option<types::GetTemplateLanguagesVars>| async move {
670 let vars = match vars {
671 Some(vars) => vars,
672 None => return Ok(None),
673 };
674
675 let con = client
676 .run_graphql_strict(types::GetTemplateLanguages::build(vars.clone()))
677 .await?
678 .get_template_languages
679 .context("backend did not return any data")?;
680
681 let items = con
682 .edges
683 .into_iter()
684 .flatten()
685 .filter_map(|edge| edge.node)
686 .collect::<Vec<_>>();
687
688 let next_cursor = con
689 .page_info
690 .end_cursor
691 .filter(|_| con.page_info.has_next_page);
692
693 let next_vars = next_cursor.map(|after| types::GetTemplateLanguagesVars {
694 after: Some(after),
695 ..vars
696 });
697
698 #[allow(clippy::type_complexity)]
699 let res: Result<
700 Option<(
701 Vec<types::TemplateLanguage>,
702 Option<types::GetTemplateLanguagesVars>,
703 )>,
704 anyhow::Error,
705 > = Ok(Some((items, next_vars)));
706
707 res
708 },
709 )
710}
711
712pub fn fetch_all_app_templates_from_framework(
716 client: &WasmerClient,
717 page_size: i32,
718 sort_by: Option<types::AppTemplatesSortBy>,
719 framework: String,
720) -> impl futures::Stream<Item = Result<Vec<types::AppTemplate>, anyhow::Error>> + '_ {
721 let vars = GetAppTemplatesFromFrameworkVars {
722 framework_slug: framework.clone().to_string(),
723 first: page_size,
724 sort_by,
725 after: None,
726 };
727
728 futures::stream::try_unfold(
729 Some(vars),
730 move |vars: Option<types::GetAppTemplatesFromFrameworkVars>| async move {
731 let vars = match vars {
732 Some(vars) => vars,
733 None => return Ok(None),
734 };
735
736 let con = client
737 .run_graphql_strict(types::GetAppTemplatesFromFramework::build(vars.clone()))
738 .await?
739 .get_app_templates
740 .context("backend did not return any data")?;
741
742 let items = con
743 .edges
744 .into_iter()
745 .flatten()
746 .filter_map(|edge| edge.node)
747 .collect::<Vec<_>>();
748
749 let next_cursor = con
750 .page_info
751 .end_cursor
752 .filter(|_| con.page_info.has_next_page);
753
754 let next_vars = next_cursor.map(|after| types::GetAppTemplatesFromFrameworkVars {
755 after: Some(after),
756 ..vars
757 });
758
759 #[allow(clippy::type_complexity)]
760 let res: Result<
761 Option<(
762 Vec<types::AppTemplate>,
763 Option<types::GetAppTemplatesFromFrameworkVars>,
764 )>,
765 anyhow::Error,
766 > = Ok(Some((items, next_vars)));
767
768 res
769 },
770 )
771}
772
773pub async fn fetch_app_template_frameworks(
775 client: &WasmerClient,
776 after: Option<String>,
777 first: Option<i32>,
778) -> Result<Option<types::TemplateFrameworkConnection>, anyhow::Error> {
779 client
780 .run_graphql_strict(types::GetTemplateFrameworks::build(
781 GetTemplateFrameworksVars { after, first },
782 ))
783 .await
784 .map(|r| r.get_template_frameworks)
785}
786
787pub fn fetch_all_app_template_frameworks(
791 client: &WasmerClient,
792 page_size: Option<i32>,
793) -> impl futures::Stream<Item = Result<Vec<types::TemplateFramework>, anyhow::Error>> + '_ {
794 let vars = GetTemplateFrameworksVars {
795 after: None,
796 first: page_size,
797 };
798
799 futures::stream::try_unfold(
800 Some(vars),
801 move |vars: Option<types::GetTemplateFrameworksVars>| async move {
802 let vars = match vars {
803 Some(vars) => vars,
804 None => return Ok(None),
805 };
806
807 let con = client
808 .run_graphql_strict(types::GetTemplateFrameworks::build(vars.clone()))
809 .await?
810 .get_template_frameworks
811 .context("backend did not return any data")?;
812
813 let items = con
814 .edges
815 .into_iter()
816 .flatten()
817 .filter_map(|edge| edge.node)
818 .collect::<Vec<_>>();
819
820 let next_cursor = con
821 .page_info
822 .end_cursor
823 .filter(|_| con.page_info.has_next_page);
824
825 let next_vars = next_cursor.map(|after| types::GetTemplateFrameworksVars {
826 after: Some(after),
827 ..vars
828 });
829
830 #[allow(clippy::type_complexity)]
831 let res: Result<
832 Option<(
833 Vec<types::TemplateFramework>,
834 Option<types::GetTemplateFrameworksVars>,
835 )>,
836 anyhow::Error,
837 > = Ok(Some((items, next_vars)));
838
839 res
840 },
841 )
842}
843
844#[derive(Debug)]
846pub enum UploadMethod {
847 R2,
848}
849
850impl UploadMethod {
851 pub fn as_str(&self) -> &'static str {
852 match self {
853 UploadMethod::R2 => "R2",
854 }
855 }
856}
857
858pub async fn get_signed_url_for_package_upload(
860 client: &WasmerClient,
861 expires_after_seconds: Option<i32>,
862 filename: Option<&str>,
863 name: Option<&str>,
864 version: Option<&str>,
865 method: Option<UploadMethod>,
866) -> Result<Option<SignedUrl>, anyhow::Error> {
867 client
868 .run_graphql_strict(types::GetSignedUrlForPackageUpload::build(
869 GetSignedUrlForPackageUploadVariables {
870 expires_after_seconds,
871 filename,
872 name,
873 version,
874 method: method.map(|m| m.as_str()),
875 },
876 ))
877 .await
878 .map(|r| r.get_signed_url_for_package_upload)
879}
880
881pub async fn generate_upload_url(
883 client: &WasmerClient,
884 filename: &str,
885 name: Option<&str>,
886 version: Option<&str>,
887 expires_after_seconds: Option<i32>,
888 method: Option<UploadMethod>,
889) -> Result<SignedUrl, anyhow::Error> {
890 let payload = client
891 .run_graphql_strict(types::GenerateUploadUrl::build(
892 GenerateUploadUrlVariables {
893 expires_after_seconds,
894 filename,
895 name,
896 version,
897 method: method.map(|m| m.as_str()),
898 },
899 ))
900 .await
901 .and_then(|res| {
902 res.generate_upload_url
903 .context("generateUploadUrl mutation did not return data")
904 })?;
905
906 Ok(payload.signed_url)
907}
908
909pub async fn autobuild_config_for_zip_upload(
911 client: &WasmerClient,
912 upload_url: &str,
913) -> Result<Option<types::AutobuildConfigForZipUploadPayload>, anyhow::Error> {
914 client
915 .run_graphql_strict(types::AutobuildConfigForZipUpload::build(
916 AutobuildConfigForZipUploadVariables { upload_url },
917 ))
918 .await
919 .map(|res| res.autobuild_config_for_zip_upload)
920}
921
922pub async fn deploy_via_autobuild(
924 client: &WasmerClient,
925 vars: DeployViaAutobuildVars,
926) -> Result<Option<types::DeployViaAutobuildPayload>, anyhow::Error> {
927 client
928 .run_graphql_strict(types::DeployViaAutobuild::build(vars))
929 .await
930 .map(|res| res.deploy_via_autobuild)
931}
932pub async fn push_package_release(
934 client: &WasmerClient,
935 name: Option<&str>,
936 namespace: &str,
937 signed_url: &str,
938 private: Option<bool>,
939) -> Result<Option<PushPackageReleasePayload>, anyhow::Error> {
940 client
941 .run_graphql_strict(types::PushPackageRelease::build(
942 types::PushPackageReleaseVariables {
943 name,
944 namespace,
945 private,
946 signed_url,
947 },
948 ))
949 .await
950 .map(|r| r.push_package_release)
951}
952
953#[allow(clippy::too_many_arguments)]
954pub async fn tag_package_release(
955 client: &WasmerClient,
956 description: Option<&str>,
957 homepage: Option<&str>,
958 license: Option<&str>,
959 license_file: Option<&str>,
960 manifest: Option<&str>,
961 name: &str,
962 namespace: Option<&str>,
963 package_release_id: &cynic::Id,
964 private: Option<bool>,
965 readme: Option<&str>,
966 repository: Option<&str>,
967 version: &str,
968) -> Result<Option<TagPackageReleasePayload>, anyhow::Error> {
969 client
970 .run_graphql_strict(types::TagPackageRelease::build(
971 types::TagPackageReleaseVariables {
972 description,
973 homepage,
974 license,
975 license_file,
976 manifest,
977 name,
978 namespace,
979 package_release_id,
980 private,
981 readme,
982 repository,
983 version,
984 },
985 ))
986 .await
987 .map(|r| r.tag_package_release)
988}
989
990pub async fn current_user(client: &WasmerClient) -> Result<Option<types::User>, anyhow::Error> {
992 client
993 .run_graphql(types::GetCurrentUser::build(()))
994 .await
995 .map(|x| x.viewer)
996}
997
998pub async fn current_user_with_namespaces(
1002 client: &WasmerClient,
1003 namespace_role: Option<types::GrapheneRole>,
1004) -> Result<types::UserWithNamespaces, anyhow::Error> {
1005 client
1006 .run_graphql(types::GetCurrentUserWithNamespaces::build(
1007 types::GetCurrentUserWithNamespacesVars { namespace_role },
1008 ))
1009 .await?
1010 .viewer
1011 .context("not logged in")
1012}
1013
1014pub async fn get_app(
1016 client: &WasmerClient,
1017 owner: String,
1018 name: String,
1019) -> Result<Option<types::DeployApp>, anyhow::Error> {
1020 client
1021 .run_graphql(types::GetDeployApp::build(types::GetDeployAppVars {
1022 name,
1023 owner,
1024 }))
1025 .await
1026 .map(|x| x.get_deploy_app)
1027}
1028
1029pub async fn get_app_by_alias(
1031 client: &WasmerClient,
1032 alias: String,
1033) -> Result<Option<types::DeployApp>, anyhow::Error> {
1034 client
1035 .run_graphql(types::GetDeployAppByAlias::build(
1036 types::GetDeployAppByAliasVars { alias },
1037 ))
1038 .await
1039 .map(|x| x.get_app_by_global_alias)
1040}
1041
1042pub async fn get_app_version(
1044 client: &WasmerClient,
1045 owner: String,
1046 name: String,
1047 version: String,
1048) -> Result<Option<types::DeployAppVersion>, anyhow::Error> {
1049 client
1050 .run_graphql(types::GetDeployAppVersion::build(
1051 types::GetDeployAppVersionVars {
1052 name,
1053 owner,
1054 version,
1055 },
1056 ))
1057 .await
1058 .map(|x| x.get_deploy_app_version)
1059}
1060
1061pub async fn get_app_with_version(
1063 client: &WasmerClient,
1064 owner: String,
1065 name: String,
1066 version: String,
1067) -> Result<GetDeployAppAndVersion, anyhow::Error> {
1068 client
1069 .run_graphql(types::GetDeployAppAndVersion::build(
1070 types::GetDeployAppAndVersionVars {
1071 name,
1072 owner,
1073 version,
1074 },
1075 ))
1076 .await
1077}
1078
1079pub async fn get_app_and_package_by_name(
1081 client: &WasmerClient,
1082 vars: types::GetPackageAndAppVars,
1083) -> Result<(Option<types::Package>, Option<types::DeployApp>), anyhow::Error> {
1084 let res = client
1085 .run_graphql(types::GetPackageAndApp::build(vars))
1086 .await?;
1087 Ok((res.get_package, res.get_deploy_app))
1088}
1089
1090pub async fn get_deploy_apps(
1092 client: &WasmerClient,
1093 vars: types::GetDeployAppsVars,
1094) -> Result<DeployAppConnection, anyhow::Error> {
1095 let res = client
1096 .run_graphql(types::GetDeployApps::build(vars))
1097 .await?;
1098 res.get_deploy_apps.context("no apps returned")
1099}
1100
1101pub fn get_deploy_apps_stream(
1103 client: &WasmerClient,
1104 vars: types::GetDeployAppsVars,
1105) -> impl futures::Stream<Item = Result<Vec<DeployApp>, anyhow::Error>> + '_ {
1106 futures::stream::try_unfold(
1107 Some(vars),
1108 move |vars: Option<types::GetDeployAppsVars>| async move {
1109 let vars = match vars {
1110 Some(vars) => vars,
1111 None => return Ok(None),
1112 };
1113
1114 let page = get_deploy_apps(client, vars.clone()).await?;
1115
1116 let end_cursor = page.page_info.end_cursor;
1117
1118 let items = page
1119 .edges
1120 .into_iter()
1121 .filter_map(|x| x.and_then(|x| x.node))
1122 .collect::<Vec<_>>();
1123
1124 let new_vars = end_cursor.map(|c| types::GetDeployAppsVars {
1125 after: Some(c),
1126 ..vars
1127 });
1128
1129 Ok(Some((items, new_vars)))
1130 },
1131 )
1132}
1133
1134pub async fn get_deploy_app_versions(
1136 client: &WasmerClient,
1137 vars: GetDeployAppVersionsVars,
1138) -> Result<DeployAppVersionConnection, anyhow::Error> {
1139 let res = client
1140 .run_graphql_strict(types::GetDeployAppVersions::build(vars))
1141 .await?;
1142 let versions = res.get_deploy_app.context("app not found")?.versions;
1143 Ok(versions)
1144}
1145
1146pub async fn app_deployments(
1148 client: &WasmerClient,
1149 vars: types::GetAppDeploymentsVariables,
1150) -> Result<Vec<types::Deployment>, anyhow::Error> {
1151 let res = client
1152 .run_graphql_strict(types::GetAppDeployments::build(vars))
1153 .await?;
1154 let builds = res
1155 .get_deploy_app
1156 .and_then(|x| x.deployments)
1157 .context("no data returned")?
1158 .edges
1159 .into_iter()
1160 .flatten()
1161 .filter_map(|x| x.node)
1162 .collect();
1163
1164 Ok(builds)
1165}
1166
1167pub async fn app_deployment(
1169 client: &WasmerClient,
1170 id: String,
1171) -> Result<types::AutobuildRepository, anyhow::Error> {
1172 let node = get_node(client, id.clone())
1173 .await?
1174 .with_context(|| format!("app deployment with id '{id}' not found"))?;
1175 match node {
1176 types::Node::AutobuildRepository(x) => Ok(*x),
1177 _ => anyhow::bail!("invalid node type returned"),
1178 }
1179}
1180
1181pub async fn all_app_versions(
1185 client: &WasmerClient,
1186 owner: String,
1187 name: String,
1188) -> Result<Vec<DeployAppVersion>, anyhow::Error> {
1189 let mut vars = GetDeployAppVersionsVars {
1190 owner,
1191 name,
1192 offset: None,
1193 before: None,
1194 after: None,
1195 first: Some(10),
1196 last: None,
1197 sort_by: None,
1198 };
1199
1200 let mut all_versions = Vec::<DeployAppVersion>::new();
1201
1202 loop {
1203 let page = get_deploy_app_versions(client, vars.clone()).await?;
1204 if page.edges.is_empty() {
1205 break;
1206 }
1207
1208 for edge in page.edges {
1209 let edge = match edge {
1210 Some(edge) => edge,
1211 None => continue,
1212 };
1213 let version = match edge.node {
1214 Some(item) => item,
1215 None => continue,
1216 };
1217
1218 if all_versions.iter().any(|v| v.id == version.id) == false {
1220 all_versions.push(version);
1221 }
1222
1223 vars.after = Some(edge.cursor);
1225 }
1226 }
1227
1228 Ok(all_versions)
1229}
1230
1231pub async fn get_deploy_app_versions_by_id(
1233 client: &WasmerClient,
1234 vars: types::GetDeployAppVersionsByIdVars,
1235) -> Result<DeployAppVersionConnection, anyhow::Error> {
1236 let res = client
1237 .run_graphql_strict(types::GetDeployAppVersionsById::build(vars))
1238 .await?;
1239 let versions = res
1240 .node
1241 .context("app not found")?
1242 .into_app()
1243 .context("invalid node type returned")?
1244 .versions;
1245 Ok(versions)
1246}
1247
1248pub async fn all_app_versions_by_id(
1252 client: &WasmerClient,
1253 app_id: impl Into<String>,
1254) -> Result<Vec<DeployAppVersion>, anyhow::Error> {
1255 let mut vars = types::GetDeployAppVersionsByIdVars {
1256 id: cynic::Id::new(app_id),
1257 offset: None,
1258 before: None,
1259 after: None,
1260 first: Some(10),
1261 last: None,
1262 sort_by: None,
1263 };
1264
1265 let mut all_versions = Vec::<DeployAppVersion>::new();
1266
1267 loop {
1268 let page = get_deploy_app_versions_by_id(client, vars.clone()).await?;
1269 if page.edges.is_empty() {
1270 break;
1271 }
1272
1273 for edge in page.edges {
1274 let edge = match edge {
1275 Some(edge) => edge,
1276 None => continue,
1277 };
1278 let version = match edge.node {
1279 Some(item) => item,
1280 None => continue,
1281 };
1282
1283 if all_versions.iter().any(|v| v.id == version.id) == false {
1285 all_versions.push(version);
1286 }
1287
1288 vars.after = Some(edge.cursor);
1290 }
1291 }
1292
1293 Ok(all_versions)
1294}
1295
1296pub async fn app_version_activate(
1298 client: &WasmerClient,
1299 version: String,
1300) -> Result<DeployApp, anyhow::Error> {
1301 let res = client
1302 .run_graphql_strict(types::MarkAppVersionAsActive::build(
1303 types::MarkAppVersionAsActiveVars {
1304 input: types::MarkAppVersionAsActiveInput {
1305 app_version: version.into(),
1306 },
1307 },
1308 ))
1309 .await?;
1310 res.mark_app_version_as_active
1311 .context("app not found")
1312 .map(|x| x.app)
1313}
1314
1315pub async fn get_node(
1317 client: &WasmerClient,
1318 id: String,
1319) -> Result<Option<types::Node>, anyhow::Error> {
1320 client
1321 .run_graphql(types::GetNode::build(types::GetNodeVars { id: id.into() }))
1322 .await
1323 .map(|x| x.node)
1324}
1325
1326pub async fn get_app_by_id(
1328 client: &WasmerClient,
1329 app_id: String,
1330) -> Result<DeployApp, anyhow::Error> {
1331 get_app_by_id_opt(client, app_id)
1332 .await?
1333 .context("app not found")
1334}
1335
1336pub async fn get_app_by_id_opt(
1338 client: &WasmerClient,
1339 app_id: String,
1340) -> Result<Option<DeployApp>, anyhow::Error> {
1341 let app_opt = client
1342 .run_graphql(types::GetDeployAppById::build(
1343 types::GetDeployAppByIdVars {
1344 app_id: app_id.into(),
1345 },
1346 ))
1347 .await?
1348 .app;
1349
1350 if let Some(app) = app_opt {
1351 let app = app.into_deploy_app().context("app conversion failed")?;
1352 Ok(Some(app))
1353 } else {
1354 Ok(None)
1355 }
1356}
1357
1358pub async fn get_app_with_version_by_id(
1360 client: &WasmerClient,
1361 app_id: String,
1362 version_id: String,
1363) -> Result<(DeployApp, DeployAppVersion), anyhow::Error> {
1364 let res = client
1365 .run_graphql(types::GetDeployAppAndVersionById::build(
1366 types::GetDeployAppAndVersionByIdVars {
1367 app_id: app_id.into(),
1368 version_id: version_id.into(),
1369 },
1370 ))
1371 .await?;
1372
1373 let app = res
1374 .app
1375 .context("app not found")?
1376 .into_deploy_app()
1377 .context("app conversion failed")?;
1378 let version = res
1379 .version
1380 .context("version not found")?
1381 .into_deploy_app_version()
1382 .context("version conversion failed")?;
1383
1384 Ok((app, version))
1385}
1386
1387pub async fn get_app_version_by_id(
1389 client: &WasmerClient,
1390 version_id: String,
1391) -> Result<DeployAppVersion, anyhow::Error> {
1392 client
1393 .run_graphql(types::GetDeployAppVersionById::build(
1394 types::GetDeployAppVersionByIdVars {
1395 version_id: version_id.into(),
1396 },
1397 ))
1398 .await?
1399 .version
1400 .context("app not found")?
1401 .into_deploy_app_version()
1402 .context("app version conversion failed")
1403}
1404
1405pub async fn get_app_version_by_id_with_app(
1406 client: &WasmerClient,
1407 version_id: String,
1408) -> Result<(DeployApp, DeployAppVersion), anyhow::Error> {
1409 let version = client
1410 .run_graphql(types::GetDeployAppVersionById::build(
1411 types::GetDeployAppVersionByIdVars {
1412 version_id: version_id.into(),
1413 },
1414 ))
1415 .await?
1416 .version
1417 .context("app not found")?
1418 .into_deploy_app_version()
1419 .context("app version conversion failed")?;
1420
1421 let app_id = version
1422 .app
1423 .as_ref()
1424 .context("could not load app for version")?
1425 .id
1426 .clone();
1427
1428 let app = get_app_by_id(client, app_id.into_inner()).await?;
1429
1430 Ok((app, version))
1431}
1432
1433pub async fn user_apps_page(
1434 client: &WasmerClient,
1435 sort: types::DeployAppsSortBy,
1436 cursor: Option<String>,
1437) -> Result<Paginated<types::DeployApp>, anyhow::Error> {
1438 let user = client
1439 .run_graphql(types::GetCurrentUserWithApps::build(
1440 GetCurrentUserWithAppsVars {
1441 after: cursor,
1442 first: Some(10),
1443 sort: Some(sort),
1444 },
1445 ))
1446 .await?
1447 .viewer
1448 .context("not logged in")?;
1449
1450 let apps: Vec<_> = user
1451 .apps
1452 .edges
1453 .into_iter()
1454 .flatten()
1455 .filter_map(|x| x.node)
1456 .collect();
1457
1458 let out = Paginated {
1459 items: apps,
1460 next_cursor: user.apps.page_info.end_cursor,
1461 };
1462
1463 Ok(out)
1464}
1465
1466pub async fn user_apps(
1470 client: &WasmerClient,
1471 sort: types::DeployAppsSortBy,
1472) -> impl futures::Stream<Item = Result<Vec<types::DeployApp>, anyhow::Error>> + '_ {
1473 futures::stream::try_unfold(None, move |cursor| async move {
1474 let user = client
1475 .run_graphql(types::GetCurrentUserWithApps::build(
1476 GetCurrentUserWithAppsVars {
1477 first: Some(10),
1478 after: cursor,
1479 sort: Some(sort),
1480 },
1481 ))
1482 .await?
1483 .viewer
1484 .context("not logged in")?;
1485
1486 let apps: Vec<_> = user
1487 .apps
1488 .edges
1489 .into_iter()
1490 .flatten()
1491 .filter_map(|x| x.node)
1492 .collect();
1493
1494 let cursor = user.apps.page_info.end_cursor;
1495
1496 if apps.is_empty() {
1497 Ok(None)
1498 } else {
1499 Ok(Some((apps, cursor)))
1500 }
1501 })
1502}
1503
1504pub async fn user_accessible_apps(
1506 client: &WasmerClient,
1507 sort: types::DeployAppsSortBy,
1508) -> Result<
1509 impl futures::Stream<Item = Result<Vec<types::DeployApp>, anyhow::Error>> + '_,
1510 anyhow::Error,
1511> {
1512 let user_apps = user_apps(client, sort).await;
1513
1514 let namespace_res = client
1516 .run_graphql(types::GetCurrentUserWithNamespaces::build(
1517 types::GetCurrentUserWithNamespacesVars {
1518 namespace_role: None,
1519 },
1520 ))
1521 .await?;
1522 let active_user = namespace_res.viewer.context("not logged in")?;
1523 let namespace_names = active_user
1524 .namespaces
1525 .edges
1526 .iter()
1527 .filter_map(|edge| edge.as_ref())
1528 .filter_map(|edge| edge.node.as_ref())
1529 .map(|node| node.name.clone())
1530 .collect::<Vec<_>>();
1531
1532 let mut ns_apps = vec![];
1533 for ns in namespace_names {
1534 let apps = namespace_apps(client, ns, sort).await;
1535 ns_apps.push(apps);
1536 }
1537
1538 Ok((user_apps, ns_apps.merge()).merge())
1539}
1540
1541pub async fn namespace_apps_page(
1545 client: &WasmerClient,
1546 namespace: String,
1547 sort: types::DeployAppsSortBy,
1548 cursor: Option<String>,
1549) -> Result<Paginated<types::DeployApp>, anyhow::Error> {
1550 let namespace = namespace.clone();
1551
1552 let res = client
1553 .run_graphql(types::GetNamespaceApps::build(GetNamespaceAppsVars {
1554 name: namespace.to_string(),
1555 after: cursor,
1556 sort: Some(sort),
1557 }))
1558 .await?
1559 .get_namespace
1560 .context("namespace not found")?
1561 .apps;
1562
1563 let apps: Vec<_> = res
1564 .edges
1565 .into_iter()
1566 .flatten()
1567 .filter_map(|x| x.node)
1568 .collect();
1569
1570 let out = Paginated {
1571 items: apps,
1572 next_cursor: res.page_info.end_cursor,
1573 };
1574
1575 Ok(out)
1576}
1577
1578pub async fn namespace_apps(
1582 client: &WasmerClient,
1583 namespace: String,
1584 sort: types::DeployAppsSortBy,
1585) -> impl futures::Stream<Item = Result<Vec<types::DeployApp>, anyhow::Error>> + '_ {
1586 let namespace = namespace.clone();
1587
1588 futures::stream::try_unfold((None, namespace), move |(cursor, namespace)| async move {
1589 let res = client
1590 .run_graphql(types::GetNamespaceApps::build(GetNamespaceAppsVars {
1591 name: namespace.to_string(),
1592 after: cursor,
1593 sort: Some(sort),
1594 }))
1595 .await?;
1596
1597 let ns = res
1598 .get_namespace
1599 .with_context(|| format!("failed to get namespace '{namespace}'"))?;
1600
1601 let apps: Vec<_> = ns
1602 .apps
1603 .edges
1604 .into_iter()
1605 .flatten()
1606 .filter_map(|x| x.node)
1607 .collect();
1608
1609 let cursor = ns.apps.page_info.end_cursor;
1610
1611 if apps.is_empty() {
1612 Ok(None)
1613 } else {
1614 Ok(Some((apps, (cursor, namespace))))
1615 }
1616 })
1617}
1618
1619pub async fn publish_deploy_app(
1621 client: &WasmerClient,
1622 vars: PublishDeployAppVars,
1623) -> Result<DeployAppVersion, anyhow::Error> {
1624 let res = client
1625 .run_graphql_raw(types::PublishDeployApp::build(vars))
1626 .await?;
1627
1628 if let Some(app) = res
1629 .data
1630 .and_then(|d| d.publish_deploy_app)
1631 .map(|d| d.deploy_app_version)
1632 {
1633 Ok(app)
1634 } else {
1635 Err(GraphQLApiFailure::from_errors(
1636 "could not publish app",
1637 res.errors,
1638 ))
1639 }
1640}
1641
1642pub async fn delete_app(client: &WasmerClient, app_id: String) -> Result<(), anyhow::Error> {
1644 let res = client
1645 .run_graphql_strict(types::DeleteApp::build(types::DeleteAppVars {
1646 app_id: app_id.into(),
1647 }))
1648 .await?
1649 .delete_app
1650 .context("API did not return data for the delete_app mutation")?;
1651
1652 if !res.success {
1653 bail!("App deletion failed for an unknown reason");
1654 }
1655
1656 Ok(())
1657}
1658
1659pub async fn user_namespaces(
1661 client: &WasmerClient,
1662) -> Result<Vec<types::Namespace>, anyhow::Error> {
1663 let user = client
1664 .run_graphql(types::GetCurrentUserWithNamespaces::build(
1665 types::GetCurrentUserWithNamespacesVars {
1666 namespace_role: None,
1667 },
1668 ))
1669 .await?
1670 .viewer
1671 .context("not logged in")?;
1672
1673 let ns = user
1674 .namespaces
1675 .edges
1676 .into_iter()
1677 .flatten()
1678 .filter_map(|x| x.node)
1680 .collect();
1681
1682 Ok(ns)
1683}
1684
1685pub async fn get_namespace(
1687 client: &WasmerClient,
1688 name: String,
1689) -> Result<Option<types::Namespace>, anyhow::Error> {
1690 client
1691 .run_graphql(types::GetNamespace::build(types::GetNamespaceVars { name }))
1692 .await
1693 .map(|x| x.get_namespace)
1694}
1695
1696pub async fn create_namespace(
1698 client: &WasmerClient,
1699 vars: CreateNamespaceVars,
1700) -> Result<types::Namespace, anyhow::Error> {
1701 client
1702 .run_graphql(types::CreateNamespace::build(vars))
1703 .await?
1704 .create_namespace
1705 .map(|x| x.namespace)
1706 .context("no namespace returned")
1707}
1708
1709pub async fn get_package(
1711 client: &WasmerClient,
1712 name: String,
1713) -> Result<Option<types::Package>, anyhow::Error> {
1714 client
1715 .run_graphql_strict(types::GetPackage::build(types::GetPackageVars { name }))
1716 .await
1717 .map(|x| x.get_package)
1718}
1719
1720pub async fn get_package_version(
1722 client: &WasmerClient,
1723 name: String,
1724 version: String,
1725) -> Result<Option<types::PackageVersionWithPackage>, anyhow::Error> {
1726 client
1727 .run_graphql_strict(types::GetPackageVersion::build(
1728 types::GetPackageVersionVars { name, version },
1729 ))
1730 .await
1731 .map(|x| x.get_package_version)
1732}
1733
1734pub async fn get_package_versions(
1736 client: &WasmerClient,
1737 vars: types::AllPackageVersionsVars,
1738) -> Result<PackageVersionConnection, anyhow::Error> {
1739 let res = client
1740 .run_graphql(types::GetAllPackageVersions::build(vars))
1741 .await?;
1742 Ok(res.all_package_versions)
1743}
1744
1745pub async fn get_package_release(
1747 client: &WasmerClient,
1748 hash: &str,
1749) -> Result<Option<types::PackageWebc>, anyhow::Error> {
1750 let hash = hash.trim_start_matches("sha256:");
1751 client
1752 .run_graphql_strict(types::GetPackageRelease::build(
1753 types::GetPackageReleaseVars {
1754 hash: hash.to_string(),
1755 },
1756 ))
1757 .await
1758 .map(|x| x.get_package_release)
1759}
1760
1761pub async fn get_package_releases(
1762 client: &WasmerClient,
1763 vars: types::AllPackageReleasesVars,
1764) -> Result<types::PackageWebcConnection, anyhow::Error> {
1765 let res = client
1766 .run_graphql(types::GetAllPackageReleases::build(vars))
1767 .await?;
1768 Ok(res.all_package_releases)
1769}
1770
1771pub fn get_package_versions_stream(
1773 client: &WasmerClient,
1774 vars: types::AllPackageVersionsVars,
1775) -> impl futures::Stream<Item = Result<Vec<types::PackageVersionWithPackage>, anyhow::Error>> + '_
1776{
1777 futures::stream::try_unfold(
1778 Some(vars),
1779 move |vars: Option<types::AllPackageVersionsVars>| async move {
1780 let vars = match vars {
1781 Some(vars) => vars,
1782 None => return Ok(None),
1783 };
1784
1785 let page = get_package_versions(client, vars.clone()).await?;
1786
1787 let end_cursor = page.page_info.end_cursor;
1788
1789 let items = page
1790 .edges
1791 .into_iter()
1792 .filter_map(|x| x.and_then(|x| x.node))
1793 .collect::<Vec<_>>();
1794
1795 let new_vars = end_cursor.map(|cursor| types::AllPackageVersionsVars {
1796 after: Some(cursor),
1797 ..vars
1798 });
1799
1800 Ok(Some((items, new_vars)))
1801 },
1802 )
1803}
1804
1805pub fn get_package_releases_stream(
1807 client: &WasmerClient,
1808 vars: types::AllPackageReleasesVars,
1809) -> impl futures::Stream<Item = Result<Vec<types::PackageWebc>, anyhow::Error>> + '_ {
1810 futures::stream::try_unfold(
1811 Some(vars),
1812 move |vars: Option<types::AllPackageReleasesVars>| async move {
1813 let vars = match vars {
1814 Some(vars) => vars,
1815 None => return Ok(None),
1816 };
1817
1818 let page = get_package_releases(client, vars.clone()).await?;
1819
1820 let end_cursor = page.page_info.end_cursor;
1821
1822 let items = page
1823 .edges
1824 .into_iter()
1825 .filter_map(|x| x.and_then(|x| x.node))
1826 .collect::<Vec<_>>();
1827
1828 let new_vars = end_cursor.map(|cursor| types::AllPackageReleasesVars {
1829 after: Some(cursor),
1830 ..vars
1831 });
1832
1833 Ok(Some((items, new_vars)))
1834 },
1835 )
1836}
1837
1838#[derive(Debug, PartialEq)]
1839pub enum TokenKind {
1840 SSH,
1841}
1842
1843pub async fn generate_deploy_config_token_raw(
1844 client: &WasmerClient,
1845 token_kind: TokenKind,
1846) -> Result<String, anyhow::Error> {
1847 let res = client
1848 .run_graphql(types::GenerateDeployConfigToken::build(
1849 types::GenerateDeployConfigTokenVars {
1850 input: match token_kind {
1851 TokenKind::SSH => "{}".to_string(),
1852 },
1853 },
1854 ))
1855 .await?;
1856
1857 res.generate_deploy_config_token
1858 .map(|x| x.token)
1859 .context("no token returned")
1860}
1861
1862pub async fn generate_ssh_token(
1867 client: &WasmerClient,
1868 app_id: Option<String>,
1869) -> Result<String, anyhow::Error> {
1870 let res = client
1871 .run_graphql_strict(types::GenerateSshToken::build(
1872 types::GenerateSshTokenVariables {
1873 app_id: app_id.map(cynic::Id::new),
1874 },
1875 ))
1876 .await?;
1877
1878 res.generate_ssh_token
1879 .map(|x| x.token)
1880 .context("no token returned")
1881}
1882
1883#[tracing::instrument(skip_all, level = "debug")]
1888#[allow(clippy::let_with_type_underscore)]
1889#[allow(clippy::too_many_arguments)]
1890fn get_app_logs(
1891 client: &WasmerClient,
1892 name: String,
1893 owner: String,
1894 tag: Option<String>,
1895 start: OffsetDateTime,
1896 end: Option<OffsetDateTime>,
1897 watch: bool,
1898 streams: Option<Vec<LogStream>>,
1899 request_id: Option<String>,
1900 instance_ids: Option<Vec<String>>,
1901) -> impl futures::Stream<Item = Result<Vec<Log>, anyhow::Error>> + '_ {
1902 let span = tracing::Span::current();
1906
1907 futures::stream::try_unfold(start, move |start| {
1908 let variables = types::GetDeployAppLogsVars {
1909 name: name.clone(),
1910 owner: owner.clone(),
1911 version: tag.clone(),
1912 first: Some(100),
1913 starting_from: unix_timestamp(start),
1914 until: end.map(unix_timestamp),
1915 streams: streams.clone(),
1916 request_id: request_id.clone(),
1917 instance_ids: instance_ids.clone(),
1918 };
1919
1920 let fut = async move {
1921 loop {
1922 let deploy_app_version = client
1923 .run_graphql(types::GetDeployAppLogs::build(variables.clone()))
1924 .await?
1925 .get_deploy_app_version
1926 .context("app version not found")?;
1927
1928 let page: Vec<_> = deploy_app_version
1929 .logs
1930 .edges
1931 .into_iter()
1932 .flatten()
1933 .filter_map(|edge| edge.node)
1934 .collect();
1935
1936 if page.is_empty() {
1937 if watch {
1938 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
1943 std::thread::sleep(Duration::from_secs(1));
1944
1945 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
1946 tokio::time::sleep(Duration::from_secs(1)).await;
1947
1948 continue;
1949 }
1950
1951 break Ok(None);
1952 } else {
1953 let last_message = page.last().expect("The page is non-empty");
1954 let timestamp = last_message.timestamp;
1955 let timestamp = OffsetDateTime::from_unix_timestamp_nanos(timestamp as i128)
1958 .with_context(|| {
1959 format!("Unable to interpret {timestamp} as a unix timestamp")
1960 })?;
1961
1962 let next_timestamp = timestamp + Duration::from_nanos(1_000);
1968
1969 break Ok(Some((page, next_timestamp)));
1970 }
1971 }
1972 };
1973
1974 fut.instrument(span.clone())
1975 })
1976}
1977
1978#[tracing::instrument(skip_all, level = "debug")]
1984#[allow(clippy::let_with_type_underscore)]
1985#[allow(clippy::too_many_arguments)]
1986pub async fn get_app_logs_paginated(
1987 client: &WasmerClient,
1988 name: String,
1989 owner: String,
1990 tag: Option<String>,
1991 start: OffsetDateTime,
1992 end: Option<OffsetDateTime>,
1993 watch: bool,
1994 streams: Option<Vec<LogStream>>,
1995) -> impl futures::Stream<Item = Result<Vec<Log>, anyhow::Error>> + '_ {
1996 let stream = get_app_logs(
1997 client, name, owner, tag, start, end, watch, streams, None, None,
1998 );
1999
2000 stream.map(|res| {
2001 let mut logs = Vec::new();
2002 let mut hasher = HashSet::new();
2003 let mut page = res?;
2004
2005 page.retain(|log| hasher.insert((log.message.clone(), log.timestamp.round() as i128)));
2008
2009 logs.extend(page);
2010
2011 Ok(logs)
2012 })
2013}
2014
2015#[tracing::instrument(skip_all, level = "debug")]
2021#[allow(clippy::let_with_type_underscore)]
2022#[allow(clippy::too_many_arguments)]
2023pub async fn get_app_logs_paginated_filter_instance(
2024 client: &WasmerClient,
2025 name: String,
2026 owner: String,
2027 tag: Option<String>,
2028 start: OffsetDateTime,
2029 end: Option<OffsetDateTime>,
2030 watch: bool,
2031 streams: Option<Vec<LogStream>>,
2032 instance_ids: Vec<String>,
2033) -> impl futures::Stream<Item = Result<Vec<Log>, anyhow::Error>> + '_ {
2034 let stream = get_app_logs(
2035 client,
2036 name,
2037 owner,
2038 tag,
2039 start,
2040 end,
2041 watch,
2042 streams,
2043 None,
2044 Some(instance_ids),
2045 );
2046
2047 stream.map(|res| {
2048 let mut logs = Vec::new();
2049 let mut hasher = HashSet::new();
2050 let mut page = res?;
2051
2052 page.retain(|log| hasher.insert((log.message.clone(), log.timestamp.round() as i128)));
2055
2056 logs.extend(page);
2057
2058 Ok(logs)
2059 })
2060}
2061
2062#[tracing::instrument(skip_all, level = "debug")]
2068#[allow(clippy::let_with_type_underscore)]
2069#[allow(clippy::too_many_arguments)]
2070pub async fn get_app_logs_paginated_filter_request(
2071 client: &WasmerClient,
2072 name: String,
2073 owner: String,
2074 tag: Option<String>,
2075 start: OffsetDateTime,
2076 end: Option<OffsetDateTime>,
2077 watch: bool,
2078 streams: Option<Vec<LogStream>>,
2079 request_id: String,
2080) -> impl futures::Stream<Item = Result<Vec<Log>, anyhow::Error>> + '_ {
2081 let stream = get_app_logs(
2082 client,
2083 name,
2084 owner,
2085 tag,
2086 start,
2087 end,
2088 watch,
2089 streams,
2090 Some(request_id),
2091 None,
2092 );
2093
2094 stream.map(|res| {
2095 let mut logs = Vec::new();
2096 let mut hasher = HashSet::new();
2097 let mut page = res?;
2098
2099 page.retain(|log| hasher.insert((log.message.clone(), log.timestamp.round() as i128)));
2102
2103 logs.extend(page);
2104
2105 Ok(logs)
2106 })
2107}
2108
2109pub async fn get_domain(
2113 client: &WasmerClient,
2114 domain: String,
2115) -> Result<Option<types::DnsDomain>, anyhow::Error> {
2116 let vars = types::GetDomainVars { domain };
2117
2118 let opt = client
2119 .run_graphql(types::GetDomain::build(vars))
2120 .await?
2121 .get_domain;
2122 Ok(opt)
2123}
2124
2125pub async fn get_domain_zone_file(
2129 client: &WasmerClient,
2130 domain: String,
2131) -> Result<Option<types::DnsDomainWithZoneFile>, anyhow::Error> {
2132 let vars = types::GetDomainVars { domain };
2133
2134 let opt = client
2135 .run_graphql(types::GetDomainWithZoneFile::build(vars))
2136 .await?
2137 .get_domain;
2138 Ok(opt)
2139}
2140
2141pub async fn get_domain_with_records(
2143 client: &WasmerClient,
2144 domain: String,
2145) -> Result<Option<types::DnsDomainWithRecords>, anyhow::Error> {
2146 let vars = types::GetDomainVars { domain };
2147
2148 let opt = client
2149 .run_graphql(types::GetDomainWithRecords::build(vars))
2150 .await?
2151 .get_domain;
2152 Ok(opt)
2153}
2154
2155pub async fn register_domain(
2157 client: &WasmerClient,
2158 name: String,
2159 namespace: Option<String>,
2160 import_records: Option<bool>,
2161) -> Result<types::DnsDomain, anyhow::Error> {
2162 let vars = types::RegisterDomainVars {
2163 name,
2164 namespace,
2165 import_records,
2166 };
2167 let opt = client
2168 .run_graphql_strict(types::RegisterDomain::build(vars))
2169 .await?
2170 .register_domain
2171 .context("Domain registration failed")?
2172 .domain
2173 .context("Domain registration failed, no associatede domain found.")?;
2174 Ok(opt)
2175}
2176
2177pub async fn get_all_dns_records(
2181 client: &WasmerClient,
2182 vars: types::GetAllDnsRecordsVariables,
2183) -> Result<types::DnsRecordConnection, anyhow::Error> {
2184 client
2185 .run_graphql_strict(types::GetAllDnsRecords::build(vars))
2186 .await
2187 .map(|x| x.get_all_dnsrecords)
2188}
2189
2190pub async fn get_all_domains(
2192 client: &WasmerClient,
2193 vars: types::GetAllDomainsVariables,
2194) -> Result<Vec<DnsDomain>, anyhow::Error> {
2195 let connection = client
2196 .run_graphql_strict(types::GetAllDomains::build(vars))
2197 .await
2198 .map(|x| x.get_all_domains)
2199 .context("no domains returned")?;
2200 Ok(connection
2201 .edges
2202 .into_iter()
2203 .flatten()
2204 .filter_map(|x| x.node)
2205 .collect())
2206}
2207
2208pub fn get_all_dns_records_stream(
2212 client: &WasmerClient,
2213 vars: types::GetAllDnsRecordsVariables,
2214) -> impl futures::Stream<Item = Result<Vec<types::DnsRecord>, anyhow::Error>> + '_ {
2215 futures::stream::try_unfold(
2216 Some(vars),
2217 move |vars: Option<types::GetAllDnsRecordsVariables>| async move {
2218 let vars = match vars {
2219 Some(vars) => vars,
2220 None => return Ok(None),
2221 };
2222
2223 let page = get_all_dns_records(client, vars.clone()).await?;
2224
2225 let end_cursor = page.page_info.end_cursor;
2226
2227 let items = page
2228 .edges
2229 .into_iter()
2230 .filter_map(|x| x.and_then(|x| x.node))
2231 .collect::<Vec<_>>();
2232
2233 let new_vars = end_cursor.map(|c| types::GetAllDnsRecordsVariables {
2234 after: Some(c),
2235 ..vars
2236 });
2237
2238 Ok(Some((items, new_vars)))
2239 },
2240 )
2241}
2242
2243pub async fn purge_cache_for_app_version(
2244 client: &WasmerClient,
2245 vars: types::PurgeCacheForAppVersionVars,
2246) -> Result<(), anyhow::Error> {
2247 client
2248 .run_graphql_strict(types::PurgeCacheForAppVersion::build(vars))
2249 .await
2250 .map(|x| x.purge_cache_for_app_version)
2251 .context("backend did not return data")?;
2252
2253 Ok(())
2254}
2255
2256fn unix_timestamp(ts: OffsetDateTime) -> f64 {
2259 let nanos_per_second = 1_000_000_000;
2260 let timestamp = ts.unix_timestamp_nanos();
2261 let nanos = timestamp % nanos_per_second;
2262 let secs = timestamp / nanos_per_second;
2263
2264 (secs as f64) + (nanos as f64 / nanos_per_second as f64)
2265}
2266
2267pub async fn upsert_domain_from_zone_file(
2269 client: &WasmerClient,
2270 zone_file_contents: String,
2271 delete_missing_records: bool,
2272) -> Result<DnsDomain, anyhow::Error> {
2273 let vars = UpsertDomainFromZoneFileVars {
2274 zone_file: zone_file_contents,
2275 delete_missing_records: Some(delete_missing_records),
2276 };
2277 let res = client
2278 .run_graphql_strict(types::UpsertDomainFromZoneFile::build(vars))
2279 .await?;
2280
2281 let domain = res
2282 .upsert_domain_from_zone_file
2283 .context("Upserting domain from zonefile failed")?
2284 .domain;
2285
2286 Ok(domain)
2287}