1use std::{collections::HashSet, time::Duration};
2
3use anyhow::{bail, Context};
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 types::{self, *},
16 GraphQLApiFailure, WasmerClient,
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_s3_credentials(
267 client: &WasmerClient,
268 app_id: impl Into<String>,
269) -> Result<types::S3Credentials, anyhow::Error> {
270 let app_id = app_id.into();
271
272 let app1 = get_app_by_id(client, app_id.clone()).await?;
274
275 let vars = types::GetDeployAppVars {
276 owner: app1.owner.global_name,
277 name: app1.name,
278 };
279 client
280 .run_graphql_strict(types::GetDeployAppS3Credentials::build(vars))
281 .await?
282 .get_deploy_app
283 .context("app not found")?
284 .s3_credentials
285 .context("app does not have S3 credentials")
286}
287
288pub async fn get_all_app_regions(client: &WasmerClient) -> Result<Vec<AppRegion>, anyhow::Error> {
292 let mut vars = GetAllAppRegionsVariables {
293 after: None,
294 before: None,
295 first: None,
296 last: None,
297 offset: None,
298 };
299
300 let mut all_regions = Vec::<AppRegion>::new();
301
302 loop {
303 let page = get_regions(client, vars.clone()).await?;
304 if page.edges.is_empty() {
305 break;
306 }
307
308 for edge in page.edges {
309 let edge = match edge {
310 Some(edge) => edge,
311 None => continue,
312 };
313 let version = match edge.node {
314 Some(item) => item,
315 None => continue,
316 };
317
318 all_regions.push(version);
319
320 vars.after = Some(edge.cursor);
322 }
323 }
324
325 Ok(all_regions)
326}
327
328pub async fn get_regions(
330 client: &WasmerClient,
331 vars: GetAllAppRegionsVariables,
332) -> Result<AppRegionConnection, anyhow::Error> {
333 let res = client
334 .run_graphql_strict(types::GetAllAppRegions::build(vars))
335 .await?;
336 Ok(res.get_app_regions)
337}
338
339pub async fn get_all_app_secrets(
343 client: &WasmerClient,
344 app_id: impl Into<String>,
345) -> Result<Vec<Secret>, anyhow::Error> {
346 let mut vars = GetAllAppSecretsVariables {
347 after: None,
348 app_id: types::Id::from(app_id),
349 before: None,
350 first: None,
351 last: None,
352 offset: None,
353 names: None,
354 };
355
356 let mut all_secrets = Vec::<Secret>::new();
357
358 loop {
359 let page = get_app_secrets(client, vars.clone()).await?;
360 if page.edges.is_empty() {
361 break;
362 }
363
364 for edge in page.edges {
365 let edge = match edge {
366 Some(edge) => edge,
367 None => continue,
368 };
369 let version = match edge.node {
370 Some(item) => item,
371 None => continue,
372 };
373
374 all_secrets.push(version);
375
376 vars.after = Some(edge.cursor);
378 }
379 }
380
381 Ok(all_secrets)
382}
383
384pub async fn get_app_secrets(
386 client: &WasmerClient,
387 vars: GetAllAppSecretsVariables,
388) -> Result<SecretConnection, anyhow::Error> {
389 let res = client
390 .run_graphql_strict(types::GetAllAppSecrets::build(vars))
391 .await?;
392 res.get_app_secrets.context("app not found")
393}
394
395pub async fn delete_app_secret(
396 client: &WasmerClient,
397 secret_id: impl Into<String>,
398) -> Result<Option<DeleteAppSecretPayload>, anyhow::Error> {
399 client
400 .run_graphql_strict(types::DeleteAppSecret::build(DeleteAppSecretVariables {
401 id: types::Id::from(secret_id.into()),
402 }))
403 .await
404 .map(|v| v.delete_app_secret)
405}
406
407pub async fn fetch_webc_package(
412 client: &WasmerClient,
413 ident: &PackageIdent,
414 default_registry: &Url,
415) -> Result<Container, anyhow::Error> {
416 let url = match ident {
417 PackageIdent::Named(n) => Url::parse(&format!(
418 "{default_registry}/{}:{}",
419 n.full_name(),
420 n.version_or_default()
421 ))?,
422 PackageIdent::Hash(h) => match get_package_release(client, &h.to_string()).await? {
423 Some(webc) => Url::parse(&webc.webc_url)?,
424 None => anyhow::bail!("Could not find package with hash '{}'", h),
425 },
426 };
427
428 let data = client
429 .client
430 .get(url)
431 .header(reqwest::header::USER_AGENT, &client.user_agent)
432 .header(reqwest::header::ACCEPT, "application/webc")
433 .send()
434 .await?
435 .error_for_status()?
436 .bytes()
437 .await?;
438
439 from_bytes(data).context("failed to parse webc package")
440}
441
442pub async fn fetch_app_template_from_slug(
444 client: &WasmerClient,
445 slug: String,
446) -> Result<Option<types::AppTemplate>, anyhow::Error> {
447 client
448 .run_graphql_strict(types::GetAppTemplateFromSlug::build(
449 GetAppTemplateFromSlugVariables { slug },
450 ))
451 .await
452 .map(|v| v.get_app_template)
453}
454
455pub async fn fetch_app_templates_from_framework(
457 client: &WasmerClient,
458 framework_slug: String,
459 first: i32,
460 after: Option<String>,
461 sort_by: Option<types::AppTemplatesSortBy>,
462) -> Result<Option<types::AppTemplateConnection>, anyhow::Error> {
463 client
464 .run_graphql_strict(types::GetAppTemplatesFromFramework::build(
465 GetAppTemplatesFromFrameworkVars {
466 framework_slug,
467 first,
468 after,
469 sort_by,
470 },
471 ))
472 .await
473 .map(|r| r.get_app_templates)
474}
475
476pub async fn fetch_app_templates(
478 client: &WasmerClient,
479 category_slug: String,
480 first: i32,
481 after: Option<String>,
482 sort_by: Option<types::AppTemplatesSortBy>,
483) -> Result<Option<types::AppTemplateConnection>, anyhow::Error> {
484 client
485 .run_graphql_strict(types::GetAppTemplates::build(GetAppTemplatesVars {
486 category_slug,
487 first,
488 after,
489 sort_by,
490 }))
491 .await
492 .map(|r| r.get_app_templates)
493}
494
495pub fn fetch_all_app_templates(
499 client: &WasmerClient,
500 page_size: i32,
501 sort_by: Option<types::AppTemplatesSortBy>,
502) -> impl futures::Stream<Item = Result<Vec<types::AppTemplate>, anyhow::Error>> + '_ {
503 let vars = GetAppTemplatesVars {
504 category_slug: String::new(),
505 first: page_size,
506 sort_by,
507 after: None,
508 };
509
510 futures::stream::try_unfold(
511 Some(vars),
512 move |vars: Option<types::GetAppTemplatesVars>| async move {
513 let vars = match vars {
514 Some(vars) => vars,
515 None => return Ok(None),
516 };
517
518 let con = client
519 .run_graphql_strict(types::GetAppTemplates::build(vars.clone()))
520 .await?
521 .get_app_templates
522 .context("backend did not return any data")?;
523
524 let items = con
525 .edges
526 .into_iter()
527 .flatten()
528 .filter_map(|edge| edge.node)
529 .collect::<Vec<_>>();
530
531 let next_cursor = con
532 .page_info
533 .end_cursor
534 .filter(|_| con.page_info.has_next_page);
535
536 let next_vars = next_cursor.map(|after| types::GetAppTemplatesVars {
537 after: Some(after),
538 ..vars
539 });
540
541 #[allow(clippy::type_complexity)]
542 let res: Result<
543 Option<(Vec<types::AppTemplate>, Option<types::GetAppTemplatesVars>)>,
544 anyhow::Error,
545 > = Ok(Some((items, next_vars)));
546
547 res
548 },
549 )
550}
551
552pub fn fetch_all_app_templates_from_language(
556 client: &WasmerClient,
557 page_size: i32,
558 sort_by: Option<types::AppTemplatesSortBy>,
559 language: String,
560) -> impl futures::Stream<Item = Result<Vec<types::AppTemplate>, anyhow::Error>> + '_ {
561 let vars = GetAppTemplatesFromLanguageVars {
562 language_slug: language.clone().to_string(),
563 first: page_size,
564 sort_by,
565 after: None,
566 };
567
568 futures::stream::try_unfold(
569 Some(vars),
570 move |vars: Option<types::GetAppTemplatesFromLanguageVars>| async move {
571 let vars = match vars {
572 Some(vars) => vars,
573 None => return Ok(None),
574 };
575
576 let con = client
577 .run_graphql_strict(types::GetAppTemplatesFromLanguage::build(vars.clone()))
578 .await?
579 .get_app_templates
580 .context("backend did not return any data")?;
581
582 let items = con
583 .edges
584 .into_iter()
585 .flatten()
586 .filter_map(|edge| edge.node)
587 .collect::<Vec<_>>();
588
589 let next_cursor = con
590 .page_info
591 .end_cursor
592 .filter(|_| con.page_info.has_next_page);
593
594 let next_vars = next_cursor.map(|after| types::GetAppTemplatesFromLanguageVars {
595 after: Some(after),
596 ..vars
597 });
598
599 #[allow(clippy::type_complexity)]
600 let res: Result<
601 Option<(
602 Vec<types::AppTemplate>,
603 Option<types::GetAppTemplatesFromLanguageVars>,
604 )>,
605 anyhow::Error,
606 > = Ok(Some((items, next_vars)));
607
608 res
609 },
610 )
611}
612
613pub async fn fetch_app_template_languages(
615 client: &WasmerClient,
616 after: Option<String>,
617 first: Option<i32>,
618) -> Result<Option<types::TemplateLanguageConnection>, anyhow::Error> {
619 client
620 .run_graphql_strict(types::GetTemplateLanguages::build(
621 GetTemplateLanguagesVars { after, first },
622 ))
623 .await
624 .map(|r| r.get_template_languages)
625}
626
627pub fn fetch_all_app_template_languages(
631 client: &WasmerClient,
632 page_size: Option<i32>,
633) -> impl futures::Stream<Item = Result<Vec<types::TemplateLanguage>, anyhow::Error>> + '_ {
634 let vars = GetTemplateLanguagesVars {
635 after: None,
636 first: page_size,
637 };
638
639 futures::stream::try_unfold(
640 Some(vars),
641 move |vars: Option<types::GetTemplateLanguagesVars>| async move {
642 let vars = match vars {
643 Some(vars) => vars,
644 None => return Ok(None),
645 };
646
647 let con = client
648 .run_graphql_strict(types::GetTemplateLanguages::build(vars.clone()))
649 .await?
650 .get_template_languages
651 .context("backend did not return any data")?;
652
653 let items = con
654 .edges
655 .into_iter()
656 .flatten()
657 .filter_map(|edge| edge.node)
658 .collect::<Vec<_>>();
659
660 let next_cursor = con
661 .page_info
662 .end_cursor
663 .filter(|_| con.page_info.has_next_page);
664
665 let next_vars = next_cursor.map(|after| types::GetTemplateLanguagesVars {
666 after: Some(after),
667 ..vars
668 });
669
670 #[allow(clippy::type_complexity)]
671 let res: Result<
672 Option<(
673 Vec<types::TemplateLanguage>,
674 Option<types::GetTemplateLanguagesVars>,
675 )>,
676 anyhow::Error,
677 > = Ok(Some((items, next_vars)));
678
679 res
680 },
681 )
682}
683
684pub fn fetch_all_app_templates_from_framework(
688 client: &WasmerClient,
689 page_size: i32,
690 sort_by: Option<types::AppTemplatesSortBy>,
691 framework: String,
692) -> impl futures::Stream<Item = Result<Vec<types::AppTemplate>, anyhow::Error>> + '_ {
693 let vars = GetAppTemplatesFromFrameworkVars {
694 framework_slug: framework.clone().to_string(),
695 first: page_size,
696 sort_by,
697 after: None,
698 };
699
700 futures::stream::try_unfold(
701 Some(vars),
702 move |vars: Option<types::GetAppTemplatesFromFrameworkVars>| async move {
703 let vars = match vars {
704 Some(vars) => vars,
705 None => return Ok(None),
706 };
707
708 let con = client
709 .run_graphql_strict(types::GetAppTemplatesFromFramework::build(vars.clone()))
710 .await?
711 .get_app_templates
712 .context("backend did not return any data")?;
713
714 let items = con
715 .edges
716 .into_iter()
717 .flatten()
718 .filter_map(|edge| edge.node)
719 .collect::<Vec<_>>();
720
721 let next_cursor = con
722 .page_info
723 .end_cursor
724 .filter(|_| con.page_info.has_next_page);
725
726 let next_vars = next_cursor.map(|after| types::GetAppTemplatesFromFrameworkVars {
727 after: Some(after),
728 ..vars
729 });
730
731 #[allow(clippy::type_complexity)]
732 let res: Result<
733 Option<(
734 Vec<types::AppTemplate>,
735 Option<types::GetAppTemplatesFromFrameworkVars>,
736 )>,
737 anyhow::Error,
738 > = Ok(Some((items, next_vars)));
739
740 res
741 },
742 )
743}
744
745pub async fn fetch_app_template_frameworks(
747 client: &WasmerClient,
748 after: Option<String>,
749 first: Option<i32>,
750) -> Result<Option<types::TemplateFrameworkConnection>, anyhow::Error> {
751 client
752 .run_graphql_strict(types::GetTemplateFrameworks::build(
753 GetTemplateFrameworksVars { after, first },
754 ))
755 .await
756 .map(|r| r.get_template_frameworks)
757}
758
759pub fn fetch_all_app_template_frameworks(
763 client: &WasmerClient,
764 page_size: Option<i32>,
765) -> impl futures::Stream<Item = Result<Vec<types::TemplateFramework>, anyhow::Error>> + '_ {
766 let vars = GetTemplateFrameworksVars {
767 after: None,
768 first: page_size,
769 };
770
771 futures::stream::try_unfold(
772 Some(vars),
773 move |vars: Option<types::GetTemplateFrameworksVars>| async move {
774 let vars = match vars {
775 Some(vars) => vars,
776 None => return Ok(None),
777 };
778
779 let con = client
780 .run_graphql_strict(types::GetTemplateFrameworks::build(vars.clone()))
781 .await?
782 .get_template_frameworks
783 .context("backend did not return any data")?;
784
785 let items = con
786 .edges
787 .into_iter()
788 .flatten()
789 .filter_map(|edge| edge.node)
790 .collect::<Vec<_>>();
791
792 let next_cursor = con
793 .page_info
794 .end_cursor
795 .filter(|_| con.page_info.has_next_page);
796
797 let next_vars = next_cursor.map(|after| types::GetTemplateFrameworksVars {
798 after: Some(after),
799 ..vars
800 });
801
802 #[allow(clippy::type_complexity)]
803 let res: Result<
804 Option<(
805 Vec<types::TemplateFramework>,
806 Option<types::GetTemplateFrameworksVars>,
807 )>,
808 anyhow::Error,
809 > = Ok(Some((items, next_vars)));
810
811 res
812 },
813 )
814}
815
816pub async fn get_signed_url_for_package_upload(
818 client: &WasmerClient,
819 expires_after_seconds: Option<i32>,
820 filename: Option<&str>,
821 name: Option<&str>,
822 version: Option<&str>,
823) -> Result<Option<SignedUrl>, anyhow::Error> {
824 client
825 .run_graphql_strict(types::GetSignedUrlForPackageUpload::build(
826 GetSignedUrlForPackageUploadVariables {
827 expires_after_seconds,
828 filename,
829 name,
830 version,
831 },
832 ))
833 .await
834 .map(|r| r.get_signed_url_for_package_upload)
835}
836pub async fn push_package_release(
838 client: &WasmerClient,
839 name: Option<&str>,
840 namespace: &str,
841 signed_url: &str,
842 private: Option<bool>,
843) -> Result<Option<PushPackageReleasePayload>, anyhow::Error> {
844 client
845 .run_graphql_strict(types::PushPackageRelease::build(
846 types::PushPackageReleaseVariables {
847 name,
848 namespace,
849 private,
850 signed_url,
851 },
852 ))
853 .await
854 .map(|r| r.push_package_release)
855}
856
857#[allow(clippy::too_many_arguments)]
858pub async fn tag_package_release(
859 client: &WasmerClient,
860 description: Option<&str>,
861 homepage: Option<&str>,
862 license: Option<&str>,
863 license_file: Option<&str>,
864 manifest: Option<&str>,
865 name: &str,
866 namespace: Option<&str>,
867 package_release_id: &cynic::Id,
868 private: Option<bool>,
869 readme: Option<&str>,
870 repository: Option<&str>,
871 version: &str,
872) -> Result<Option<TagPackageReleasePayload>, anyhow::Error> {
873 client
874 .run_graphql_strict(types::TagPackageRelease::build(
875 types::TagPackageReleaseVariables {
876 description,
877 homepage,
878 license,
879 license_file,
880 manifest,
881 name,
882 namespace,
883 package_release_id,
884 private,
885 readme,
886 repository,
887 version,
888 },
889 ))
890 .await
891 .map(|r| r.tag_package_release)
892}
893
894pub async fn current_user(client: &WasmerClient) -> Result<Option<types::User>, anyhow::Error> {
896 client
897 .run_graphql(types::GetCurrentUser::build(()))
898 .await
899 .map(|x| x.viewer)
900}
901
902pub async fn current_user_with_namespaces(
906 client: &WasmerClient,
907 namespace_role: Option<types::GrapheneRole>,
908) -> Result<types::UserWithNamespaces, anyhow::Error> {
909 client
910 .run_graphql(types::GetCurrentUserWithNamespaces::build(
911 types::GetCurrentUserWithNamespacesVars { namespace_role },
912 ))
913 .await?
914 .viewer
915 .context("not logged in")
916}
917
918pub async fn get_app(
920 client: &WasmerClient,
921 owner: String,
922 name: String,
923) -> Result<Option<types::DeployApp>, anyhow::Error> {
924 client
925 .run_graphql(types::GetDeployApp::build(types::GetDeployAppVars {
926 name,
927 owner,
928 }))
929 .await
930 .map(|x| x.get_deploy_app)
931}
932
933pub async fn get_app_by_alias(
935 client: &WasmerClient,
936 alias: String,
937) -> Result<Option<types::DeployApp>, anyhow::Error> {
938 client
939 .run_graphql(types::GetDeployAppByAlias::build(
940 types::GetDeployAppByAliasVars { alias },
941 ))
942 .await
943 .map(|x| x.get_app_by_global_alias)
944}
945
946pub async fn get_app_version(
948 client: &WasmerClient,
949 owner: String,
950 name: String,
951 version: String,
952) -> Result<Option<types::DeployAppVersion>, anyhow::Error> {
953 client
954 .run_graphql(types::GetDeployAppVersion::build(
955 types::GetDeployAppVersionVars {
956 name,
957 owner,
958 version,
959 },
960 ))
961 .await
962 .map(|x| x.get_deploy_app_version)
963}
964
965pub async fn get_app_with_version(
967 client: &WasmerClient,
968 owner: String,
969 name: String,
970 version: String,
971) -> Result<GetDeployAppAndVersion, anyhow::Error> {
972 client
973 .run_graphql(types::GetDeployAppAndVersion::build(
974 types::GetDeployAppAndVersionVars {
975 name,
976 owner,
977 version,
978 },
979 ))
980 .await
981}
982
983pub async fn get_app_and_package_by_name(
985 client: &WasmerClient,
986 vars: types::GetPackageAndAppVars,
987) -> Result<(Option<types::Package>, Option<types::DeployApp>), anyhow::Error> {
988 let res = client
989 .run_graphql(types::GetPackageAndApp::build(vars))
990 .await?;
991 Ok((res.get_package, res.get_deploy_app))
992}
993
994pub async fn get_deploy_apps(
996 client: &WasmerClient,
997 vars: types::GetDeployAppsVars,
998) -> Result<DeployAppConnection, anyhow::Error> {
999 let res = client
1000 .run_graphql(types::GetDeployApps::build(vars))
1001 .await?;
1002 res.get_deploy_apps.context("no apps returned")
1003}
1004
1005pub fn get_deploy_apps_stream(
1007 client: &WasmerClient,
1008 vars: types::GetDeployAppsVars,
1009) -> impl futures::Stream<Item = Result<Vec<DeployApp>, anyhow::Error>> + '_ {
1010 futures::stream::try_unfold(
1011 Some(vars),
1012 move |vars: Option<types::GetDeployAppsVars>| async move {
1013 let vars = match vars {
1014 Some(vars) => vars,
1015 None => return Ok(None),
1016 };
1017
1018 let page = get_deploy_apps(client, vars.clone()).await?;
1019
1020 let end_cursor = page.page_info.end_cursor;
1021
1022 let items = page
1023 .edges
1024 .into_iter()
1025 .filter_map(|x| x.and_then(|x| x.node))
1026 .collect::<Vec<_>>();
1027
1028 let new_vars = end_cursor.map(|c| types::GetDeployAppsVars {
1029 after: Some(c),
1030 ..vars
1031 });
1032
1033 Ok(Some((items, new_vars)))
1034 },
1035 )
1036}
1037
1038pub async fn get_deploy_app_versions(
1040 client: &WasmerClient,
1041 vars: GetDeployAppVersionsVars,
1042) -> Result<DeployAppVersionConnection, anyhow::Error> {
1043 let res = client
1044 .run_graphql_strict(types::GetDeployAppVersions::build(vars))
1045 .await?;
1046 let versions = res.get_deploy_app.context("app not found")?.versions;
1047 Ok(versions)
1048}
1049
1050pub async fn app_deployments(
1052 client: &WasmerClient,
1053 vars: types::GetAppDeploymentsVariables,
1054) -> Result<Vec<types::Deployment>, anyhow::Error> {
1055 let res = client
1056 .run_graphql_strict(types::GetAppDeployments::build(vars))
1057 .await?;
1058 let builds = res
1059 .get_deploy_app
1060 .and_then(|x| x.deployments)
1061 .context("no data returned")?
1062 .edges
1063 .into_iter()
1064 .flatten()
1065 .filter_map(|x| x.node)
1066 .collect();
1067
1068 Ok(builds)
1069}
1070
1071pub async fn app_deployment(
1073 client: &WasmerClient,
1074 id: String,
1075) -> Result<types::AutobuildRepository, anyhow::Error> {
1076 let node = get_node(client, id.clone())
1077 .await?
1078 .with_context(|| format!("app deployment with id '{id}' not found"))?;
1079 match node {
1080 types::Node::AutobuildRepository(x) => Ok(*x),
1081 _ => anyhow::bail!("invalid node type returned"),
1082 }
1083}
1084
1085pub async fn all_app_versions(
1089 client: &WasmerClient,
1090 owner: String,
1091 name: String,
1092) -> Result<Vec<DeployAppVersion>, anyhow::Error> {
1093 let mut vars = GetDeployAppVersionsVars {
1094 owner,
1095 name,
1096 offset: None,
1097 before: None,
1098 after: None,
1099 first: Some(10),
1100 last: None,
1101 sort_by: None,
1102 };
1103
1104 let mut all_versions = Vec::<DeployAppVersion>::new();
1105
1106 loop {
1107 let page = get_deploy_app_versions(client, vars.clone()).await?;
1108 if page.edges.is_empty() {
1109 break;
1110 }
1111
1112 for edge in page.edges {
1113 let edge = match edge {
1114 Some(edge) => edge,
1115 None => continue,
1116 };
1117 let version = match edge.node {
1118 Some(item) => item,
1119 None => continue,
1120 };
1121
1122 if all_versions.iter().any(|v| v.id == version.id) == false {
1124 all_versions.push(version);
1125 }
1126
1127 vars.after = Some(edge.cursor);
1129 }
1130 }
1131
1132 Ok(all_versions)
1133}
1134
1135pub async fn get_deploy_app_versions_by_id(
1137 client: &WasmerClient,
1138 vars: types::GetDeployAppVersionsByIdVars,
1139) -> Result<DeployAppVersionConnection, anyhow::Error> {
1140 let res = client
1141 .run_graphql_strict(types::GetDeployAppVersionsById::build(vars))
1142 .await?;
1143 let versions = res
1144 .node
1145 .context("app not found")?
1146 .into_app()
1147 .context("invalid node type returned")?
1148 .versions;
1149 Ok(versions)
1150}
1151
1152pub async fn all_app_versions_by_id(
1156 client: &WasmerClient,
1157 app_id: impl Into<String>,
1158) -> Result<Vec<DeployAppVersion>, anyhow::Error> {
1159 let mut vars = types::GetDeployAppVersionsByIdVars {
1160 id: cynic::Id::new(app_id),
1161 offset: None,
1162 before: None,
1163 after: None,
1164 first: Some(10),
1165 last: None,
1166 sort_by: None,
1167 };
1168
1169 let mut all_versions = Vec::<DeployAppVersion>::new();
1170
1171 loop {
1172 let page = get_deploy_app_versions_by_id(client, vars.clone()).await?;
1173 if page.edges.is_empty() {
1174 break;
1175 }
1176
1177 for edge in page.edges {
1178 let edge = match edge {
1179 Some(edge) => edge,
1180 None => continue,
1181 };
1182 let version = match edge.node {
1183 Some(item) => item,
1184 None => continue,
1185 };
1186
1187 if all_versions.iter().any(|v| v.id == version.id) == false {
1189 all_versions.push(version);
1190 }
1191
1192 vars.after = Some(edge.cursor);
1194 }
1195 }
1196
1197 Ok(all_versions)
1198}
1199
1200pub async fn app_version_activate(
1202 client: &WasmerClient,
1203 version: String,
1204) -> Result<DeployApp, anyhow::Error> {
1205 let res = client
1206 .run_graphql_strict(types::MarkAppVersionAsActive::build(
1207 types::MarkAppVersionAsActiveVars {
1208 input: types::MarkAppVersionAsActiveInput {
1209 app_version: version.into(),
1210 },
1211 },
1212 ))
1213 .await?;
1214 res.mark_app_version_as_active
1215 .context("app not found")
1216 .map(|x| x.app)
1217}
1218
1219pub async fn get_node(
1221 client: &WasmerClient,
1222 id: String,
1223) -> Result<Option<types::Node>, anyhow::Error> {
1224 client
1225 .run_graphql(types::GetNode::build(types::GetNodeVars { id: id.into() }))
1226 .await
1227 .map(|x| x.node)
1228}
1229
1230pub async fn get_app_by_id(
1232 client: &WasmerClient,
1233 app_id: String,
1234) -> Result<DeployApp, anyhow::Error> {
1235 get_app_by_id_opt(client, app_id)
1236 .await?
1237 .context("app not found")
1238}
1239
1240pub async fn get_app_by_id_opt(
1242 client: &WasmerClient,
1243 app_id: String,
1244) -> Result<Option<DeployApp>, anyhow::Error> {
1245 let app_opt = client
1246 .run_graphql(types::GetDeployAppById::build(
1247 types::GetDeployAppByIdVars {
1248 app_id: app_id.into(),
1249 },
1250 ))
1251 .await?
1252 .app;
1253
1254 if let Some(app) = app_opt {
1255 let app = app.into_deploy_app().context("app conversion failed")?;
1256 Ok(Some(app))
1257 } else {
1258 Ok(None)
1259 }
1260}
1261
1262pub async fn get_app_with_version_by_id(
1264 client: &WasmerClient,
1265 app_id: String,
1266 version_id: String,
1267) -> Result<(DeployApp, DeployAppVersion), anyhow::Error> {
1268 let res = client
1269 .run_graphql(types::GetDeployAppAndVersionById::build(
1270 types::GetDeployAppAndVersionByIdVars {
1271 app_id: app_id.into(),
1272 version_id: version_id.into(),
1273 },
1274 ))
1275 .await?;
1276
1277 let app = res
1278 .app
1279 .context("app not found")?
1280 .into_deploy_app()
1281 .context("app conversion failed")?;
1282 let version = res
1283 .version
1284 .context("version not found")?
1285 .into_deploy_app_version()
1286 .context("version conversion failed")?;
1287
1288 Ok((app, version))
1289}
1290
1291pub async fn get_app_version_by_id(
1293 client: &WasmerClient,
1294 version_id: String,
1295) -> Result<DeployAppVersion, anyhow::Error> {
1296 client
1297 .run_graphql(types::GetDeployAppVersionById::build(
1298 types::GetDeployAppVersionByIdVars {
1299 version_id: version_id.into(),
1300 },
1301 ))
1302 .await?
1303 .version
1304 .context("app not found")?
1305 .into_deploy_app_version()
1306 .context("app version conversion failed")
1307}
1308
1309pub async fn get_app_version_by_id_with_app(
1310 client: &WasmerClient,
1311 version_id: String,
1312) -> Result<(DeployApp, DeployAppVersion), anyhow::Error> {
1313 let version = client
1314 .run_graphql(types::GetDeployAppVersionById::build(
1315 types::GetDeployAppVersionByIdVars {
1316 version_id: version_id.into(),
1317 },
1318 ))
1319 .await?
1320 .version
1321 .context("app not found")?
1322 .into_deploy_app_version()
1323 .context("app version conversion failed")?;
1324
1325 let app_id = version
1326 .app
1327 .as_ref()
1328 .context("could not load app for version")?
1329 .id
1330 .clone();
1331
1332 let app = get_app_by_id(client, app_id.into_inner()).await?;
1333
1334 Ok((app, version))
1335}
1336
1337pub async fn user_apps(
1341 client: &WasmerClient,
1342 sort: types::DeployAppsSortBy,
1343) -> impl futures::Stream<Item = Result<Vec<types::DeployApp>, anyhow::Error>> + '_ {
1344 futures::stream::try_unfold(None, move |cursor| async move {
1345 let user = client
1346 .run_graphql(types::GetCurrentUserWithApps::build(
1347 GetCurrentUserWithAppsVars {
1348 after: cursor,
1349 sort: Some(sort),
1350 },
1351 ))
1352 .await?
1353 .viewer
1354 .context("not logged in")?;
1355
1356 let apps: Vec<_> = user
1357 .apps
1358 .edges
1359 .into_iter()
1360 .flatten()
1361 .filter_map(|x| x.node)
1362 .collect();
1363
1364 let cursor = user.apps.page_info.end_cursor;
1365
1366 if apps.is_empty() {
1367 Ok(None)
1368 } else {
1369 Ok(Some((apps, cursor)))
1370 }
1371 })
1372}
1373
1374pub async fn user_accessible_apps(
1376 client: &WasmerClient,
1377 sort: types::DeployAppsSortBy,
1378) -> Result<
1379 impl futures::Stream<Item = Result<Vec<types::DeployApp>, anyhow::Error>> + '_,
1380 anyhow::Error,
1381> {
1382 let user_apps = user_apps(client, sort).await;
1383
1384 let namespace_res = client
1386 .run_graphql(types::GetCurrentUserWithNamespaces::build(
1387 types::GetCurrentUserWithNamespacesVars {
1388 namespace_role: None,
1389 },
1390 ))
1391 .await?;
1392 let active_user = namespace_res.viewer.context("not logged in")?;
1393 let namespace_names = active_user
1394 .namespaces
1395 .edges
1396 .iter()
1397 .filter_map(|edge| edge.as_ref())
1398 .filter_map(|edge| edge.node.as_ref())
1399 .map(|node| node.name.clone())
1400 .collect::<Vec<_>>();
1401
1402 let mut ns_apps = vec![];
1403 for ns in namespace_names {
1404 let apps = namespace_apps(client, ns, sort).await;
1405 ns_apps.push(apps);
1406 }
1407
1408 Ok((user_apps, ns_apps.merge()).merge())
1409}
1410
1411pub async fn namespace_apps(
1415 client: &WasmerClient,
1416 namespace: String,
1417 sort: types::DeployAppsSortBy,
1418) -> impl futures::Stream<Item = Result<Vec<types::DeployApp>, anyhow::Error>> + '_ {
1419 let namespace = namespace.clone();
1420
1421 futures::stream::try_unfold((None, namespace), move |(cursor, namespace)| async move {
1422 let res = client
1423 .run_graphql(types::GetNamespaceApps::build(GetNamespaceAppsVars {
1424 name: namespace.to_string(),
1425 after: cursor,
1426 sort: Some(sort),
1427 }))
1428 .await?;
1429
1430 let ns = res
1431 .get_namespace
1432 .with_context(|| format!("failed to get namespace '{namespace}'"))?;
1433
1434 let apps: Vec<_> = ns
1435 .apps
1436 .edges
1437 .into_iter()
1438 .flatten()
1439 .filter_map(|x| x.node)
1440 .collect();
1441
1442 let cursor = ns.apps.page_info.end_cursor;
1443
1444 if apps.is_empty() {
1445 Ok(None)
1446 } else {
1447 Ok(Some((apps, (cursor, namespace))))
1448 }
1449 })
1450}
1451
1452pub async fn publish_deploy_app(
1454 client: &WasmerClient,
1455 vars: PublishDeployAppVars,
1456) -> Result<DeployAppVersion, anyhow::Error> {
1457 let res = client
1458 .run_graphql_raw(types::PublishDeployApp::build(vars))
1459 .await?;
1460
1461 if let Some(app) = res
1462 .data
1463 .and_then(|d| d.publish_deploy_app)
1464 .map(|d| d.deploy_app_version)
1465 {
1466 Ok(app)
1467 } else {
1468 Err(GraphQLApiFailure::from_errors(
1469 "could not publish app",
1470 res.errors,
1471 ))
1472 }
1473}
1474
1475pub async fn delete_app(client: &WasmerClient, app_id: String) -> Result<(), anyhow::Error> {
1477 let res = client
1478 .run_graphql_strict(types::DeleteApp::build(types::DeleteAppVars {
1479 app_id: app_id.into(),
1480 }))
1481 .await?
1482 .delete_app
1483 .context("API did not return data for the delete_app mutation")?;
1484
1485 if !res.success {
1486 bail!("App deletion failed for an unknown reason");
1487 }
1488
1489 Ok(())
1490}
1491
1492pub async fn user_namespaces(
1494 client: &WasmerClient,
1495) -> Result<Vec<types::Namespace>, anyhow::Error> {
1496 let user = client
1497 .run_graphql(types::GetCurrentUserWithNamespaces::build(
1498 types::GetCurrentUserWithNamespacesVars {
1499 namespace_role: None,
1500 },
1501 ))
1502 .await?
1503 .viewer
1504 .context("not logged in")?;
1505
1506 let ns = user
1507 .namespaces
1508 .edges
1509 .into_iter()
1510 .flatten()
1511 .filter_map(|x| x.node)
1513 .collect();
1514
1515 Ok(ns)
1516}
1517
1518pub async fn get_namespace(
1520 client: &WasmerClient,
1521 name: String,
1522) -> Result<Option<types::Namespace>, anyhow::Error> {
1523 client
1524 .run_graphql(types::GetNamespace::build(types::GetNamespaceVars { name }))
1525 .await
1526 .map(|x| x.get_namespace)
1527}
1528
1529pub async fn create_namespace(
1531 client: &WasmerClient,
1532 vars: CreateNamespaceVars,
1533) -> Result<types::Namespace, anyhow::Error> {
1534 client
1535 .run_graphql(types::CreateNamespace::build(vars))
1536 .await?
1537 .create_namespace
1538 .map(|x| x.namespace)
1539 .context("no namespace returned")
1540}
1541
1542pub async fn get_package(
1544 client: &WasmerClient,
1545 name: String,
1546) -> Result<Option<types::Package>, anyhow::Error> {
1547 client
1548 .run_graphql_strict(types::GetPackage::build(types::GetPackageVars { name }))
1549 .await
1550 .map(|x| x.get_package)
1551}
1552
1553pub async fn get_package_version(
1555 client: &WasmerClient,
1556 name: String,
1557 version: String,
1558) -> Result<Option<types::PackageVersionWithPackage>, anyhow::Error> {
1559 client
1560 .run_graphql_strict(types::GetPackageVersion::build(
1561 types::GetPackageVersionVars { name, version },
1562 ))
1563 .await
1564 .map(|x| x.get_package_version)
1565}
1566
1567pub async fn get_package_versions(
1569 client: &WasmerClient,
1570 vars: types::AllPackageVersionsVars,
1571) -> Result<PackageVersionConnection, anyhow::Error> {
1572 let res = client
1573 .run_graphql(types::GetAllPackageVersions::build(vars))
1574 .await?;
1575 Ok(res.all_package_versions)
1576}
1577
1578pub async fn get_package_release(
1580 client: &WasmerClient,
1581 hash: &str,
1582) -> Result<Option<types::PackageWebc>, anyhow::Error> {
1583 let hash = hash.trim_start_matches("sha256:");
1584 client
1585 .run_graphql_strict(types::GetPackageRelease::build(
1586 types::GetPackageReleaseVars {
1587 hash: hash.to_string(),
1588 },
1589 ))
1590 .await
1591 .map(|x| x.get_package_release)
1592}
1593
1594pub async fn get_package_releases(
1595 client: &WasmerClient,
1596 vars: types::AllPackageReleasesVars,
1597) -> Result<types::PackageWebcConnection, anyhow::Error> {
1598 let res = client
1599 .run_graphql(types::GetAllPackageReleases::build(vars))
1600 .await?;
1601 Ok(res.all_package_releases)
1602}
1603
1604pub fn get_package_versions_stream(
1606 client: &WasmerClient,
1607 vars: types::AllPackageVersionsVars,
1608) -> impl futures::Stream<Item = Result<Vec<types::PackageVersionWithPackage>, anyhow::Error>> + '_
1609{
1610 futures::stream::try_unfold(
1611 Some(vars),
1612 move |vars: Option<types::AllPackageVersionsVars>| async move {
1613 let vars = match vars {
1614 Some(vars) => vars,
1615 None => return Ok(None),
1616 };
1617
1618 let page = get_package_versions(client, vars.clone()).await?;
1619
1620 let end_cursor = page.page_info.end_cursor;
1621
1622 let items = page
1623 .edges
1624 .into_iter()
1625 .filter_map(|x| x.and_then(|x| x.node))
1626 .collect::<Vec<_>>();
1627
1628 let new_vars = end_cursor.map(|cursor| types::AllPackageVersionsVars {
1629 after: Some(cursor),
1630 ..vars
1631 });
1632
1633 Ok(Some((items, new_vars)))
1634 },
1635 )
1636}
1637
1638pub fn get_package_releases_stream(
1640 client: &WasmerClient,
1641 vars: types::AllPackageReleasesVars,
1642) -> impl futures::Stream<Item = Result<Vec<types::PackageWebc>, anyhow::Error>> + '_ {
1643 futures::stream::try_unfold(
1644 Some(vars),
1645 move |vars: Option<types::AllPackageReleasesVars>| async move {
1646 let vars = match vars {
1647 Some(vars) => vars,
1648 None => return Ok(None),
1649 };
1650
1651 let page = get_package_releases(client, vars.clone()).await?;
1652
1653 let end_cursor = page.page_info.end_cursor;
1654
1655 let items = page
1656 .edges
1657 .into_iter()
1658 .filter_map(|x| x.and_then(|x| x.node))
1659 .collect::<Vec<_>>();
1660
1661 let new_vars = end_cursor.map(|cursor| types::AllPackageReleasesVars {
1662 after: Some(cursor),
1663 ..vars
1664 });
1665
1666 Ok(Some((items, new_vars)))
1667 },
1668 )
1669}
1670
1671#[derive(Debug, PartialEq)]
1672pub enum TokenKind {
1673 SSH,
1674}
1675
1676pub async fn generate_deploy_config_token_raw(
1677 client: &WasmerClient,
1678 token_kind: TokenKind,
1679) -> Result<String, anyhow::Error> {
1680 let res = client
1681 .run_graphql(types::GenerateDeployConfigToken::build(
1682 types::GenerateDeployConfigTokenVars {
1683 input: match token_kind {
1684 TokenKind::SSH => "{}".to_string(),
1685 },
1686 },
1687 ))
1688 .await?;
1689
1690 res.generate_deploy_config_token
1691 .map(|x| x.token)
1692 .context("no token returned")
1693}
1694
1695#[tracing::instrument(skip_all, level = "debug")]
1700#[allow(clippy::let_with_type_underscore)]
1701#[allow(clippy::too_many_arguments)]
1702fn get_app_logs(
1703 client: &WasmerClient,
1704 name: String,
1705 owner: String,
1706 tag: Option<String>,
1707 start: OffsetDateTime,
1708 end: Option<OffsetDateTime>,
1709 watch: bool,
1710 streams: Option<Vec<LogStream>>,
1711 request_id: Option<String>,
1712 instance_ids: Option<Vec<String>>,
1713) -> impl futures::Stream<Item = Result<Vec<Log>, anyhow::Error>> + '_ {
1714 let span = tracing::Span::current();
1718
1719 futures::stream::try_unfold(start, move |start| {
1720 let variables = types::GetDeployAppLogsVars {
1721 name: name.clone(),
1722 owner: owner.clone(),
1723 version: tag.clone(),
1724 first: Some(100),
1725 starting_from: unix_timestamp(start),
1726 until: end.map(unix_timestamp),
1727 streams: streams.clone(),
1728 request_id: request_id.clone(),
1729 instance_ids: instance_ids.clone(),
1730 };
1731
1732 let fut = async move {
1733 loop {
1734 let deploy_app_version = client
1735 .run_graphql(types::GetDeployAppLogs::build(variables.clone()))
1736 .await?
1737 .get_deploy_app_version
1738 .context("app version not found")?;
1739
1740 let page: Vec<_> = deploy_app_version
1741 .logs
1742 .edges
1743 .into_iter()
1744 .flatten()
1745 .filter_map(|edge| edge.node)
1746 .collect();
1747
1748 if page.is_empty() {
1749 if watch {
1750 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
1755 std::thread::sleep(Duration::from_secs(1));
1756
1757 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
1758 tokio::time::sleep(Duration::from_secs(1)).await;
1759
1760 continue;
1761 }
1762
1763 break Ok(None);
1764 } else {
1765 let last_message = page.last().expect("The page is non-empty");
1766 let timestamp = last_message.timestamp;
1767 let timestamp = OffsetDateTime::from_unix_timestamp_nanos(timestamp as i128)
1770 .with_context(|| {
1771 format!("Unable to interpret {timestamp} as a unix timestamp")
1772 })?;
1773
1774 let next_timestamp = timestamp + Duration::from_nanos(1_000);
1780
1781 break Ok(Some((page, next_timestamp)));
1782 }
1783 }
1784 };
1785
1786 fut.instrument(span.clone())
1787 })
1788}
1789
1790#[tracing::instrument(skip_all, level = "debug")]
1796#[allow(clippy::let_with_type_underscore)]
1797#[allow(clippy::too_many_arguments)]
1798pub async fn get_app_logs_paginated(
1799 client: &WasmerClient,
1800 name: String,
1801 owner: String,
1802 tag: Option<String>,
1803 start: OffsetDateTime,
1804 end: Option<OffsetDateTime>,
1805 watch: bool,
1806 streams: Option<Vec<LogStream>>,
1807) -> impl futures::Stream<Item = Result<Vec<Log>, anyhow::Error>> + '_ {
1808 let stream = get_app_logs(
1809 client, name, owner, tag, start, end, watch, streams, None, None,
1810 );
1811
1812 stream.map(|res| {
1813 let mut logs = Vec::new();
1814 let mut hasher = HashSet::new();
1815 let mut page = res?;
1816
1817 page.retain(|log| hasher.insert((log.message.clone(), log.timestamp.round() as i128)));
1820
1821 logs.extend(page);
1822
1823 Ok(logs)
1824 })
1825}
1826
1827#[tracing::instrument(skip_all, level = "debug")]
1833#[allow(clippy::let_with_type_underscore)]
1834#[allow(clippy::too_many_arguments)]
1835pub async fn get_app_logs_paginated_filter_instance(
1836 client: &WasmerClient,
1837 name: String,
1838 owner: String,
1839 tag: Option<String>,
1840 start: OffsetDateTime,
1841 end: Option<OffsetDateTime>,
1842 watch: bool,
1843 streams: Option<Vec<LogStream>>,
1844 instance_ids: Vec<String>,
1845) -> impl futures::Stream<Item = Result<Vec<Log>, anyhow::Error>> + '_ {
1846 let stream = get_app_logs(
1847 client,
1848 name,
1849 owner,
1850 tag,
1851 start,
1852 end,
1853 watch,
1854 streams,
1855 None,
1856 Some(instance_ids),
1857 );
1858
1859 stream.map(|res| {
1860 let mut logs = Vec::new();
1861 let mut hasher = HashSet::new();
1862 let mut page = res?;
1863
1864 page.retain(|log| hasher.insert((log.message.clone(), log.timestamp.round() as i128)));
1867
1868 logs.extend(page);
1869
1870 Ok(logs)
1871 })
1872}
1873
1874#[tracing::instrument(skip_all, level = "debug")]
1880#[allow(clippy::let_with_type_underscore)]
1881#[allow(clippy::too_many_arguments)]
1882pub async fn get_app_logs_paginated_filter_request(
1883 client: &WasmerClient,
1884 name: String,
1885 owner: String,
1886 tag: Option<String>,
1887 start: OffsetDateTime,
1888 end: Option<OffsetDateTime>,
1889 watch: bool,
1890 streams: Option<Vec<LogStream>>,
1891 request_id: String,
1892) -> impl futures::Stream<Item = Result<Vec<Log>, anyhow::Error>> + '_ {
1893 let stream = get_app_logs(
1894 client,
1895 name,
1896 owner,
1897 tag,
1898 start,
1899 end,
1900 watch,
1901 streams,
1902 Some(request_id),
1903 None,
1904 );
1905
1906 stream.map(|res| {
1907 let mut logs = Vec::new();
1908 let mut hasher = HashSet::new();
1909 let mut page = res?;
1910
1911 page.retain(|log| hasher.insert((log.message.clone(), log.timestamp.round() as i128)));
1914
1915 logs.extend(page);
1916
1917 Ok(logs)
1918 })
1919}
1920
1921pub async fn get_domain(
1925 client: &WasmerClient,
1926 domain: String,
1927) -> Result<Option<types::DnsDomain>, anyhow::Error> {
1928 let vars = types::GetDomainVars { domain };
1929
1930 let opt = client
1931 .run_graphql(types::GetDomain::build(vars))
1932 .await
1933 .map_err(anyhow::Error::from)?
1934 .get_domain;
1935 Ok(opt)
1936}
1937
1938pub async fn get_domain_zone_file(
1942 client: &WasmerClient,
1943 domain: String,
1944) -> Result<Option<types::DnsDomainWithZoneFile>, anyhow::Error> {
1945 let vars = types::GetDomainVars { domain };
1946
1947 let opt = client
1948 .run_graphql(types::GetDomainWithZoneFile::build(vars))
1949 .await
1950 .map_err(anyhow::Error::from)?
1951 .get_domain;
1952 Ok(opt)
1953}
1954
1955pub async fn get_domain_with_records(
1957 client: &WasmerClient,
1958 domain: String,
1959) -> Result<Option<types::DnsDomainWithRecords>, anyhow::Error> {
1960 let vars = types::GetDomainVars { domain };
1961
1962 let opt = client
1963 .run_graphql(types::GetDomainWithRecords::build(vars))
1964 .await
1965 .map_err(anyhow::Error::from)?
1966 .get_domain;
1967 Ok(opt)
1968}
1969
1970pub async fn register_domain(
1972 client: &WasmerClient,
1973 name: String,
1974 namespace: Option<String>,
1975 import_records: Option<bool>,
1976) -> Result<types::DnsDomain, anyhow::Error> {
1977 let vars = types::RegisterDomainVars {
1978 name,
1979 namespace,
1980 import_records,
1981 };
1982 let opt = client
1983 .run_graphql_strict(types::RegisterDomain::build(vars))
1984 .await
1985 .map_err(anyhow::Error::from)?
1986 .register_domain
1987 .context("Domain registration failed")?
1988 .domain
1989 .context("Domain registration failed, no associatede domain found.")?;
1990 Ok(opt)
1991}
1992
1993pub async fn get_all_dns_records(
1997 client: &WasmerClient,
1998 vars: types::GetAllDnsRecordsVariables,
1999) -> Result<types::DnsRecordConnection, anyhow::Error> {
2000 client
2001 .run_graphql_strict(types::GetAllDnsRecords::build(vars))
2002 .await
2003 .map_err(anyhow::Error::from)
2004 .map(|x| x.get_all_dnsrecords)
2005}
2006
2007pub async fn get_all_domains(
2009 client: &WasmerClient,
2010 vars: types::GetAllDomainsVariables,
2011) -> Result<Vec<DnsDomain>, anyhow::Error> {
2012 let connection = client
2013 .run_graphql_strict(types::GetAllDomains::build(vars))
2014 .await
2015 .map_err(anyhow::Error::from)
2016 .map(|x| x.get_all_domains)
2017 .context("no domains returned")?;
2018 Ok(connection
2019 .edges
2020 .into_iter()
2021 .flatten()
2022 .filter_map(|x| x.node)
2023 .collect())
2024}
2025
2026pub fn get_all_dns_records_stream(
2030 client: &WasmerClient,
2031 vars: types::GetAllDnsRecordsVariables,
2032) -> impl futures::Stream<Item = Result<Vec<types::DnsRecord>, anyhow::Error>> + '_ {
2033 futures::stream::try_unfold(
2034 Some(vars),
2035 move |vars: Option<types::GetAllDnsRecordsVariables>| async move {
2036 let vars = match vars {
2037 Some(vars) => vars,
2038 None => return Ok(None),
2039 };
2040
2041 let page = get_all_dns_records(client, vars.clone()).await?;
2042
2043 let end_cursor = page.page_info.end_cursor;
2044
2045 let items = page
2046 .edges
2047 .into_iter()
2048 .filter_map(|x| x.and_then(|x| x.node))
2049 .collect::<Vec<_>>();
2050
2051 let new_vars = end_cursor.map(|c| types::GetAllDnsRecordsVariables {
2052 after: Some(c),
2053 ..vars
2054 });
2055
2056 Ok(Some((items, new_vars)))
2057 },
2058 )
2059}
2060
2061pub async fn purge_cache_for_app_version(
2062 client: &WasmerClient,
2063 vars: types::PurgeCacheForAppVersionVars,
2064) -> Result<(), anyhow::Error> {
2065 client
2066 .run_graphql_strict(types::PurgeCacheForAppVersion::build(vars))
2067 .await
2068 .map_err(anyhow::Error::from)
2069 .map(|x| x.purge_cache_for_app_version)
2070 .context("backend did not return data")?;
2071
2072 Ok(())
2073}
2074
2075fn unix_timestamp(ts: OffsetDateTime) -> f64 {
2078 let nanos_per_second = 1_000_000_000;
2079 let timestamp = ts.unix_timestamp_nanos();
2080 let nanos = timestamp % nanos_per_second;
2081 let secs = timestamp / nanos_per_second;
2082
2083 (secs as f64) + (nanos as f64 / nanos_per_second as f64)
2084}
2085
2086pub async fn upsert_domain_from_zone_file(
2088 client: &WasmerClient,
2089 zone_file_contents: String,
2090 delete_missing_records: bool,
2091) -> Result<DnsDomain, anyhow::Error> {
2092 let vars = UpsertDomainFromZoneFileVars {
2093 zone_file: zone_file_contents,
2094 delete_missing_records: Some(delete_missing_records),
2095 };
2096 let res = client
2097 .run_graphql_strict(types::UpsertDomainFromZoneFile::build(vars))
2098 .await?;
2099
2100 let domain = res
2101 .upsert_domain_from_zone_file
2102 .context("Upserting domain from zonefile failed")?
2103 .domain;
2104
2105 Ok(domain)
2106}