Skip to main content

asimov_cli/commands/module/
install.rs

1// This is free and unencumbered software released into the public domain.
2
3use crate::{StandardOptions, SysexitsError::*};
4use asimov_installer::InstallOptions;
5use asimov_module::{ModuleManifest, ReadVarError};
6use color_print::{ceprintln, cprintln};
7use core::error::Error;
8
9pub async fn install(
10    module_names: &Vec<String>,
11    version: &Option<String>,
12    model_size: &Option<String>,
13    flags: &StandardOptions,
14) -> Result<(), Box<dyn Error>> {
15    let registry = asimov_registry::Registry::default();
16    let installer = asimov_installer::Installer::default();
17
18    let install_options = InstallOptions::builder()
19        .maybe_version(version.clone())
20        .maybe_model_size(model_size.clone())
21        .build();
22
23    let module_names = if module_names.len() == 1 && module_names[0] == "all" {
24        fetch_all_module_names().await.map_err(|e| {
25            tracing::error!("unable to fetch list of all modules: {e}");
26            EX_UNAVAILABLE
27        })?
28    } else {
29        module_names.clone()
30    };
31
32    for module_name in module_names {
33        if !registry
34            .is_module_installed(&module_name)
35            .await
36            .unwrap_or(false)
37        {
38            let target_version = if let Some(want) = version {
39                want.clone()
40            } else {
41                installer
42                    .fetch_latest_release(&module_name)
43                    .await
44                    .map_err(|e| {
45                        tracing::error!(
46                            "unable to find latest release for module `{module_name}`: {e}"
47                        );
48                        EX_UNAVAILABLE
49                    })?
50            };
51
52            if flags.verbose > 0 {
53                cprintln!(
54                    "<s,g>โœ“</> Found version <s>{target_version}</> for module <s>{module_name}</>."
55                );
56            }
57
58            if flags.verbose > 1 {
59                cprintln!("<s,c>ยป</> Installing module <s>{module_name}</>...");
60            }
61
62            installer
63                .install_module(module_name.clone(), &install_options)
64                .await
65                .map_err(|e| {
66                    tracing::error!("failed to install module `{module_name}`: {e}");
67                    EX_UNAVAILABLE
68                })?;
69
70            if flags.verbose > 0 {
71                cprintln!("<s,g>โœ“</> Installed module <s>{module_name}</>.");
72            }
73        } else if flags.verbose > 0 {
74            cprintln!("<s,g>โœ“</> Module <s>{module_name}</> is already installed.");
75        }
76
77        if registry
78            .is_module_enabled(&module_name)
79            .await
80            .unwrap_or(false)
81        {
82            continue;
83        }
84
85        let manifest = registry.read_manifest(&module_name).await.map_err(|e| {
86            tracing::error!("failed to read module manifest for `{module_name}`: {e}");
87            EX_UNAVAILABLE
88        })?;
89
90        let variables = manifest
91            .manifest
92            .config
93            .iter()
94            .flat_map(|conf| conf.variables.iter());
95
96        let mut missing_variables = Vec::new();
97        for var in variables {
98            if var.default_value.is_some() {
99                continue;
100            }
101            match manifest.manifest.variable(&var.name, None) {
102                Ok(_) => (),
103                Err(ReadVarError::UnconfiguredVar(_)) => {
104                    missing_variables.push(var);
105                },
106                Err(e) => {
107                    tracing::error!(
108                        "failed to read configuration variable `{}` for module `{module_name}`: {e}",
109                        var.name
110                    );
111                    return Err(EX_UNAVAILABLE.into());
112                },
113            }
114        }
115
116        if missing_variables.is_empty() {
117            registry.enable_module(&module_name).await.map_err(|e| {
118                tracing::error!("failed to enable installed module `{module_name}`: {e}");
119                EX_UNAVAILABLE
120            })?;
121        } else {
122            ceprintln!(
123                "<s,y>warn:</> Module <s>{module_name}</> wasn't enabled automatically due to missing configuration."
124            );
125            ceprintln!("<s,dim>hint:</> Module <s>{module_name}</> requires configuration:");
126
127            for var in missing_variables {
128                let desc_suffix = if let Some(ref desc) = var.description {
129                    format!(" (Description: \"{desc}\")")
130                } else {
131                    String::new()
132                };
133
134                ceprintln!(
135                    "<s,dim>hint:</>   Missing variable: <s>{}</s>{}",
136                    var.name,
137                    desc_suffix
138                );
139
140                if let Some(ref env) = var.environment {
141                    ceprintln!(
142                        "<s,dim>hint:</>   Alternative: set environment variable: <s>{env}</>"
143                    );
144                }
145            }
146
147            ceprintln!("<s,dim>hint:</>   To configure: <s>asimov module config {module_name}</s>");
148            ceprintln!("<s,dim>hint:</>   To enable: <s>asimov module enable {module_name}</s>");
149        }
150    }
151
152    Ok(())
153}
154
155pub async fn fetch_all_module_names() -> Result<Vec<String>, Box<dyn core::error::Error>> {
156    let url = "https://github.com/asimov-modules/asimov-modules/raw/master/all/.asimov/module.yaml";
157
158    let client = reqwest::Client::builder()
159        .user_agent("asimov-module-cli")
160        .connect_timeout(std::time::Duration::from_secs(10))
161        .read_timeout(std::time::Duration::from_secs(30))
162        .build()
163        .expect("Failed to build HTTP client");
164
165    let response = client
166        .get(url)
167        .send()
168        .await
169        .map_err(|e| format!("request failed: {e}"))?;
170
171    if !response.status().is_success() {
172        Err(format!(
173            "HTTP status code was not successful: {0}",
174            response.status()
175        ))?;
176    }
177
178    let content = response
179        .text()
180        .await
181        .inspect_err(|err| tracing::debug!(?err))?;
182
183    let manifest: ModuleManifest = serde_yml::from_str(&content)
184        .inspect_err(|err| tracing::debug!(?err, ?content))
185        .map_err(|e| format!("unable to deserialize GitHub response: {e}"))?;
186
187    Ok(manifest.requires.modules)
188}