use std::net::{IpAddr, Ipv4Addr};
use std::path::PathBuf;
use std::process::Stdio;
use std::str::FromStr;
use axum::http::Uri;
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use serde_derive::{Deserialize, Serialize};
use tokio::fs::File;
use tokio::io::{AsyncWriteExt, BufWriter};
use tokio::process::Command;
use super::{CHANNEL_NIGHTLY, CHANNEL_STABLE};
use crate::model::errors::MissingEnvVar;
use crate::utils::apierror::{error_backend_failure, specialize, ApiError};
use crate::utils::comma_sep_to_vec;
use crate::utils::token::generate_token;
pub fn get_var<T: AsRef<str>>(name: T) -> Result<String, MissingEnvVar> {
let key = name.as_ref();
std::env::var(key).map_err(|original| MissingEnvVar {
original,
var_name: key.to_string(),
})
}
#[derive(Debug, Serialize, Deserialize, Copy, Clone, PartialEq, Eq)]
pub enum ExternalRegistryProtocol {
Git,
Sparse,
}
impl ExternalRegistryProtocol {
#[must_use]
pub fn new(sparse: bool) -> Self {
if sparse {
Self::Sparse
} else {
Self::Git
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ExternalRegistry {
pub name: String,
pub index: String,
pub protocol: ExternalRegistryProtocol,
#[serde(rename = "docsRoot")]
pub docs_root: String,
pub login: String,
pub token: String,
}
impl ExternalRegistry {
fn from_env(reg_index: usize) -> Result<Option<ExternalRegistry>, MissingEnvVar> {
if let Ok(name) = get_var(format!("REGISTRY_EXTERNAL_{reg_index}_NAME")) {
let mut index = get_var(format!("REGISTRY_EXTERNAL_{reg_index}_INDEX"))?;
let protocol = if let Some(rest) = index.strip_prefix("sparse+") {
index = rest.to_string();
ExternalRegistryProtocol::Sparse
} else {
ExternalRegistryProtocol::Git
};
let docs_root = get_var(format!("REGISTRY_EXTERNAL_{reg_index}_DOCS"))?;
let login = get_var(format!("REGISTRY_EXTERNAL_{reg_index}_LOGIN"))?;
let token = get_var(format!("REGISTRY_EXTERNAL_{reg_index}_TOKEN"))?;
Ok(Some(ExternalRegistry {
name,
index,
protocol,
docs_root,
login,
token,
}))
} else {
Ok(None)
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum StorageConfig {
FileSystem,
S3 {
params: S3Params,
bucket: String,
},
}
impl StorageConfig {
fn from_env() -> Result<StorageConfig, MissingEnvVar> {
let storage_kind = get_var("REGISTRY_STORAGE")?;
Ok(match storage_kind.as_str() {
"s3" | "S3" => StorageConfig::S3 {
params: S3Params {
endpoint: get_var("REGISTRY_S3_URI")?,
region: get_var("REGISTRY_S3_REGION")?,
access_key: get_var("REGISTRY_S3_ACCESS_KEY")?,
secret_key: get_var("REGISTRY_S3_SECRET_KEY")?,
root: get_var("REGISTRY_S3_ROOT").unwrap_or_default(),
},
bucket: get_var("REGISTRY_S3_BUCKET")?,
},
"" | "fs" | "FS" | "filesystem" | "FileSystem" => StorageConfig::FileSystem,
_ => panic!("invalid REGISTRY_STORAGE"),
})
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct S3Params {
pub endpoint: String,
pub region: String,
#[serde(rename = "accessKey")]
pub access_key: String,
#[serde(rename = "secretKey")]
pub secret_key: String,
pub root: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct IndexConfig {
#[serde(rename = "homeDir")]
pub home_dir: String,
pub location: String,
#[serde(rename = "allowProtocolGit")]
pub allow_protocol_git: bool,
#[serde(rename = "allowProtocolSparse")]
pub allow_protocol_sparse: bool,
#[serde(rename = "remoteOrigin")]
pub remote_origin: Option<String>,
#[serde(rename = "remoteSshKeyFileName")]
pub remote_ssh_key_file_name: Option<String>,
#[serde(rename = "remotePushChanges")]
pub remote_push_changes: bool,
#[serde(rename = "userName")]
pub user_name: String,
#[serde(rename = "userEmail")]
pub user_email: String,
pub public: IndexPublicConfig,
}
impl IndexConfig {
fn from_env(home_dir: &str, data_dir: &str, web_public_uri: &str) -> Result<IndexConfig, MissingEnvVar> {
Ok(IndexConfig {
home_dir: home_dir.to_string(),
location: format!("{data_dir}/index"),
allow_protocol_git: get_var("REGISTRY_INDEX_PROTOCOL_GIT").map(|v| v == "true").unwrap_or(false),
allow_protocol_sparse: get_var("REGISTRY_INDEX_PROTOCOL_SPARSE").map(|v| v == "true").unwrap_or(true),
remote_origin: get_var("REGISTRY_GIT_REMOTE").ok(),
remote_ssh_key_file_name: get_var("REGISTRY_GIT_REMOTE_SSH_KEY_FILENAME").ok(),
remote_push_changes: get_var("REGISTRY_GIT_REMOTE_PUSH_CHANGES")
.is_ok_and(|value| value == "1" || value.eq_ignore_ascii_case("true")),
user_name: get_var("REGISTRY_GIT_USER_NAME")?,
user_email: get_var("REGISTRY_GIT_USER_EMAIL")?,
public: IndexPublicConfig {
dl: format!("{web_public_uri}/api/v1/crates"),
api: web_public_uri.to_string(),
auth_required: true,
},
})
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct IndexPublicConfig {
pub dl: String,
pub api: String,
#[serde(rename = "auth-required")]
pub auth_required: bool,
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct SmtpConfig {
pub host: String,
pub port: u16,
pub login: String,
pub password: String,
}
impl SmtpConfig {
fn from_env() -> Result<Self, MissingEnvVar> {
Ok(Self {
host: get_var("REGISTRY_EMAIL_SMTP_HOST")?,
port: get_var("REGISTRY_EMAIL_SMTP_PORT")
.map(|s| s.parse().expect("invalid REGISTRY_EMAIL_SMTP_PORT"))
.unwrap_or(465),
login: get_var("REGISTRY_EMAIL_SMTP_LOGIN")?,
password: get_var("REGISTRY_EMAIL_SMTP_PASSWORD")?,
})
}
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct EmailConfig {
pub smtp: SmtpConfig,
pub sender: String,
pub cc: String,
}
impl EmailConfig {
fn from_env() -> Result<Self, MissingEnvVar> {
Ok(Self {
smtp: SmtpConfig::from_env()?,
sender: get_var("REGISTRY_EMAIL_SENDER")?,
cc: get_var("REGISTRY_EMAIL_CC").unwrap_or_default(),
})
}
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct NodeRoleMaster {
#[serde(rename = "workerToken")]
pub worker_token: Option<String>,
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct NodeRoleWorker {
pub name: String,
#[serde(rename = "workerToken")]
pub worker_token: String,
#[serde(rename = "masterUri")]
pub master_uri: String,
pub capabilities: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum NodeRole {
Standalone,
Master(NodeRoleMaster),
Worker(NodeRoleWorker),
}
impl NodeRole {
fn from_env() -> Result<Self, MissingEnvVar> {
let role_name = get_var("REGISTRY_NODE_ROLE").ok();
match role_name.as_deref() {
Some("master") => Ok(Self::Master(NodeRoleMaster {
worker_token: get_var("REGISTRY_NODE_WORKER_TOKEN").ok(),
})),
Some("worker") => Ok(Self::Worker(NodeRoleWorker {
name: get_var("REGISTRY_NODE_WORKER_NAME")?,
worker_token: get_var("REGISTRY_NODE_WORKER_TOKEN")?,
master_uri: get_var("REGISTRY_NODE_MASTER_URI")?,
capabilities: get_var("REGISTRY_NODE_WORKER_CAPABILITIES")
.ok()
.as_deref()
.map(comma_sep_to_vec)
.unwrap_or_default(),
})),
_ => Ok(Self::Standalone),
}
}
#[must_use]
pub fn get_worker_token(&self) -> Option<&str> {
match self {
Self::Standalone => None,
Self::Master(master_config) => master_config.worker_token.as_deref(),
Self::Worker(worker_config) => Some(&worker_config.worker_token),
}
}
#[must_use]
pub fn is_worker(&self) -> bool {
matches!(self, Self::Worker(_))
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[allow(clippy::struct_excessive_bools)]
pub struct Configuration {
#[serde(rename = "logLevel")]
pub log_level: String,
#[serde(rename = "logDatetimeFormat")]
pub log_datetime_format: String,
#[serde(rename = "webListenOnIp")]
pub web_listenon_ip: IpAddr,
#[serde(rename = "webListenOnPort")]
pub web_listenon_port: u16,
#[serde(rename = "webPublicUri")]
pub web_public_uri: String,
#[serde(rename = "webDomain")]
pub web_domain: String,
#[serde(rename = "webBodyLimit")]
pub web_body_limit: usize,
#[serde(rename = "webHotReloadPath")]
pub web_hot_reload_path: Option<String>,
#[serde(rename = "homeDir")]
pub home_dir: String,
#[serde(rename = "dataDir")]
pub data_dir: String,
#[serde(rename = "indexConfig")]
pub index: IndexConfig,
pub storage: StorageConfig,
#[serde(rename = "storageTimeout")]
pub storage_timeout: u64,
#[serde(rename = "oauthLoginUri")]
pub oauth_login_uri: String,
#[serde(rename = "oauthTokenUri")]
pub oauth_token_uri: String,
#[serde(rename = "oauthCallbackUri")]
pub oauth_callback_uri: String,
#[serde(rename = "oauthUserInfoUri")]
pub oauth_userinfo_uri: String,
#[serde(rename = "oauthUserInfoPathEmail")]
pub oauth_userinfo_path_email: String,
#[serde(rename = "oauthUserInfoPathFullName")]
pub oauth_userinfo_path_fullname: String,
#[serde(rename = "oauthClientId")]
pub oauth_client_id: String,
#[serde(rename = "oauthClientSecret")]
pub oauth_client_secret: String,
#[serde(rename = "oauthClientScope")]
pub oauth_client_scope: String,
#[serde(rename = "externalRegistries")]
pub external_registries: Vec<ExternalRegistry>,
#[serde(rename = "docsGenMock")]
pub docs_gen_mock: bool,
#[serde(rename = "docsAutoinstallTargets")]
pub docs_autoinstall_targets: bool,
#[serde(rename = "depsCheckPeriod")]
pub deps_check_period: u64,
#[serde(rename = "depsStaleRegistry")]
pub deps_stale_registry: u64,
#[serde(rename = "depsStaleAnalysis")]
pub deps_stale_analysis: i64,
#[serde(rename = "depsNotifyOutdated")]
pub deps_notify_outdated: bool,
#[serde(rename = "depsNotifyCVEs")]
pub deps_notify_cves: bool,
pub email: EmailConfig,
#[serde(rename = "selfLocalName")]
pub self_local_name: String,
#[serde(rename = "selfServiceLogin")]
pub self_service_login: String,
#[serde(rename = "selfServiceToken")]
pub self_service_token: String,
#[serde(rename = "selfToolchainVersionStable")]
pub self_toolchain_version_stable: semver::Version,
#[serde(rename = "selfToolchainVersionNightly")]
pub self_toolchain_version_nightly: semver::Version,
#[serde(rename = "selfToolchainHost")]
pub self_toolchain_host: String,
#[serde(rename = "selfKnownTargets")]
pub self_known_targets: Vec<String>,
#[serde(rename = "selfInstalledTargets")]
pub self_installed_targets: Vec<String>,
#[serde(rename = "selfInstallableTargets")]
pub self_installable_targets: Vec<String>,
#[serde(rename = "selfRole")]
pub self_role: NodeRole,
}
impl Default for Configuration {
fn default() -> Self {
Self {
log_level: String::from("INFO"),
log_datetime_format: String::from("[%Y-%m-%d %H:%M:%S]"),
web_listenon_ip: IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
web_listenon_port: 80,
web_public_uri: String::from("http://localhost"),
web_domain: String::from("localhost"),
web_body_limit: 10 * 1024 * 1024,
web_hot_reload_path: None,
home_dir: String::from("/home/cratery"),
data_dir: String::from("/data"),
index: IndexConfig {
home_dir: String::from("/home/cratery"),
location: String::from("/data/index"),
allow_protocol_git: true,
allow_protocol_sparse: true,
remote_origin: None,
remote_ssh_key_file_name: None,
remote_push_changes: false,
user_name: String::from("Cratery"),
user_email: String::from("cratery@localhost"),
public: IndexPublicConfig {
dl: String::from("http://localhost/api/v1/crates"),
api: String::from("http://localhost"),
auth_required: true,
},
},
storage: StorageConfig::FileSystem,
storage_timeout: 3000,
oauth_login_uri: String::new(),
oauth_token_uri: String::new(),
oauth_callback_uri: String::new(),
oauth_userinfo_uri: String::new(),
oauth_userinfo_path_email: String::from("email"),
oauth_userinfo_path_fullname: String::from("fullName"),
oauth_client_id: String::new(),
oauth_client_secret: String::new(),
oauth_client_scope: String::new(),
external_registries: Vec::new(),
docs_gen_mock: true,
docs_autoinstall_targets: false,
deps_check_period: 60,
deps_stale_registry: 60 * 1000,
deps_stale_analysis: 24 * 60,
deps_notify_outdated: false,
deps_notify_cves: false,
email: EmailConfig::default(),
self_local_name: String::from("localhost"),
self_service_login: String::new(),
self_service_token: String::new(),
self_toolchain_version_stable: semver::Version::new(0, 0, 0),
self_toolchain_version_nightly: semver::Version::new(0, 0, 0),
self_toolchain_host: String::new(),
self_known_targets: Vec::new(),
self_installed_targets: Vec::new(),
self_installable_targets: Vec::new(),
self_role: NodeRole::Master(NodeRoleMaster::default()),
}
}
}
impl Configuration {
pub async fn from_env() -> Result<Self, MissingEnvVar> {
let home_dir = get_var("REGISTRY_HOME_DIR")
.or(get_var("HOME"))
.unwrap_or_else(|_| String::from("/home/cratery"));
let data_dir = get_var("REGISTRY_DATA_DIR")?;
let web_public_uri = get_var("REGISTRY_WEB_PUBLIC_URI")?;
let web_domain = Uri::from_str(&web_public_uri)
.expect("invalid REGISTRY_WEB_PUBLIC_URI")
.host()
.unwrap_or_default()
.to_string();
let self_local_name = match get_var("REGISTRY_SELF_LOCAL_NAME") {
Ok(value) => value,
Err(_) => match web_domain.rfind('.') {
Some(index) => web_domain[index..].to_string(),
None => web_domain.clone(),
},
};
let index = IndexConfig::from_env(&home_dir, &data_dir, &web_public_uri)?;
let storage = StorageConfig::from_env()?;
let deps_notify_outdated = get_var("REGISTRY_DEPS_NOTIFY_OUTDATED").map(|v| v == "true").unwrap_or(false);
let deps_notify_cves = get_var("REGISTRY_DEPS_NOTIFY_CVES").map(|v| v == "true").unwrap_or(false);
let email = if deps_notify_outdated || deps_notify_cves {
EmailConfig::from_env()?
} else {
EmailConfig::default()
};
let mut external_registries = Vec::new();
let mut external_registry_index = 1;
while let Some(registry) = ExternalRegistry::from_env(external_registry_index)? {
external_registries.push(registry);
external_registry_index += 1;
}
let self_role = NodeRole::from_env()?;
Ok(Self {
log_level: get_var("REGISTRY_LOG_LEVEL").unwrap_or_else(|_| String::from("INFO")),
log_datetime_format: get_var("REGISTRY_LOG_DATE_TIME_FORMAT")
.unwrap_or_else(|_| String::from("[%Y-%m-%d %H:%M:%S]")),
web_listenon_ip: get_var("REGISTRY_WEB_LISTENON_IP").map_or_else(
|_| IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)),
|s| IpAddr::from_str(&s).expect("invalid REGISTRY_WEB_LISTENON_IP"),
),
web_listenon_port: get_var("REGISTRY_WEB_LISTENON_PORT")
.map(|s| s.parse().expect("invalid REGISTRY_WEB_LISTENON_PORT"))
.unwrap_or(80),
web_domain,
web_public_uri,
web_body_limit: get_var("REGISTRY_WEB_BODY_LIMIT")
.map(|s| s.parse().expect("invalid REGISTRY_WEB_BODY_LIMIT"))
.unwrap_or(10 * 1024 * 1024),
web_hot_reload_path: get_var("REGISTRY_WEB_HOT_RELOAD_PATH").ok(),
home_dir,
data_dir,
index,
storage,
storage_timeout: get_var("REGISTRY_STORAGE_TIMEOUT")
.map(|s| s.parse().expect("invalid REGISTRY_STORAGE_TIMEOUT"))
.unwrap_or(3000),
oauth_login_uri: get_var("REGISTRY_OAUTH_LOGIN_URI")?,
oauth_token_uri: get_var("REGISTRY_OAUTH_TOKEN_URI")?,
oauth_callback_uri: get_var("REGISTRY_OAUTH_CALLBACK_URI")?,
oauth_userinfo_uri: get_var("REGISTRY_OAUTH_USERINFO_URI")?,
oauth_userinfo_path_email: get_var("REGISTRY_OAUTH_USERINFO_PATH_EMAIL").unwrap_or_else(|_| String::from("email")),
oauth_userinfo_path_fullname: get_var("REGISTRY_OAUTH_USERINFO_PATH_FULLNAME")
.unwrap_or_else(|_| String::from("name")),
oauth_client_id: get_var("REGISTRY_OAUTH_CLIENT_ID")?,
oauth_client_secret: get_var("REGISTRY_OAUTH_CLIENT_SECRET")?,
oauth_client_scope: get_var("REGISTRY_OAUTH_CLIENT_SCOPE")?,
docs_gen_mock: get_var("REGISTRY_DOCS_GEN_MOCK").map(|v| v == "true").unwrap_or(false),
docs_autoinstall_targets: get_var("REGISTRY_DOCS_AUTOINSTALL_TARGETS")
.map(|v| v == "true")
.unwrap_or(false),
deps_check_period: get_var("REGISTRY_DEPS_CHECK_PERIOD")
.map(|s| s.parse().expect("invalid REGISTRY_DEPS_CHECK_PERIOD"))
.unwrap_or(60), deps_stale_registry: get_var("REGISTRY_DEPS_STALE_REGISTRY")
.map(|s| s.parse().expect("invalid REGISTRY_DEPS_STALE_REGISTRY"))
.unwrap_or(60 * 1000), deps_stale_analysis: get_var("REGISTRY_DEPS_STALE_ANALYSIS")
.map(|s| s.parse().expect("invalid REGISTRY_DEPS_STALE_ANALYSIS"))
.unwrap_or(24 * 60), deps_notify_outdated,
deps_notify_cves,
email,
self_local_name,
self_service_login: generate_token(16),
self_service_token: generate_token(64),
self_toolchain_version_stable: get_rustc_version(CHANNEL_STABLE).await,
self_toolchain_version_nightly: get_rustc_version(CHANNEL_NIGHTLY).await,
self_toolchain_host: get_rustc_host().await,
self_known_targets: get_known_targets().await,
self_installed_targets: get_installed_targets(CHANNEL_NIGHTLY).await,
self_installable_targets: get_installable_targets(CHANNEL_NIGHTLY).await,
self_role,
external_registries,
})
}
#[must_use]
pub fn get_home_path_for(&self, path: &[&str]) -> PathBuf {
let mut result = PathBuf::from(&self.home_dir);
for e in path {
result.push(e);
}
result
}
#[must_use]
pub fn get_database_filename(&self) -> String {
format!("{}/registry.db", self.data_dir)
}
#[must_use]
pub fn get_database_url(&self) -> String {
format!("sqlite://{}/registry.db", self.data_dir)
}
#[must_use]
pub fn get_index_git_config(&self) -> IndexConfig {
self.index.clone()
}
pub async fn write_auth_config(&self) -> Result<(), ApiError> {
if self.index.allow_protocol_git {
self.write_auth_config_git_config().await?;
self.write_auth_config_git_credentials().await?;
}
self.write_auth_config_cargo_config().await?;
self.write_auth_config_cargo_credentials().await?;
Ok(())
}
async fn write_auth_config_git_config(&self) -> Result<(), ApiError> {
let file = File::create(self.get_home_path_for(&[".gitconfig"])).await?;
let mut writer = BufWriter::new(file);
writer.write_all("[credential]\n helper = store\n".as_bytes()).await?;
writer.flush().await?;
Ok(())
}
async fn write_auth_config_git_credentials(&self) -> Result<(), ApiError> {
let file = File::create(self.get_home_path_for(&[".git-credentials"])).await?;
let mut writer = BufWriter::new(file);
let index = self.web_public_uri.find('/').unwrap() + 2;
writer
.write_all(
format!(
"{}{}:{}@{}\n",
&self.web_public_uri[..index],
self.self_service_login,
self.self_service_token,
&self.web_public_uri[index..]
)
.as_bytes(),
)
.await?;
for registry in &self.external_registries {
let index = registry.index.find('/').unwrap() + 2;
writer
.write_all(
format!(
"{}{}:{}@{}",
®istry.index[..index],
registry.login,
registry.token,
®istry.index[index..]
)
.as_bytes(),
)
.await?;
}
writer.flush().await?;
Ok(())
}
async fn write_auth_config_cargo_config(&self) -> Result<(), ApiError> {
let file = File::create(self.get_home_path_for(&[".cargo", "config.toml"])).await?;
let mut writer = BufWriter::new(file);
writer.write_all("[registry]\n".as_bytes()).await?;
writer
.write_all("global-credential-providers = [\"cargo:token\"]\n".as_bytes())
.await?;
writer.write_all("\n".as_bytes()).await?;
writer.write_all("[registries]\n".as_bytes()).await?;
if self.index.allow_protocol_git {
writer
.write_all(format!("{} = {{ index = \"{}\" }}\n", self.self_local_name, self.web_public_uri).as_bytes())
.await?;
if self.index.allow_protocol_sparse {
writer
.write_all(
format!(
"{}sparse = {{ index = \"sparse+{}/\" }}\n",
self.self_local_name, self.web_public_uri
)
.as_bytes(),
)
.await?;
}
} else if self.index.allow_protocol_sparse {
writer
.write_all(
format!(
"{} = {{ index = \"sparse+{}/\" }}\n",
self.self_local_name, self.web_public_uri
)
.as_bytes(),
)
.await?;
}
for registry in &self.external_registries {
writer
.write_all(format!("{} = {{ index = \"{}\" }}\n", registry.name, registry.index).as_bytes())
.await?;
}
writer.flush().await?;
Ok(())
}
async fn write_auth_config_cargo_credentials(&self) -> Result<(), ApiError> {
let file = File::create(self.get_home_path_for(&[".cargo", "credentials.toml"])).await?;
let mut writer = BufWriter::new(file);
writer
.write_all(format!("[registries.{}]\n", self.self_local_name).as_bytes())
.await?;
writer
.write_all(
format!(
"token = \"Basic {}\"\n",
STANDARD.encode(format!("{}:{}", self.self_service_login, self.self_service_token))
)
.as_bytes(),
)
.await?;
if self.index.allow_protocol_git && self.index.allow_protocol_sparse {
writer
.write_all(format!("[registries.{}sparse]\n", self.self_local_name).as_bytes())
.await?;
writer
.write_all(
format!(
"token = \"Basic {}\"\n",
STANDARD.encode(format!("{}:{}", self.self_service_login, self.self_service_token))
)
.as_bytes(),
)
.await?;
}
for registry in &self.external_registries {
writer
.write_all(format!("[registries.{}]\n", registry.name).as_bytes())
.await?;
writer
.write_all(
format!(
"token = \"Basic {}\"\n",
STANDARD.encode(format!("{}:{}", registry.login, registry.token))
)
.as_bytes(),
)
.await?;
}
writer.flush().await?;
Ok(())
}
#[must_use]
pub fn get_self_as_external(&self) -> ExternalRegistry {
ExternalRegistry {
name: self.self_local_name.clone(),
index: if self.index.allow_protocol_sparse {
format!("{}/", self.web_public_uri)
} else {
self.web_public_uri.clone()
},
protocol: ExternalRegistryProtocol::new(self.index.allow_protocol_sparse),
docs_root: format!("{}/docs", self.web_public_uri),
login: self.self_service_login.clone(),
token: self.self_service_token.clone(),
}
}
pub fn set_self_from_external(&mut self, external_config: ExternalRegistry) {
self.self_local_name = external_config.name;
self.web_public_uri = if external_config.protocol == ExternalRegistryProtocol::Sparse {
external_config.index[..(external_config.index.len() - 1)].to_string()
} else {
external_config.index
};
self.index.allow_protocol_sparse = external_config.protocol == ExternalRegistryProtocol::Sparse;
self.index.allow_protocol_git = external_config.protocol == ExternalRegistryProtocol::Git;
self.self_service_login = external_config.login;
self.self_service_token = external_config.token;
}
}
async fn get_rustc_version(channel: &'static str) -> semver::Version {
let child = Command::new("rustc")
.args([channel, "--version"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
let output = child.wait_with_output().await.unwrap();
let output = String::from_utf8(output.stdout).unwrap();
output.split_ascii_whitespace().nth(1).unwrap().parse().unwrap()
}
async fn get_rustc_host() -> String {
let child = Command::new("rustc")
.args([CHANNEL_STABLE, "-vV"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
let output = child.wait_with_output().await.unwrap();
let output = String::from_utf8(output.stdout).unwrap();
output
.lines()
.find_map(|line| line.strip_prefix("host: ").map(str::to_string))
.unwrap()
}
async fn get_known_targets() -> Vec<String> {
let child = Command::new("rustc")
.args([CHANNEL_STABLE, "--print", "target-list"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
let output = child.wait_with_output().await.unwrap();
let output = String::from_utf8(output.stdout).unwrap();
output.lines().map(str::to_string).collect()
}
pub async fn get_installed_targets(channel: &'static str) -> Vec<String> {
let child = Command::new("rustup")
.args([channel, "target", "list", "--installed"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
let output = child.wait_with_output().await.unwrap();
let output = String::from_utf8(output.stdout).unwrap();
output.lines().map(str::to_string).collect()
}
async fn get_installable_targets(channel: &'static str) -> Vec<String> {
let child = Command::new("rustup")
.args([channel, "target", "list"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
let output = child.wait_with_output().await.unwrap();
let output = String::from_utf8(output.stdout).unwrap();
output.lines().map(str::to_string).collect()
}
pub async fn install_target(channel: &'static str, target: &str) -> Result<(), ApiError> {
let child = Command::new("rustup")
.args([channel, "target", "add", target])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
let output = child.wait_with_output().await.unwrap();
if output.status.success() {
Ok(())
} else {
Err(specialize(
error_backend_failure(),
format!("Failed to install target {target} for channel {channel}"),
))
}
}