1use std::{collections::BTreeMap, ops::Deref};
2
3use oci_client::{
4 client::{ImageData, ImageLayer, PushResponse},
5 manifest::OciImageManifest,
6 secrets::RegistryAuth,
7 Client, Reference,
8};
9
10use crate::{
11 config::ToConfig, WasmConfig, WASM_LAYER_MEDIA_TYPE, WASM_MANIFEST_CONFIG_MEDIA_TYPE,
12 WASM_MANIFEST_MEDIA_TYPE,
13};
14
15pub struct WasmClient {
17 client: Client,
18}
19
20impl AsRef<Client> for WasmClient {
21 fn as_ref(&self) -> &Client {
22 &self.client
23 }
24}
25
26impl Deref for WasmClient {
27 type Target = Client;
28
29 fn deref(&self) -> &Self::Target {
30 &self.client
31 }
32}
33
34impl From<Client> for WasmClient {
35 fn from(value: Client) -> Self {
36 Self { client: value }
37 }
38}
39
40impl From<WasmClient> for Client {
41 fn from(value: WasmClient) -> Self {
42 value.client
43 }
44}
45
46impl WasmClient {
47 pub fn new(client: Client) -> Self {
49 Self::from(client)
50 }
51
52 pub async fn pull(&self, image: &Reference, auth: &RegistryAuth) -> anyhow::Result<ImageData> {
55 let image_data = self
56 .client
57 .pull(image, auth, vec![WASM_LAYER_MEDIA_TYPE])
58 .await?;
59 if image_data.layers.len() != 1 {
60 anyhow::bail!("Wasm components must have exactly one layer");
61 }
62
63 if image_data.config.media_type != WASM_MANIFEST_CONFIG_MEDIA_TYPE {
64 anyhow::bail!(
65 "Wasm components must have a config of type {}",
66 WASM_MANIFEST_CONFIG_MEDIA_TYPE
67 );
68 }
69
70 Ok(image_data)
71 }
72
73 pub async fn pull_manifest_and_config(
76 &self,
77 image: &Reference,
78 auth: &RegistryAuth,
79 ) -> anyhow::Result<(OciImageManifest, WasmConfig, String)> {
80 let (manifest, digest, config) = self.client.pull_manifest_and_config(image, auth).await?;
81 if manifest.layers.len() != 1 {
82 anyhow::bail!("Wasm components must have exactly one layer");
83 }
84 if manifest.media_type.as_deref().unwrap_or_default() != WASM_MANIFEST_MEDIA_TYPE {
85 anyhow::bail!(
86 "Wasm components must have a manifest of type {}",
87 WASM_MANIFEST_MEDIA_TYPE
88 );
89 }
90
91 if manifest.config.media_type != WASM_MANIFEST_CONFIG_MEDIA_TYPE {
92 anyhow::bail!(
93 "Wasm components must have a config of type {}",
94 WASM_MANIFEST_CONFIG_MEDIA_TYPE
95 );
96 }
97
98 let config = WasmConfig::try_from(config)?;
99 Ok((manifest, config, digest))
100 }
101
102 pub async fn push(
105 &self,
106 image: &Reference,
107 auth: &RegistryAuth,
108 component_layer: ImageLayer,
109 config: impl ToConfig,
110 annotations: Option<BTreeMap<String, String>>,
111 ) -> anyhow::Result<PushResponse> {
112 let layers = vec![component_layer];
113 let config = config.to_config()?;
114 let mut manifest = OciImageManifest::build(&layers, &config, annotations);
115 manifest.media_type = Some(WASM_MANIFEST_MEDIA_TYPE.to_string());
116 self.client
117 .push(image, &layers, config, auth, Some(manifest))
118 .await
119 .map_err(Into::into)
120 }
121}