1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
//!  SPDX-License-Identifier: MIT
//!
//! Copyright (c) 2023, eunomia-bpf
//! All rights reserved.
//!
use std::path::Path;

use log::{debug, info};
use oci_distribution::{secrets::RegistryAuth, Client, Reference};
use tokio::{fs::File, io::AsyncReadExt};
use url::Url;

use crate::{
    error::{Error, Result},
    oci::wasm::pull::pull_wasm_from_registry,
};

use self::push::push_wasm_to_registry;

use super::{auth::get_auth_info_by_url, default_schema_port, get_client};

/// Module containing stuff related to pulling image
pub mod pull;
/// Module containing stuff related to pushing image
pub mod push;

/// Parse the URL, return things that will be used for pushing / pulling
/// returns (..., repo_url_strip_auth_info)
pub fn parse_img_url(url: &str) -> Result<(Client, RegistryAuth, Reference, String)> {
    let img_url = Url::parse(url).map_err(|e| Error::InvalidParam(e.to_string()))?;
    let auth = match get_auth_info_by_url(&img_url) {
        Ok((username, password)) => {
            println!("auth with username: {}", username);
            RegistryAuth::Basic(username, password)
        }
        Err(err) => {
            if matches!(err, Error::LoginInfoNotFound(_)) {
                RegistryAuth::Anonymous
            } else {
                return Err(err);
            }
        }
    };
    debug!("Parsed auth info: {:?}", auth);
    let client = get_client(&img_url)?;
    let Some(host) = img_url.host() else {
        return Err(Error::InvalidParam(format!("invalid url: {}",url)))
    };

    let repo_url = format!(
        "{}:{}{}",
        host,
        img_url
            .port()
            .unwrap_or(default_schema_port(img_url.scheme())?),
        img_url.path()
    );

    Ok((
        client,
        auth,
        repo_url
            .parse::<Reference>()
            .map_err(|e| Error::InvalidParam(e.to_string()))?,
        repo_url,
    ))
}

/// Push an image
pub async fn wasm_push(file: String, img_url: String) -> Result<()> {
    // TODO check the file is valid wasm file
    let path = Path::new(file.as_str());
    let mut module = vec![];
    if path.exists() && path.is_file() {
        info!("read content from file {}", file);
        let mut f = File::open(path).await.map_err(Error::IOErr)?;
        f.read_to_end(&mut module).await.map_err(Error::IOErr)?;
    } else {
        return Err(Error::InvalidParam(format!(
            "file {} not exist or is not regular file",
            file
        )));
    }

    let (mut client, auth, reference, repo_url) = parse_img_url(img_url.as_str())?;

    info!("pushing to {}", repo_url);
    let url = push_wasm_to_registry(&mut client, &auth, &reference, module, None).await?;
    info!("successful push to {}", repo_url);

    println!("successfully push to to {}", url);
    Ok(())
}

/// Pull an image
pub async fn wasm_pull(img: &str) -> Result<Vec<u8>> {
    let (mut client, auth, reference, repo_url) = parse_img_url(img)?;
    info!("pulling from {}", repo_url);
    let img_content = pull_wasm_from_registry(&mut client, &auth, &reference).await?;
    info!(
        "successful pull {} bytes from {}",
        img_content.len(),
        repo_url
    );
    // TODO check the pull data is valid wasm file
    Ok(img_content)
}