mod config;
mod error;
mod helper;
use std::{
env, fs, io,
path::{Path, PathBuf},
string::String,
};
use crate::registry::{CredentialProvider, Credentials};
use config::{canonical_server, DockerConfig, EnvironmentAuths};
pub use error::DockerCredentialError;
use helper::{run as run_helper, validate_name as validate_helper_name};
#[derive(Default)]
pub struct DockerCredentialProvider {
config: DockerConfig,
environment_auths: EnvironmentAuths,
resolved: Option<OwnedCredentials>,
}
impl DockerCredentialProvider {
pub fn empty() -> Self {
Self::default()
}
pub fn from_env() -> Result<Self, DockerCredentialError> {
let mut provider = match docker_config_path() {
Some(path) => Self::from_path_if_present(&path)?,
None => Self::empty(),
};
if let Some(value) = env::var_os("DOCKER_AUTH_CONFIG") {
let value = value
.into_string()
.map_err(|_| DockerCredentialError::EnvironmentNotUnicode)?;
provider.environment_auths = EnvironmentAuths::parse(value.as_bytes())?;
}
Ok(provider)
}
pub fn from_path(path: impl AsRef<Path>) -> Result<Self, DockerCredentialError> {
let path = path.as_ref();
let bytes = fs::read(path).map_err(|source| DockerCredentialError::ReadConfig {
path: path.to_path_buf(),
source,
})?;
Self::from_config_bytes(&bytes)
}
pub fn from_config_bytes(bytes: &[u8]) -> Result<Self, DockerCredentialError> {
Ok(Self {
config: DockerConfig::parse(bytes)?,
environment_auths: EnvironmentAuths::default(),
resolved: None,
})
}
pub fn credentials_for(
&mut self,
authority: &str,
) -> Result<Option<Credentials<'_>>, DockerCredentialError> {
self.resolved = self.resolve_with(authority, run_helper)?;
Ok(self.resolved.as_ref().map(OwnedCredentials::borrow))
}
fn from_path_if_present(path: &Path) -> Result<Self, DockerCredentialError> {
match fs::read(path) {
Ok(bytes) => Self::from_config_bytes(&bytes),
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(Self::empty()),
Err(source) => Err(DockerCredentialError::ReadConfig {
path: path.to_path_buf(),
source,
}),
}
}
fn resolve_with<F>(
&self,
authority: &str,
mut helper_runner: F,
) -> Result<Option<OwnedCredentials>, DockerCredentialError>
where
F: FnMut(&str, &str) -> Result<Option<OwnedCredentials>, DockerCredentialError>,
{
let server = canonical_server(authority);
if let Some(credentials) = self.environment_auths.credentials_for(&server)? {
return Ok(Some(credentials));
}
if let Some(helper) = self.config.helper_for(&server) {
validate_helper_name(helper)?;
return helper_runner(helper, &server);
}
self.config.credentials_for(&server)
}
}
impl CredentialProvider for DockerCredentialProvider {
type Error = DockerCredentialError;
fn credentials<'a>(
&'a mut self,
authority: &str,
_scope: Option<&str>,
) -> Result<Option<Credentials<'a>>, Self::Error> {
self.credentials_for(authority)
}
}
struct OwnedCredentials {
username: String,
secret: String,
}
impl OwnedCredentials {
fn borrow(&self) -> Credentials<'_> {
Credentials {
username: &self.username,
password: &self.secret,
}
}
}
fn docker_config_path() -> Option<PathBuf> {
if let Some(directory) = env::var_os("DOCKER_CONFIG").filter(|value| !value.is_empty()) {
return Some(PathBuf::from(directory).join("config.json"));
}
home_directory().map(|home| home.join(".docker").join("config.json"))
}
fn home_directory() -> Option<PathBuf> {
env::var_os("HOME")
.or_else(|| env::var_os("USERPROFILE"))
.filter(|value| !value.is_empty())
.map(PathBuf::from)
}
#[cfg(test)]
mod tests;