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
844pub async fn get_signed_url_for_package_upload(
846 client: &WasmerClient,
847 expires_after_seconds: Option<i32>,
848 filename: Option<&str>,
849 name: Option<&str>,
850 version: Option<&str>,
851) -> Result<Option<SignedUrl>, anyhow::Error> {
852 client
853 .run_graphql_strict(types::GetSignedUrlForPackageUpload::build(
854 GetSignedUrlForPackageUploadVariables {
855 expires_after_seconds,
856 filename,
857 name,
858 version,
859 },
860 ))
861 .await
862 .map(|r| r.get_signed_url_for_package_upload)
863}
864
865pub async fn generate_upload_url(
867 client: &WasmerClient,
868 filename: &str,
869 name: Option<&str>,
870 version: Option<&str>,
871 expires_after_seconds: Option<i32>,
872) -> Result<SignedUrl, anyhow::Error> {
873 let payload = client
874 .run_graphql_strict(types::GenerateUploadUrl::build(
875 GenerateUploadUrlVariables {
876 expires_after_seconds,
877 filename,
878 name,
879 version,
880 },
881 ))
882 .await
883 .and_then(|res| {
884 res.generate_upload_url
885 .context("generateUploadUrl mutation did not return data")
886 })?;
887
888 Ok(payload.signed_url)
889}
890
891pub async fn autobuild_config_for_zip_upload(
893 client: &WasmerClient,
894 upload_url: &str,
895) -> Result<Option<types::AutobuildConfigForZipUploadPayload>, anyhow::Error> {
896 client
897 .run_graphql_strict(types::AutobuildConfigForZipUpload::build(
898 AutobuildConfigForZipUploadVariables { upload_url },
899 ))
900 .await
901 .map(|res| res.autobuild_config_for_zip_upload)
902}
903
904pub async fn deploy_via_autobuild(
906 client: &WasmerClient,
907 vars: DeployViaAutobuildVars,
908) -> Result<Option<types::DeployViaAutobuildPayload>, anyhow::Error> {
909 client
910 .run_graphql_strict(types::DeployViaAutobuild::build(vars))
911 .await
912 .map(|res| res.deploy_via_autobuild)
913}
914pub async fn push_package_release(
916 client: &WasmerClient,
917 name: Option<&str>,
918 namespace: &str,
919 signed_url: &str,
920 private: Option<bool>,
921) -> Result<Option<PushPackageReleasePayload>, anyhow::Error> {
922 client
923 .run_graphql_strict(types::PushPackageRelease::build(
924 types::PushPackageReleaseVariables {
925 name,
926 namespace,
927 private,
928 signed_url,
929 },
930 ))
931 .await
932 .map(|r| r.push_package_release)
933}
934
935#[allow(clippy::too_many_arguments)]
936pub async fn tag_package_release(
937 client: &WasmerClient,
938 description: Option<&str>,
939 homepage: Option<&str>,
940 license: Option<&str>,
941 license_file: Option<&str>,
942 manifest: Option<&str>,
943 name: &str,
944 namespace: Option<&str>,
945 package_release_id: &cynic::Id,
946 private: Option<bool>,
947 readme: Option<&str>,
948 repository: Option<&str>,
949 version: &str,
950) -> Result<Option<TagPackageReleasePayload>, anyhow::Error> {
951 client
952 .run_graphql_strict(types::TagPackageRelease::build(
953 types::TagPackageReleaseVariables {
954 description,
955 homepage,
956 license,
957 license_file,
958 manifest,
959 name,
960 namespace,
961 package_release_id,
962 private,
963 readme,
964 repository,
965 version,
966 },
967 ))
968 .await
969 .map(|r| r.tag_package_release)
970}
971
972pub async fn current_user(client: &WasmerClient) -> Result<Option<types::User>, anyhow::Error> {
974 client
975 .run_graphql(types::GetCurrentUser::build(()))
976 .await
977 .map(|x| x.viewer)
978}
979
980pub async fn current_user_with_namespaces(
984 client: &WasmerClient,
985 namespace_role: Option<types::GrapheneRole>,
986) -> Result<types::UserWithNamespaces, anyhow::Error> {
987 client
988 .run_graphql(types::GetCurrentUserWithNamespaces::build(
989 types::GetCurrentUserWithNamespacesVars { namespace_role },
990 ))
991 .await?
992 .viewer
993 .context("not logged in")
994}
995
996pub async fn get_app(
998 client: &WasmerClient,
999 owner: String,
1000 name: String,
1001) -> Result<Option<types::DeployApp>, anyhow::Error> {
1002 client
1003 .run_graphql(types::GetDeployApp::build(types::GetDeployAppVars {
1004 name,
1005 owner,
1006 }))
1007 .await
1008 .map(|x| x.get_deploy_app)
1009}
1010
1011pub async fn get_app_by_alias(
1013 client: &WasmerClient,
1014 alias: String,
1015) -> Result<Option<types::DeployApp>, anyhow::Error> {
1016 client
1017 .run_graphql(types::GetDeployAppByAlias::build(
1018 types::GetDeployAppByAliasVars { alias },
1019 ))
1020 .await
1021 .map(|x| x.get_app_by_global_alias)
1022}
1023
1024pub async fn get_app_version(
1026 client: &WasmerClient,
1027 owner: String,
1028 name: String,
1029 version: String,
1030) -> Result<Option<types::DeployAppVersion>, anyhow::Error> {
1031 client
1032 .run_graphql(types::GetDeployAppVersion::build(
1033 types::GetDeployAppVersionVars {
1034 name,
1035 owner,
1036 version,
1037 },
1038 ))
1039 .await
1040 .map(|x| x.get_deploy_app_version)
1041}
1042
1043pub async fn get_app_with_version(
1045 client: &WasmerClient,
1046 owner: String,
1047 name: String,
1048 version: String,
1049) -> Result<GetDeployAppAndVersion, anyhow::Error> {
1050 client
1051 .run_graphql(types::GetDeployAppAndVersion::build(
1052 types::GetDeployAppAndVersionVars {
1053 name,
1054 owner,
1055 version,
1056 },
1057 ))
1058 .await
1059}
1060
1061pub async fn get_app_and_package_by_name(
1063 client: &WasmerClient,
1064 vars: types::GetPackageAndAppVars,
1065) -> Result<(Option<types::Package>, Option<types::DeployApp>), anyhow::Error> {
1066 let res = client
1067 .run_graphql(types::GetPackageAndApp::build(vars))
1068 .await?;
1069 Ok((res.get_package, res.get_deploy_app))
1070}
1071
1072pub async fn get_deploy_apps(
1074 client: &WasmerClient,
1075 vars: types::GetDeployAppsVars,
1076) -> Result<DeployAppConnection, anyhow::Error> {
1077 let res = client
1078 .run_graphql(types::GetDeployApps::build(vars))
1079 .await?;
1080 res.get_deploy_apps.context("no apps returned")
1081}
1082
1083pub fn get_deploy_apps_stream(
1085 client: &WasmerClient,
1086 vars: types::GetDeployAppsVars,
1087) -> impl futures::Stream<Item = Result<Vec<DeployApp>, anyhow::Error>> + '_ {
1088 futures::stream::try_unfold(
1089 Some(vars),
1090 move |vars: Option<types::GetDeployAppsVars>| async move {
1091 let vars = match vars {
1092 Some(vars) => vars,
1093 None => return Ok(None),
1094 };
1095
1096 let page = get_deploy_apps(client, vars.clone()).await?;
1097
1098 let end_cursor = page.page_info.end_cursor;
1099
1100 let items = page
1101 .edges
1102 .into_iter()
1103 .filter_map(|x| x.and_then(|x| x.node))
1104 .collect::<Vec<_>>();
1105
1106 let new_vars = end_cursor.map(|c| types::GetDeployAppsVars {
1107 after: Some(c),
1108 ..vars
1109 });
1110
1111 Ok(Some((items, new_vars)))
1112 },
1113 )
1114}
1115
1116pub async fn get_deploy_app_versions(
1118 client: &WasmerClient,
1119 vars: GetDeployAppVersionsVars,
1120) -> Result<DeployAppVersionConnection, anyhow::Error> {
1121 let res = client
1122 .run_graphql_strict(types::GetDeployAppVersions::build(vars))
1123 .await?;
1124 let versions = res.get_deploy_app.context("app not found")?.versions;
1125 Ok(versions)
1126}
1127
1128pub async fn app_deployments(
1130 client: &WasmerClient,
1131 vars: types::GetAppDeploymentsVariables,
1132) -> Result<Vec<types::Deployment>, anyhow::Error> {
1133 let res = client
1134 .run_graphql_strict(types::GetAppDeployments::build(vars))
1135 .await?;
1136 let builds = res
1137 .get_deploy_app
1138 .and_then(|x| x.deployments)
1139 .context("no data returned")?
1140 .edges
1141 .into_iter()
1142 .flatten()
1143 .filter_map(|x| x.node)
1144 .collect();
1145
1146 Ok(builds)
1147}
1148
1149pub async fn app_deployment(
1151 client: &WasmerClient,
1152 id: String,
1153) -> Result<types::AutobuildRepository, anyhow::Error> {
1154 let node = get_node(client, id.clone())
1155 .await?
1156 .with_context(|| format!("app deployment with id '{id}' not found"))?;
1157 match node {
1158 types::Node::AutobuildRepository(x) => Ok(*x),
1159 _ => anyhow::bail!("invalid node type returned"),
1160 }
1161}
1162
1163pub async fn all_app_versions(
1167 client: &WasmerClient,
1168 owner: String,
1169 name: String,
1170) -> Result<Vec<DeployAppVersion>, anyhow::Error> {
1171 let mut vars = GetDeployAppVersionsVars {
1172 owner,
1173 name,
1174 offset: None,
1175 before: None,
1176 after: None,
1177 first: Some(10),
1178 last: None,
1179 sort_by: None,
1180 };
1181
1182 let mut all_versions = Vec::<DeployAppVersion>::new();
1183
1184 loop {
1185 let page = get_deploy_app_versions(client, vars.clone()).await?;
1186 if page.edges.is_empty() {
1187 break;
1188 }
1189
1190 for edge in page.edges {
1191 let edge = match edge {
1192 Some(edge) => edge,
1193 None => continue,
1194 };
1195 let version = match edge.node {
1196 Some(item) => item,
1197 None => continue,
1198 };
1199
1200 if all_versions.iter().any(|v| v.id == version.id) == false {
1202 all_versions.push(version);
1203 }
1204
1205 vars.after = Some(edge.cursor);
1207 }
1208 }
1209
1210 Ok(all_versions)
1211}
1212
1213pub async fn get_deploy_app_versions_by_id(
1215 client: &WasmerClient,
1216 vars: types::GetDeployAppVersionsByIdVars,
1217) -> Result<DeployAppVersionConnection, anyhow::Error> {
1218 let res = client
1219 .run_graphql_strict(types::GetDeployAppVersionsById::build(vars))
1220 .await?;
1221 let versions = res
1222 .node
1223 .context("app not found")?
1224 .into_app()
1225 .context("invalid node type returned")?
1226 .versions;
1227 Ok(versions)
1228}
1229
1230pub async fn all_app_versions_by_id(
1234 client: &WasmerClient,
1235 app_id: impl Into<String>,
1236) -> Result<Vec<DeployAppVersion>, anyhow::Error> {
1237 let mut vars = types::GetDeployAppVersionsByIdVars {
1238 id: cynic::Id::new(app_id),
1239 offset: None,
1240 before: None,
1241 after: None,
1242 first: Some(10),
1243 last: None,
1244 sort_by: None,
1245 };
1246
1247 let mut all_versions = Vec::<DeployAppVersion>::new();
1248
1249 loop {
1250 let page = get_deploy_app_versions_by_id(client, vars.clone()).await?;
1251 if page.edges.is_empty() {
1252 break;
1253 }
1254
1255 for edge in page.edges {
1256 let edge = match edge {
1257 Some(edge) => edge,
1258 None => continue,
1259 };
1260 let version = match edge.node {
1261 Some(item) => item,
1262 None => continue,
1263 };
1264
1265 if all_versions.iter().any(|v| v.id == version.id) == false {
1267 all_versions.push(version);
1268 }
1269
1270 vars.after = Some(edge.cursor);
1272 }
1273 }
1274
1275 Ok(all_versions)
1276}
1277
1278pub async fn app_version_activate(
1280 client: &WasmerClient,
1281 version: String,
1282) -> Result<DeployApp, anyhow::Error> {
1283 let res = client
1284 .run_graphql_strict(types::MarkAppVersionAsActive::build(
1285 types::MarkAppVersionAsActiveVars {
1286 input: types::MarkAppVersionAsActiveInput {
1287 app_version: version.into(),
1288 },
1289 },
1290 ))
1291 .await?;
1292 res.mark_app_version_as_active
1293 .context("app not found")
1294 .map(|x| x.app)
1295}
1296
1297pub async fn get_node(
1299 client: &WasmerClient,
1300 id: String,
1301) -> Result<Option<types::Node>, anyhow::Error> {
1302 client
1303 .run_graphql(types::GetNode::build(types::GetNodeVars { id: id.into() }))
1304 .await
1305 .map(|x| x.node)
1306}
1307
1308pub async fn get_app_by_id(
1310 client: &WasmerClient,
1311 app_id: String,
1312) -> Result<DeployApp, anyhow::Error> {
1313 get_app_by_id_opt(client, app_id)
1314 .await?
1315 .context("app not found")
1316}
1317
1318pub async fn get_app_by_id_opt(
1320 client: &WasmerClient,
1321 app_id: String,
1322) -> Result<Option<DeployApp>, anyhow::Error> {
1323 let app_opt = client
1324 .run_graphql(types::GetDeployAppById::build(
1325 types::GetDeployAppByIdVars {
1326 app_id: app_id.into(),
1327 },
1328 ))
1329 .await?
1330 .app;
1331
1332 if let Some(app) = app_opt {
1333 let app = app.into_deploy_app().context("app conversion failed")?;
1334 Ok(Some(app))
1335 } else {
1336 Ok(None)
1337 }
1338}
1339
1340pub async fn get_app_with_version_by_id(
1342 client: &WasmerClient,
1343 app_id: String,
1344 version_id: String,
1345) -> Result<(DeployApp, DeployAppVersion), anyhow::Error> {
1346 let res = client
1347 .run_graphql(types::GetDeployAppAndVersionById::build(
1348 types::GetDeployAppAndVersionByIdVars {
1349 app_id: app_id.into(),
1350 version_id: version_id.into(),
1351 },
1352 ))
1353 .await?;
1354
1355 let app = res
1356 .app
1357 .context("app not found")?
1358 .into_deploy_app()
1359 .context("app conversion failed")?;
1360 let version = res
1361 .version
1362 .context("version not found")?
1363 .into_deploy_app_version()
1364 .context("version conversion failed")?;
1365
1366 Ok((app, version))
1367}
1368
1369pub async fn get_app_version_by_id(
1371 client: &WasmerClient,
1372 version_id: String,
1373) -> Result<DeployAppVersion, anyhow::Error> {
1374 client
1375 .run_graphql(types::GetDeployAppVersionById::build(
1376 types::GetDeployAppVersionByIdVars {
1377 version_id: version_id.into(),
1378 },
1379 ))
1380 .await?
1381 .version
1382 .context("app not found")?
1383 .into_deploy_app_version()
1384 .context("app version conversion failed")
1385}
1386
1387pub async fn get_app_version_by_id_with_app(
1388 client: &WasmerClient,
1389 version_id: String,
1390) -> Result<(DeployApp, DeployAppVersion), anyhow::Error> {
1391 let version = client
1392 .run_graphql(types::GetDeployAppVersionById::build(
1393 types::GetDeployAppVersionByIdVars {
1394 version_id: version_id.into(),
1395 },
1396 ))
1397 .await?
1398 .version
1399 .context("app not found")?
1400 .into_deploy_app_version()
1401 .context("app version conversion failed")?;
1402
1403 let app_id = version
1404 .app
1405 .as_ref()
1406 .context("could not load app for version")?
1407 .id
1408 .clone();
1409
1410 let app = get_app_by_id(client, app_id.into_inner()).await?;
1411
1412 Ok((app, version))
1413}
1414
1415pub async fn user_apps_page(
1416 client: &WasmerClient,
1417 sort: types::DeployAppsSortBy,
1418 cursor: Option<String>,
1419) -> Result<Paginated<types::DeployApp>, anyhow::Error> {
1420 let user = client
1421 .run_graphql(types::GetCurrentUserWithApps::build(
1422 GetCurrentUserWithAppsVars {
1423 after: cursor,
1424 first: Some(10),
1425 sort: Some(sort),
1426 },
1427 ))
1428 .await?
1429 .viewer
1430 .context("not logged in")?;
1431
1432 let apps: Vec<_> = user
1433 .apps
1434 .edges
1435 .into_iter()
1436 .flatten()
1437 .filter_map(|x| x.node)
1438 .collect();
1439
1440 let out = Paginated {
1441 items: apps,
1442 next_cursor: user.apps.page_info.end_cursor,
1443 };
1444
1445 Ok(out)
1446}
1447
1448pub async fn user_apps(
1452 client: &WasmerClient,
1453 sort: types::DeployAppsSortBy,
1454) -> impl futures::Stream<Item = Result<Vec<types::DeployApp>, anyhow::Error>> + '_ {
1455 futures::stream::try_unfold(None, move |cursor| async move {
1456 let user = client
1457 .run_graphql(types::GetCurrentUserWithApps::build(
1458 GetCurrentUserWithAppsVars {
1459 first: Some(10),
1460 after: cursor,
1461 sort: Some(sort),
1462 },
1463 ))
1464 .await?
1465 .viewer
1466 .context("not logged in")?;
1467
1468 let apps: Vec<_> = user
1469 .apps
1470 .edges
1471 .into_iter()
1472 .flatten()
1473 .filter_map(|x| x.node)
1474 .collect();
1475
1476 let cursor = user.apps.page_info.end_cursor;
1477
1478 if apps.is_empty() {
1479 Ok(None)
1480 } else {
1481 Ok(Some((apps, cursor)))
1482 }
1483 })
1484}
1485
1486pub async fn user_accessible_apps(
1488 client: &WasmerClient,
1489 sort: types::DeployAppsSortBy,
1490) -> Result<
1491 impl futures::Stream<Item = Result<Vec<types::DeployApp>, anyhow::Error>> + '_,
1492 anyhow::Error,
1493> {
1494 let user_apps = user_apps(client, sort).await;
1495
1496 let namespace_res = client
1498 .run_graphql(types::GetCurrentUserWithNamespaces::build(
1499 types::GetCurrentUserWithNamespacesVars {
1500 namespace_role: None,
1501 },
1502 ))
1503 .await?;
1504 let active_user = namespace_res.viewer.context("not logged in")?;
1505 let namespace_names = active_user
1506 .namespaces
1507 .edges
1508 .iter()
1509 .filter_map(|edge| edge.as_ref())
1510 .filter_map(|edge| edge.node.as_ref())
1511 .map(|node| node.name.clone())
1512 .collect::<Vec<_>>();
1513
1514 let mut ns_apps = vec![];
1515 for ns in namespace_names {
1516 let apps = namespace_apps(client, ns, sort).await;
1517 ns_apps.push(apps);
1518 }
1519
1520 Ok((user_apps, ns_apps.merge()).merge())
1521}
1522
1523pub async fn namespace_apps_page(
1527 client: &WasmerClient,
1528 namespace: String,
1529 sort: types::DeployAppsSortBy,
1530 cursor: Option<String>,
1531) -> Result<Paginated<types::DeployApp>, anyhow::Error> {
1532 let namespace = namespace.clone();
1533
1534 let res = client
1535 .run_graphql(types::GetNamespaceApps::build(GetNamespaceAppsVars {
1536 name: namespace.to_string(),
1537 after: cursor,
1538 sort: Some(sort),
1539 }))
1540 .await?
1541 .get_namespace
1542 .context("namespace not found")?
1543 .apps;
1544
1545 let apps: Vec<_> = res
1546 .edges
1547 .into_iter()
1548 .flatten()
1549 .filter_map(|x| x.node)
1550 .collect();
1551
1552 let out = Paginated {
1553 items: apps,
1554 next_cursor: res.page_info.end_cursor,
1555 };
1556
1557 Ok(out)
1558}
1559
1560pub async fn namespace_apps(
1564 client: &WasmerClient,
1565 namespace: String,
1566 sort: types::DeployAppsSortBy,
1567) -> impl futures::Stream<Item = Result<Vec<types::DeployApp>, anyhow::Error>> + '_ {
1568 let namespace = namespace.clone();
1569
1570 futures::stream::try_unfold((None, namespace), move |(cursor, namespace)| async move {
1571 let res = client
1572 .run_graphql(types::GetNamespaceApps::build(GetNamespaceAppsVars {
1573 name: namespace.to_string(),
1574 after: cursor,
1575 sort: Some(sort),
1576 }))
1577 .await?;
1578
1579 let ns = res
1580 .get_namespace
1581 .with_context(|| format!("failed to get namespace '{namespace}'"))?;
1582
1583 let apps: Vec<_> = ns
1584 .apps
1585 .edges
1586 .into_iter()
1587 .flatten()
1588 .filter_map(|x| x.node)
1589 .collect();
1590
1591 let cursor = ns.apps.page_info.end_cursor;
1592
1593 if apps.is_empty() {
1594 Ok(None)
1595 } else {
1596 Ok(Some((apps, (cursor, namespace))))
1597 }
1598 })
1599}
1600
1601pub async fn publish_deploy_app(
1603 client: &WasmerClient,
1604 vars: PublishDeployAppVars,
1605) -> Result<DeployAppVersion, anyhow::Error> {
1606 let res = client
1607 .run_graphql_raw(types::PublishDeployApp::build(vars))
1608 .await?;
1609
1610 if let Some(app) = res
1611 .data
1612 .and_then(|d| d.publish_deploy_app)
1613 .map(|d| d.deploy_app_version)
1614 {
1615 Ok(app)
1616 } else {
1617 Err(GraphQLApiFailure::from_errors(
1618 "could not publish app",
1619 res.errors,
1620 ))
1621 }
1622}
1623
1624pub async fn delete_app(client: &WasmerClient, app_id: String) -> Result<(), anyhow::Error> {
1626 let res = client
1627 .run_graphql_strict(types::DeleteApp::build(types::DeleteAppVars {
1628 app_id: app_id.into(),
1629 }))
1630 .await?
1631 .delete_app
1632 .context("API did not return data for the delete_app mutation")?;
1633
1634 if !res.success {
1635 bail!("App deletion failed for an unknown reason");
1636 }
1637
1638 Ok(())
1639}
1640
1641pub async fn user_namespaces(
1643 client: &WasmerClient,
1644) -> Result<Vec<types::Namespace>, anyhow::Error> {
1645 let user = client
1646 .run_graphql(types::GetCurrentUserWithNamespaces::build(
1647 types::GetCurrentUserWithNamespacesVars {
1648 namespace_role: None,
1649 },
1650 ))
1651 .await?
1652 .viewer
1653 .context("not logged in")?;
1654
1655 let ns = user
1656 .namespaces
1657 .edges
1658 .into_iter()
1659 .flatten()
1660 .filter_map(|x| x.node)
1662 .collect();
1663
1664 Ok(ns)
1665}
1666
1667pub async fn get_namespace(
1669 client: &WasmerClient,
1670 name: String,
1671) -> Result<Option<types::Namespace>, anyhow::Error> {
1672 client
1673 .run_graphql(types::GetNamespace::build(types::GetNamespaceVars { name }))
1674 .await
1675 .map(|x| x.get_namespace)
1676}
1677
1678pub async fn create_namespace(
1680 client: &WasmerClient,
1681 vars: CreateNamespaceVars,
1682) -> Result<types::Namespace, anyhow::Error> {
1683 client
1684 .run_graphql(types::CreateNamespace::build(vars))
1685 .await?
1686 .create_namespace
1687 .map(|x| x.namespace)
1688 .context("no namespace returned")
1689}
1690
1691pub async fn get_package(
1693 client: &WasmerClient,
1694 name: String,
1695) -> Result<Option<types::Package>, anyhow::Error> {
1696 client
1697 .run_graphql_strict(types::GetPackage::build(types::GetPackageVars { name }))
1698 .await
1699 .map(|x| x.get_package)
1700}
1701
1702pub async fn get_package_version(
1704 client: &WasmerClient,
1705 name: String,
1706 version: String,
1707) -> Result<Option<types::PackageVersionWithPackage>, anyhow::Error> {
1708 client
1709 .run_graphql_strict(types::GetPackageVersion::build(
1710 types::GetPackageVersionVars { name, version },
1711 ))
1712 .await
1713 .map(|x| x.get_package_version)
1714}
1715
1716pub async fn get_package_versions(
1718 client: &WasmerClient,
1719 vars: types::AllPackageVersionsVars,
1720) -> Result<PackageVersionConnection, anyhow::Error> {
1721 let res = client
1722 .run_graphql(types::GetAllPackageVersions::build(vars))
1723 .await?;
1724 Ok(res.all_package_versions)
1725}
1726
1727pub async fn get_package_release(
1729 client: &WasmerClient,
1730 hash: &str,
1731) -> Result<Option<types::PackageWebc>, anyhow::Error> {
1732 let hash = hash.trim_start_matches("sha256:");
1733 client
1734 .run_graphql_strict(types::GetPackageRelease::build(
1735 types::GetPackageReleaseVars {
1736 hash: hash.to_string(),
1737 },
1738 ))
1739 .await
1740 .map(|x| x.get_package_release)
1741}
1742
1743pub async fn get_package_releases(
1744 client: &WasmerClient,
1745 vars: types::AllPackageReleasesVars,
1746) -> Result<types::PackageWebcConnection, anyhow::Error> {
1747 let res = client
1748 .run_graphql(types::GetAllPackageReleases::build(vars))
1749 .await?;
1750 Ok(res.all_package_releases)
1751}
1752
1753pub fn get_package_versions_stream(
1755 client: &WasmerClient,
1756 vars: types::AllPackageVersionsVars,
1757) -> impl futures::Stream<Item = Result<Vec<types::PackageVersionWithPackage>, anyhow::Error>> + '_
1758{
1759 futures::stream::try_unfold(
1760 Some(vars),
1761 move |vars: Option<types::AllPackageVersionsVars>| async move {
1762 let vars = match vars {
1763 Some(vars) => vars,
1764 None => return Ok(None),
1765 };
1766
1767 let page = get_package_versions(client, vars.clone()).await?;
1768
1769 let end_cursor = page.page_info.end_cursor;
1770
1771 let items = page
1772 .edges
1773 .into_iter()
1774 .filter_map(|x| x.and_then(|x| x.node))
1775 .collect::<Vec<_>>();
1776
1777 let new_vars = end_cursor.map(|cursor| types::AllPackageVersionsVars {
1778 after: Some(cursor),
1779 ..vars
1780 });
1781
1782 Ok(Some((items, new_vars)))
1783 },
1784 )
1785}
1786
1787pub fn get_package_releases_stream(
1789 client: &WasmerClient,
1790 vars: types::AllPackageReleasesVars,
1791) -> impl futures::Stream<Item = Result<Vec<types::PackageWebc>, anyhow::Error>> + '_ {
1792 futures::stream::try_unfold(
1793 Some(vars),
1794 move |vars: Option<types::AllPackageReleasesVars>| async move {
1795 let vars = match vars {
1796 Some(vars) => vars,
1797 None => return Ok(None),
1798 };
1799
1800 let page = get_package_releases(client, vars.clone()).await?;
1801
1802 let end_cursor = page.page_info.end_cursor;
1803
1804 let items = page
1805 .edges
1806 .into_iter()
1807 .filter_map(|x| x.and_then(|x| x.node))
1808 .collect::<Vec<_>>();
1809
1810 let new_vars = end_cursor.map(|cursor| types::AllPackageReleasesVars {
1811 after: Some(cursor),
1812 ..vars
1813 });
1814
1815 Ok(Some((items, new_vars)))
1816 },
1817 )
1818}
1819
1820#[derive(Debug, PartialEq)]
1821pub enum TokenKind {
1822 SSH,
1823}
1824
1825pub async fn generate_deploy_config_token_raw(
1826 client: &WasmerClient,
1827 token_kind: TokenKind,
1828) -> Result<String, anyhow::Error> {
1829 let res = client
1830 .run_graphql(types::GenerateDeployConfigToken::build(
1831 types::GenerateDeployConfigTokenVars {
1832 input: match token_kind {
1833 TokenKind::SSH => "{}".to_string(),
1834 },
1835 },
1836 ))
1837 .await?;
1838
1839 res.generate_deploy_config_token
1840 .map(|x| x.token)
1841 .context("no token returned")
1842}
1843
1844pub async fn generate_ssh_token(
1849 client: &WasmerClient,
1850 app_id: Option<String>,
1851) -> Result<String, anyhow::Error> {
1852 let res = client
1853 .run_graphql_strict(types::GenerateSshToken::build(
1854 types::GenerateSshTokenVariables {
1855 app_id: app_id.map(cynic::Id::new),
1856 },
1857 ))
1858 .await?;
1859
1860 res.generate_ssh_token
1861 .map(|x| x.token)
1862 .context("no token returned")
1863}
1864
1865#[tracing::instrument(skip_all, level = "debug")]
1870#[allow(clippy::let_with_type_underscore)]
1871#[allow(clippy::too_many_arguments)]
1872fn get_app_logs(
1873 client: &WasmerClient,
1874 name: String,
1875 owner: String,
1876 tag: Option<String>,
1877 start: OffsetDateTime,
1878 end: Option<OffsetDateTime>,
1879 watch: bool,
1880 streams: Option<Vec<LogStream>>,
1881 request_id: Option<String>,
1882 instance_ids: Option<Vec<String>>,
1883) -> impl futures::Stream<Item = Result<Vec<Log>, anyhow::Error>> + '_ {
1884 let span = tracing::Span::current();
1888
1889 futures::stream::try_unfold(start, move |start| {
1890 let variables = types::GetDeployAppLogsVars {
1891 name: name.clone(),
1892 owner: owner.clone(),
1893 version: tag.clone(),
1894 first: Some(100),
1895 starting_from: unix_timestamp(start),
1896 until: end.map(unix_timestamp),
1897 streams: streams.clone(),
1898 request_id: request_id.clone(),
1899 instance_ids: instance_ids.clone(),
1900 };
1901
1902 let fut = async move {
1903 loop {
1904 let deploy_app_version = client
1905 .run_graphql(types::GetDeployAppLogs::build(variables.clone()))
1906 .await?
1907 .get_deploy_app_version
1908 .context("app version not found")?;
1909
1910 let page: Vec<_> = deploy_app_version
1911 .logs
1912 .edges
1913 .into_iter()
1914 .flatten()
1915 .filter_map(|edge| edge.node)
1916 .collect();
1917
1918 if page.is_empty() {
1919 if watch {
1920 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
1925 std::thread::sleep(Duration::from_secs(1));
1926
1927 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
1928 tokio::time::sleep(Duration::from_secs(1)).await;
1929
1930 continue;
1931 }
1932
1933 break Ok(None);
1934 } else {
1935 let last_message = page.last().expect("The page is non-empty");
1936 let timestamp = last_message.timestamp;
1937 let timestamp = OffsetDateTime::from_unix_timestamp_nanos(timestamp as i128)
1940 .with_context(|| {
1941 format!("Unable to interpret {timestamp} as a unix timestamp")
1942 })?;
1943
1944 let next_timestamp = timestamp + Duration::from_nanos(1_000);
1950
1951 break Ok(Some((page, next_timestamp)));
1952 }
1953 }
1954 };
1955
1956 fut.instrument(span.clone())
1957 })
1958}
1959
1960#[tracing::instrument(skip_all, level = "debug")]
1966#[allow(clippy::let_with_type_underscore)]
1967#[allow(clippy::too_many_arguments)]
1968pub async fn get_app_logs_paginated(
1969 client: &WasmerClient,
1970 name: String,
1971 owner: String,
1972 tag: Option<String>,
1973 start: OffsetDateTime,
1974 end: Option<OffsetDateTime>,
1975 watch: bool,
1976 streams: Option<Vec<LogStream>>,
1977) -> impl futures::Stream<Item = Result<Vec<Log>, anyhow::Error>> + '_ {
1978 let stream = get_app_logs(
1979 client, name, owner, tag, start, end, watch, streams, None, None,
1980 );
1981
1982 stream.map(|res| {
1983 let mut logs = Vec::new();
1984 let mut hasher = HashSet::new();
1985 let mut page = res?;
1986
1987 page.retain(|log| hasher.insert((log.message.clone(), log.timestamp.round() as i128)));
1990
1991 logs.extend(page);
1992
1993 Ok(logs)
1994 })
1995}
1996
1997#[tracing::instrument(skip_all, level = "debug")]
2003#[allow(clippy::let_with_type_underscore)]
2004#[allow(clippy::too_many_arguments)]
2005pub async fn get_app_logs_paginated_filter_instance(
2006 client: &WasmerClient,
2007 name: String,
2008 owner: String,
2009 tag: Option<String>,
2010 start: OffsetDateTime,
2011 end: Option<OffsetDateTime>,
2012 watch: bool,
2013 streams: Option<Vec<LogStream>>,
2014 instance_ids: Vec<String>,
2015) -> impl futures::Stream<Item = Result<Vec<Log>, anyhow::Error>> + '_ {
2016 let stream = get_app_logs(
2017 client,
2018 name,
2019 owner,
2020 tag,
2021 start,
2022 end,
2023 watch,
2024 streams,
2025 None,
2026 Some(instance_ids),
2027 );
2028
2029 stream.map(|res| {
2030 let mut logs = Vec::new();
2031 let mut hasher = HashSet::new();
2032 let mut page = res?;
2033
2034 page.retain(|log| hasher.insert((log.message.clone(), log.timestamp.round() as i128)));
2037
2038 logs.extend(page);
2039
2040 Ok(logs)
2041 })
2042}
2043
2044#[tracing::instrument(skip_all, level = "debug")]
2050#[allow(clippy::let_with_type_underscore)]
2051#[allow(clippy::too_many_arguments)]
2052pub async fn get_app_logs_paginated_filter_request(
2053 client: &WasmerClient,
2054 name: String,
2055 owner: String,
2056 tag: Option<String>,
2057 start: OffsetDateTime,
2058 end: Option<OffsetDateTime>,
2059 watch: bool,
2060 streams: Option<Vec<LogStream>>,
2061 request_id: String,
2062) -> impl futures::Stream<Item = Result<Vec<Log>, anyhow::Error>> + '_ {
2063 let stream = get_app_logs(
2064 client,
2065 name,
2066 owner,
2067 tag,
2068 start,
2069 end,
2070 watch,
2071 streams,
2072 Some(request_id),
2073 None,
2074 );
2075
2076 stream.map(|res| {
2077 let mut logs = Vec::new();
2078 let mut hasher = HashSet::new();
2079 let mut page = res?;
2080
2081 page.retain(|log| hasher.insert((log.message.clone(), log.timestamp.round() as i128)));
2084
2085 logs.extend(page);
2086
2087 Ok(logs)
2088 })
2089}
2090
2091pub async fn get_domain(
2095 client: &WasmerClient,
2096 domain: String,
2097) -> Result<Option<types::DnsDomain>, anyhow::Error> {
2098 let vars = types::GetDomainVars { domain };
2099
2100 let opt = client
2101 .run_graphql(types::GetDomain::build(vars))
2102 .await?
2103 .get_domain;
2104 Ok(opt)
2105}
2106
2107pub async fn get_domain_zone_file(
2111 client: &WasmerClient,
2112 domain: String,
2113) -> Result<Option<types::DnsDomainWithZoneFile>, anyhow::Error> {
2114 let vars = types::GetDomainVars { domain };
2115
2116 let opt = client
2117 .run_graphql(types::GetDomainWithZoneFile::build(vars))
2118 .await?
2119 .get_domain;
2120 Ok(opt)
2121}
2122
2123pub async fn get_domain_with_records(
2125 client: &WasmerClient,
2126 domain: String,
2127) -> Result<Option<types::DnsDomainWithRecords>, anyhow::Error> {
2128 let vars = types::GetDomainVars { domain };
2129
2130 let opt = client
2131 .run_graphql(types::GetDomainWithRecords::build(vars))
2132 .await?
2133 .get_domain;
2134 Ok(opt)
2135}
2136
2137pub async fn register_domain(
2139 client: &WasmerClient,
2140 name: String,
2141 namespace: Option<String>,
2142 import_records: Option<bool>,
2143) -> Result<types::DnsDomain, anyhow::Error> {
2144 let vars = types::RegisterDomainVars {
2145 name,
2146 namespace,
2147 import_records,
2148 };
2149 let opt = client
2150 .run_graphql_strict(types::RegisterDomain::build(vars))
2151 .await?
2152 .register_domain
2153 .context("Domain registration failed")?
2154 .domain
2155 .context("Domain registration failed, no associatede domain found.")?;
2156 Ok(opt)
2157}
2158
2159pub async fn get_all_dns_records(
2163 client: &WasmerClient,
2164 vars: types::GetAllDnsRecordsVariables,
2165) -> Result<types::DnsRecordConnection, anyhow::Error> {
2166 client
2167 .run_graphql_strict(types::GetAllDnsRecords::build(vars))
2168 .await
2169 .map(|x| x.get_all_dnsrecords)
2170}
2171
2172pub async fn get_all_domains(
2174 client: &WasmerClient,
2175 vars: types::GetAllDomainsVariables,
2176) -> Result<Vec<DnsDomain>, anyhow::Error> {
2177 let connection = client
2178 .run_graphql_strict(types::GetAllDomains::build(vars))
2179 .await
2180 .map(|x| x.get_all_domains)
2181 .context("no domains returned")?;
2182 Ok(connection
2183 .edges
2184 .into_iter()
2185 .flatten()
2186 .filter_map(|x| x.node)
2187 .collect())
2188}
2189
2190pub fn get_all_dns_records_stream(
2194 client: &WasmerClient,
2195 vars: types::GetAllDnsRecordsVariables,
2196) -> impl futures::Stream<Item = Result<Vec<types::DnsRecord>, anyhow::Error>> + '_ {
2197 futures::stream::try_unfold(
2198 Some(vars),
2199 move |vars: Option<types::GetAllDnsRecordsVariables>| async move {
2200 let vars = match vars {
2201 Some(vars) => vars,
2202 None => return Ok(None),
2203 };
2204
2205 let page = get_all_dns_records(client, vars.clone()).await?;
2206
2207 let end_cursor = page.page_info.end_cursor;
2208
2209 let items = page
2210 .edges
2211 .into_iter()
2212 .filter_map(|x| x.and_then(|x| x.node))
2213 .collect::<Vec<_>>();
2214
2215 let new_vars = end_cursor.map(|c| types::GetAllDnsRecordsVariables {
2216 after: Some(c),
2217 ..vars
2218 });
2219
2220 Ok(Some((items, new_vars)))
2221 },
2222 )
2223}
2224
2225pub async fn purge_cache_for_app_version(
2226 client: &WasmerClient,
2227 vars: types::PurgeCacheForAppVersionVars,
2228) -> Result<(), anyhow::Error> {
2229 client
2230 .run_graphql_strict(types::PurgeCacheForAppVersion::build(vars))
2231 .await
2232 .map(|x| x.purge_cache_for_app_version)
2233 .context("backend did not return data")?;
2234
2235 Ok(())
2236}
2237
2238fn unix_timestamp(ts: OffsetDateTime) -> f64 {
2241 let nanos_per_second = 1_000_000_000;
2242 let timestamp = ts.unix_timestamp_nanos();
2243 let nanos = timestamp % nanos_per_second;
2244 let secs = timestamp / nanos_per_second;
2245
2246 (secs as f64) + (nanos as f64 / nanos_per_second as f64)
2247}
2248
2249pub async fn upsert_domain_from_zone_file(
2251 client: &WasmerClient,
2252 zone_file_contents: String,
2253 delete_missing_records: bool,
2254) -> Result<DnsDomain, anyhow::Error> {
2255 let vars = UpsertDomainFromZoneFileVars {
2256 zone_file: zone_file_contents,
2257 delete_missing_records: Some(delete_missing_records),
2258 };
2259 let res = client
2260 .run_graphql_strict(types::UpsertDomainFromZoneFile::build(vars))
2261 .await?;
2262
2263 let domain = res
2264 .upsert_domain_from_zone_file
2265 .context("Upserting domain from zonefile failed")?
2266 .domain;
2267
2268 Ok(domain)
2269}