Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 65 additions & 23 deletions bin/core/src/api/execute/stack.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::{collections::HashSet, str::FromStr};
use std::{
collections::{HashMap, HashSet},
str::FromStr,
};

use anyhow::{Context, anyhow};
use database::mungos::mongodb::bson::{
Expand Down Expand Up @@ -85,6 +88,60 @@ impl Resolve<ExecuteArgs> for BatchDeployStack {
}
}

/// Extract all configured stack registry credentials and resolve any tokens
/// available in core. Periphery can still fall back to its local config.
async fn registry_tokens(
stack: &Stack,
) -> anyhow::Result<Vec<(String, String, String)>> {
let mut res = HashMap::with_capacity(
stack.config.image_registry.len().saturating_add(1),
);

for (domain, account) in stack
.config
.image_registry
.iter()
.map(|registry| {
(registry.domain.as_str(), registry.account.as_str())
})
.chain(
[(
stack.config.registry_provider.as_str(),
stack.config.registry_account.as_str(),
)]
.into_iter(),
)
.filter(|(domain, account)| {
!domain.is_empty() && !account.is_empty()
})
.collect::<HashSet<_>>()
{
let Some(registry_token) =
crate::helpers::registry_token(domain, account)
.await
.with_context(|| {
format!(
"Failed to get registry token in call to db. Stopping run. | {domain} | {account}"
)
})?
else {
continue;
};

res.insert(
(domain.to_string(), account.to_string()),
registry_token,
);
}

Ok(
res
.into_iter()
.map(|((domain, account), token)| (domain, account, token))
.collect(),
)
}

impl Resolve<ExecuteArgs> for DeployStack {
#[instrument(
"DeployStack",
Expand Down Expand Up @@ -151,12 +208,7 @@ impl Resolve<ExecuteArgs> for DeployStack {
let git_token =
stack_git_token(&mut stack, repo.as_mut()).await?;

let registry_token = crate::helpers::registry_token(
&stack.config.registry_provider,
&stack.config.registry_account,
).await.with_context(
|| format!("Failed to get registry token in call to db. Stopping run. | {} | {}", stack.config.registry_provider, stack.config.registry_account),
)?;
let registry_tokens = registry_tokens(&stack).await?;

// interpolate variables / secrets, returning the sanitizing replacers to send to
// periphery so it may sanitize the final command for safe logging (avoids exposing secret values)
Expand Down Expand Up @@ -199,7 +251,7 @@ impl Resolve<ExecuteArgs> for DeployStack {
stack: stack.clone(),
repo,
git_token,
registry_token,
registry_tokens,
replacers: secret_replacers.into_iter().collect(),
},
)
Expand All @@ -213,7 +265,7 @@ impl Resolve<ExecuteArgs> for DeployStack {
services: self.services,
repo,
git_token,
registry_token,
registry_tokens,
replacers: secret_replacers.into_iter().collect(),
})
.await?
Expand Down Expand Up @@ -844,12 +896,7 @@ pub async fn pull_stack_inner(

let git_token = stack_git_token(&mut stack, repo.as_mut()).await?;

let registry_token = crate::helpers::registry_token(
&stack.config.registry_provider,
&stack.config.registry_account,
).await.with_context(
|| format!("Failed to get registry token in call to db. Stopping run. | {} | {}", stack.config.registry_provider, stack.config.registry_account),
)?;
let registry_tokens = registry_tokens(&stack).await?;

// interpolate variables / secrets
let secret_replacers = if !stack.config.skip_secret_interp {
Expand Down Expand Up @@ -880,7 +927,7 @@ pub async fn pull_stack_inner(
services,
repo,
git_token,
registry_token,
registry_tokens,
replacers: secret_replacers.into_iter().collect(),
})
.await?;
Expand Down Expand Up @@ -1323,12 +1370,7 @@ impl Resolve<ExecuteArgs> for RunStackService {
let git_token =
stack_git_token(&mut stack, repo.as_mut()).await?;

let registry_token = crate::helpers::registry_token(
&stack.config.registry_provider,
&stack.config.registry_account,
).await.with_context(
|| format!("Failed to get registry token in call to db. Stopping run. | {} | {}", stack.config.registry_provider, stack.config.registry_account),
)?;
let registry_tokens = registry_tokens(&stack).await?;

let secret_replacers = if !stack.config.skip_secret_interp {
let VariablesAndSecrets { variables, secrets } =
Expand Down Expand Up @@ -1356,7 +1398,7 @@ impl Resolve<ExecuteArgs> for RunStackService {
stack,
repo,
git_token,
registry_token,
registry_tokens,
replacers: secret_replacers.into_iter().collect(),
service: self.service,
command: self.command,
Expand Down
14 changes: 8 additions & 6 deletions bin/periphery/src/api/compose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ impl Resolve<crate::api::Args> for ComposePull {
repo,
services,
git_token,
registry_token,
registry_tokens,
mut replacers,
} = self;

Expand Down Expand Up @@ -330,7 +330,8 @@ impl Resolve<crate::api::Args> for ComposePull {
}
}

maybe_login_registry(&stack, registry_token, &mut res.logs).await;
maybe_login_registry(&stack, &registry_tokens, &mut res.logs)
.await;
if !all_logs_success(&res.logs) {
return Ok(res);
}
Expand Down Expand Up @@ -432,7 +433,7 @@ impl Resolve<crate::api::Args> for ComposeUp {
repo,
services,
git_token,
registry_token,
registry_tokens,
mut replacers,
} = self;

Expand Down Expand Up @@ -476,7 +477,8 @@ impl Resolve<crate::api::Args> for ComposeUp {
return Ok(res);
}

maybe_login_registry(&stack, registry_token, &mut res.logs).await;
maybe_login_registry(&stack, &registry_tokens, &mut res.logs)
.await;
if !all_logs_success(&res.logs) {
return Ok(res);
}
Expand Down Expand Up @@ -825,7 +827,7 @@ impl Resolve<crate::api::Args> for ComposeRun {
mut stack,
repo,
git_token,
registry_token,
registry_tokens,
mut replacers,
service,
command,
Expand Down Expand Up @@ -871,7 +873,7 @@ impl Resolve<crate::api::Args> for ComposeRun {
format!("Failed to validate run directory on host after stack write (canonicalize error), path={}", run_directory.to_string_lossy())
)?;

maybe_login_registry(&stack, registry_token, &mut Vec::new())
maybe_login_registry(&stack, &registry_tokens, &mut Vec::new())
.await;

let docker_compose = docker_compose();
Expand Down
4 changes: 2 additions & 2 deletions bin/periphery/src/api/swarm/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ impl Resolve<crate::api::Args> for DeploySwarmStack {
mut stack,
repo,
git_token,
registry_token,
registry_tokens,
mut replacers,
} = self;

Expand Down Expand Up @@ -172,7 +172,7 @@ impl Resolve<crate::api::Args> for DeploySwarmStack {
}

let use_with_registry_auth =
maybe_login_registry(&stack, registry_token, &mut res.logs)
maybe_login_registry(&stack, &registry_tokens, &mut res.logs)
.await;
if !all_logs_success(&res.logs) {
return Ok(res);
Expand Down
61 changes: 40 additions & 21 deletions bin/periphery/src/stack/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Module to handle common parts of deploying Compose and Swarm Stacks.

use std::path::{Path, PathBuf};
use std::{
collections::HashSet,
path::{Path, PathBuf},
};

use anyhow::{Context as _, anyhow};
use formatting::format_serror;
Expand Down Expand Up @@ -29,35 +32,51 @@ pub mod write;
)]
pub async fn maybe_login_registry(
stack: &Stack,
registry_token: Option<String>,
registry_tokens: &[(String, String, String)],
logs: &mut Vec<Log>,
) -> bool {
if !stack.config.registry_provider.is_empty()
&& !stack.config.registry_account.is_empty()
{
if let Err(e) = docker_login(
&stack.config.registry_provider,
&stack.config.registry_account,
registry_token.as_deref(),
let registries = stack
.config
.image_registry
.iter()
.map(|r| (r.domain.as_str(), r.account.as_str()))
.chain(
[(
stack.config.registry_provider.as_str(),
stack.config.registry_account.as_str(),
)]
.into_iter(),
)
.await
.with_context(|| {
format!(
"Domain: '{}' | Account: '{}'",
stack.config.registry_provider, stack.config.registry_account
)
.filter(|(domain, account)| {
!domain.is_empty() && !account.is_empty()
})
.context("Failed to login to image registry")
.collect::<HashSet<_>>();

let mut logged_in = false;
for (domain, account) in registries {
let registry_token = registry_tokens
.iter()
.find(|(token_domain, token_account, _)| {
token_domain == domain && token_account == account
})
.map(|(_, _, token)| token.as_str());

match docker_login(domain, account, registry_token)
.await
.with_context(|| {
format!("Domain: '{domain}' | Account: '{account}'")
})
.context("Failed to login to image registry")
{
logs.push(Log::error(
Ok(did_login) => logged_in = logged_in || did_login,
Err(e) => logs.push(Log::error(
"Login to Registry",
format_serror(&e.into()),
));
)),
}
true
} else {
false
}

logged_in
}

/// Only for git repo based Stacks.
Expand Down
16 changes: 14 additions & 2 deletions client/core/rs/src/entities/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ use typeshare::typeshare;
use crate::{
deserializers::{
env_vars_deserializer, file_contents_deserializer,
option_env_vars_deserializer, option_file_contents_deserializer,
item_or_vec_deserializer, option_env_vars_deserializer,
option_file_contents_deserializer,
option_item_or_vec_deserializer,
option_maybe_string_i64_deserializer,
option_string_list_deserializer, string_list_deserializer,
},
entities::{
EnvironmentVar, ImageDigest,
EnvironmentVar, ImageDigest, build::ImageRegistryConfig,
docker::service::SwarmServiceListItem, environment_vars_from_str,
},
};
Expand Down Expand Up @@ -526,6 +528,15 @@ pub struct StackConfig {
#[builder(default)]
pub registry_account: String,

/// Configuration for the registry/s to login to before docker compose up.
#[serde(default, deserialize_with = "item_or_vec_deserializer")]
#[partial_attr(serde(
default,
deserialize_with = "option_item_or_vec_deserializer"
))]
#[builder(default)]
pub image_registry: Vec<ImageRegistryConfig>,

/// The optional command to run before the Stack is deployed.
#[serde(default)]
#[builder(default)]
Expand Down Expand Up @@ -683,6 +694,7 @@ impl Default for StackConfig {
files_on_host: Default::default(),
registry_provider: Default::default(),
registry_account: Default::default(),
image_registry: Default::default(),
file_contents: Default::default(),
auto_pull: default_auto_pull(),
poll_for_updates: Default::default(),
Expand Down
3 changes: 2 additions & 1 deletion client/core/ts/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2566,6 +2566,8 @@ export interface StackConfig {
registry_provider?: string;
/** Used with `registry_provider` to login to a registry before docker compose up. */
registry_account?: string;
/** Configuration for the registry/s to login to before docker compose up. */
image_registry?: ImageRegistryConfig[];
/** The optional command to run before the Stack is deployed. */
pre_deploy?: SystemCommand;
/** The optional command to run after the Stack is deployed. */
Expand Down Expand Up @@ -11117,4 +11119,3 @@ export type WsLoginMessage =
key: string;
secret: string;
}};

Loading