mirror of
https://github.com/moghtech/komodo.git
synced 2026-04-28 19:59:46 -05:00
core / periphery support ghcr
This commit is contained in:
@@ -8,7 +8,7 @@ use monitor_client::{
|
||||
},
|
||||
entities::{
|
||||
all_logs_success,
|
||||
build::Build,
|
||||
build::{Build, CloudRegistryConfig, ImageRegistry},
|
||||
builder::{AwsBuilderConfig, Builder, BuilderConfig},
|
||||
deployment::DeploymentState,
|
||||
monitor_timestamp,
|
||||
@@ -72,6 +72,9 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let registry_token =
|
||||
validate_account_extract_registry_token(&build)?;
|
||||
|
||||
// get the action state for the build (or insert default).
|
||||
let action_state =
|
||||
action_states().build.get_or_insert_default(&build.id).await;
|
||||
@@ -181,11 +184,6 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
if all_logs_success(&update.logs) {
|
||||
let docker_token = core_config
|
||||
.docker_accounts
|
||||
.get(&build.config.docker_account)
|
||||
.cloned();
|
||||
|
||||
// Interpolate variables / secrets into build args
|
||||
let mut global_replacers = HashSet::new();
|
||||
let mut secret_replacers = HashSet::new();
|
||||
@@ -237,7 +235,7 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
res = periphery
|
||||
.request(api::build::Build {
|
||||
build: build.clone(),
|
||||
docker_token,
|
||||
registry_token,
|
||||
replacers: secret_replacers.into_iter().collect(),
|
||||
}) => res.context("failed at call to periphery to build"),
|
||||
_ = cancel.cancelled() => {
|
||||
@@ -282,6 +280,7 @@ impl Resolve<RunBuild, (User, Update)> for State {
|
||||
.await;
|
||||
}
|
||||
|
||||
// stop the cancel listening task from going forever
|
||||
cancel.cancel();
|
||||
|
||||
cleanup_builder_instance(periphery, cleanup_data, &mut update)
|
||||
@@ -675,3 +674,35 @@ fn start_aws_builder_log(
|
||||
|
||||
format!("instance id: {instance_id}\nip: {ip}\nami id: {ami_id}\ninstance type: {instance_type}\nvolume size: {volume_gb} GB\nsubnet id: {subnet_id}\nsecurity groups: {readable_sec_group_ids}\nassign public ip: {assign_public_ip}\nuse public ip: {use_public_ip}")
|
||||
}
|
||||
|
||||
/// This will make sure that a build with non-none image registry has an account attached,
|
||||
/// and will check the core config for a token matching requirements (otherwise it is left to periphery)
|
||||
fn validate_account_extract_registry_token(
|
||||
build: &Build,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
match &build.config.image_registry {
|
||||
ImageRegistry::None(_) => Ok(None),
|
||||
ImageRegistry::DockerHub(CloudRegistryConfig {
|
||||
account, ..
|
||||
}) => {
|
||||
if account.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Must attach account to use DockerHub image registry"
|
||||
));
|
||||
}
|
||||
Ok(core_config().docker_accounts.get(account).cloned())
|
||||
}
|
||||
ImageRegistry::GithubContainerRegistry(CloudRegistryConfig {
|
||||
account,
|
||||
..
|
||||
}) => {
|
||||
if account.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Must attach account to use GithubContainerRegistry"
|
||||
));
|
||||
}
|
||||
Ok(core_config().github_accounts.get(account).cloned())
|
||||
}
|
||||
ImageRegistry::Custom(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use futures::future::join_all;
|
||||
use monitor_client::{
|
||||
api::execute::*,
|
||||
entities::{
|
||||
build::Build,
|
||||
build::{Build, ImageRegistry},
|
||||
deployment::{Deployment, DeploymentImage},
|
||||
get_image_name,
|
||||
permission::PermissionLevel,
|
||||
@@ -78,6 +78,8 @@ impl Resolve<Deploy, (User, Update)> for State {
|
||||
|
||||
let periphery = periphery_client(&server)?;
|
||||
|
||||
// This block gets the version of the image to deploy in the Build case.
|
||||
// It also gets the name of the image from the build and attaches it directly.
|
||||
let version = match deployment.config.image {
|
||||
DeploymentImage::Build { build_id, version } => {
|
||||
let build = resource::get::<Build>(&build_id).await?;
|
||||
@@ -91,10 +93,13 @@ impl Resolve<Deploy, (User, Update)> for State {
|
||||
deployment.config.image = DeploymentImage::Image {
|
||||
image: format!("{image_name}:{version}"),
|
||||
};
|
||||
// set docker account to match build docker account if it's not overridden by deployment
|
||||
if deployment.config.docker_account.is_empty() {
|
||||
deployment.config.docker_account =
|
||||
build.config.docker_account;
|
||||
// set image registry to match build docker account if it's not overridden by deployment
|
||||
if matches!(
|
||||
&deployment.config.image_registry,
|
||||
ImageRegistry::None(_)
|
||||
) {
|
||||
deployment.config.image_registry =
|
||||
build.config.image_registry;
|
||||
}
|
||||
version
|
||||
}
|
||||
@@ -156,17 +161,25 @@ impl Resolve<Deploy, (User, Update)> for State {
|
||||
update.version = version;
|
||||
update_update(update.clone()).await?;
|
||||
|
||||
let docker_token = core_config
|
||||
.docker_accounts
|
||||
.get(&deployment.config.docker_account)
|
||||
.cloned();
|
||||
let registry_token = match &deployment.config.image_registry {
|
||||
ImageRegistry::None(_) => None,
|
||||
ImageRegistry::DockerHub(params) => {
|
||||
core_config.docker_accounts.get(¶ms.account).cloned()
|
||||
}
|
||||
ImageRegistry::GithubContainerRegistry(params) => {
|
||||
core_config.github_accounts.get(¶ms.account).cloned()
|
||||
}
|
||||
ImageRegistry::Custom(_) => {
|
||||
return Err(anyhow!("Custom ImageRegistry not yet supported"))
|
||||
}
|
||||
};
|
||||
|
||||
match periphery
|
||||
.request(api::container::Deploy {
|
||||
deployment,
|
||||
stop_signal,
|
||||
stop_time,
|
||||
docker_token,
|
||||
registry_token,
|
||||
replacers: secret_replacers.into_iter().collect(),
|
||||
})
|
||||
.await
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::entities::build::{BuildConfig, BuildInfo};
|
||||
use monitor_client::entities::build::{
|
||||
BuildConfig, BuildInfo, CloudRegistryConfig, ImageRegistry,
|
||||
};
|
||||
use mungos::mongodb::bson::serde_helpers::hex_string_as_object_id;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -213,10 +215,14 @@ impl TryFrom<Build> for monitor_client::entities::build::Build {
|
||||
repo: value.repo.unwrap_or_default(),
|
||||
branch: value.branch.unwrap_or_default(),
|
||||
github_account: value.github_account.unwrap_or_default(),
|
||||
docker_account: value.docker_account.unwrap_or_default(),
|
||||
docker_organization: value
|
||||
.docker_organization
|
||||
.unwrap_or_default(),
|
||||
image_registry: ImageRegistry::DockerHub(
|
||||
CloudRegistryConfig {
|
||||
account: value.docker_account.unwrap_or_default(),
|
||||
organization: value
|
||||
.docker_organization
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
),
|
||||
pre_build: value
|
||||
.pre_build
|
||||
.map(|command| monitor_client::entities::SystemCommand {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
use monitor_client::entities::{
|
||||
build::{CloudRegistryConfig, ImageRegistry},
|
||||
NoData,
|
||||
};
|
||||
use mungos::mongodb::bson::serde_helpers::hex_string_as_object_id;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -374,10 +378,15 @@ impl TryFrom<Deployment>
|
||||
.post_image
|
||||
.unwrap_or_default(),
|
||||
extra_args: value.docker_run_args.extra_args,
|
||||
docker_account: value
|
||||
.docker_run_args
|
||||
.docker_account
|
||||
.unwrap_or_default(),
|
||||
image_registry: match value.docker_run_args.docker_account {
|
||||
Some(account) => {
|
||||
ImageRegistry::DockerHub(CloudRegistryConfig {
|
||||
account,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
None => ImageRegistry::None(NoData {}),
|
||||
},
|
||||
labels: Default::default(),
|
||||
},
|
||||
};
|
||||
|
||||
299
bin/migrator/src/legacy/v1_6/deployment.rs
Normal file
299
bin/migrator/src/legacy/v1_6/deployment.rs
Normal file
@@ -0,0 +1,299 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{resource::Resource, EnvironmentVar, Version};
|
||||
|
||||
pub type Deployment = Resource<DeploymentConfig, ()>;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct DeploymentConfig {
|
||||
/// The id of server the deployment is deployed on.
|
||||
#[serde(default, alias = "server")]
|
||||
pub server_id: String,
|
||||
|
||||
/// Whether to send ContainerStateChange alerts for this deployment.
|
||||
#[serde(default = "default_send_alerts")]
|
||||
pub send_alerts: bool,
|
||||
|
||||
/// The image which the deployment deploys.
|
||||
/// Can either be a user inputted image, or a Monitor build.
|
||||
#[serde(default)]
|
||||
pub image: DeploymentImage,
|
||||
|
||||
/// Override the account used to pull the image from the registry.
|
||||
/// If it is empty, will use the same account as the build.
|
||||
#[serde(default)]
|
||||
pub docker_account: String,
|
||||
|
||||
/// Whether to skip secret interpolation into the deployment environment variables.
|
||||
#[serde(default)]
|
||||
pub skip_secret_interp: bool,
|
||||
|
||||
/// Whether to redeploy the deployment whenever the attached build finishes.
|
||||
#[serde(default)]
|
||||
pub redeploy_on_build: bool,
|
||||
|
||||
/// Labels attached to various termination signal options.
|
||||
/// Used to specify different shutdown functionality depending on the termination signal.
|
||||
#[serde(default = "default_term_signal_labels")]
|
||||
pub term_signal_labels: Vec<TerminationSignalLabel>,
|
||||
|
||||
/// The default termination signal to use to stop the deployment. Defaults to SigTerm (default docker signal).
|
||||
#[serde(default)]
|
||||
pub termination_signal: TerminationSignal,
|
||||
|
||||
/// The termination timeout.
|
||||
#[serde(default = "default_termination_timeout")]
|
||||
pub termination_timeout: i32,
|
||||
|
||||
/// The container port mapping.
|
||||
/// Irrelevant if container network is `host`.
|
||||
/// Maps ports on host to ports on container.
|
||||
#[serde(default)]
|
||||
pub ports: Vec<Conversion>,
|
||||
|
||||
/// The container volume mapping.
|
||||
/// Maps files / folders on host to files / folders in container.
|
||||
#[serde(default)]
|
||||
pub volumes: Vec<Conversion>,
|
||||
|
||||
/// The environment variables passed to the container.
|
||||
#[serde(default)]
|
||||
pub environment: Vec<EnvironmentVar>,
|
||||
|
||||
/// The docker labels given to the container.
|
||||
#[serde(default)]
|
||||
pub labels: Vec<EnvironmentVar>,
|
||||
|
||||
/// The network attached to the container.
|
||||
/// Default is `host`.
|
||||
#[serde(default = "default_network")]
|
||||
pub network: String,
|
||||
|
||||
/// The restart mode given to the container.
|
||||
#[serde(default)]
|
||||
pub restart: RestartMode,
|
||||
|
||||
/// This is interpolated at the end of the `docker run` command,
|
||||
/// which means they are either passed to the containers inner process,
|
||||
/// or replaces the container command, depending on use of ENTRYPOINT or CMD in dockerfile.
|
||||
/// Empty is no command.
|
||||
#[serde(default)]
|
||||
pub command: String,
|
||||
|
||||
/// Extra args which are interpolated into the `docker run` command,
|
||||
/// and affect the container configuration.
|
||||
#[serde(default)]
|
||||
pub extra_args: Vec<String>,
|
||||
}
|
||||
|
||||
fn default_send_alerts() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_term_signal_labels() -> Vec<TerminationSignalLabel> {
|
||||
vec![TerminationSignalLabel::default()]
|
||||
}
|
||||
|
||||
fn default_termination_timeout() -> i32 {
|
||||
10
|
||||
}
|
||||
|
||||
fn default_network() -> String {
|
||||
String::from("host")
|
||||
}
|
||||
|
||||
impl Default for DeploymentConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
server_id: Default::default(),
|
||||
send_alerts: default_send_alerts(),
|
||||
image: Default::default(),
|
||||
docker_account: Default::default(),
|
||||
skip_secret_interp: Default::default(),
|
||||
redeploy_on_build: Default::default(),
|
||||
term_signal_labels: default_term_signal_labels(),
|
||||
termination_signal: Default::default(),
|
||||
termination_timeout: default_termination_timeout(),
|
||||
ports: Default::default(),
|
||||
volumes: Default::default(),
|
||||
environment: Default::default(),
|
||||
labels: Default::default(),
|
||||
network: default_network(),
|
||||
restart: Default::default(),
|
||||
command: Default::default(),
|
||||
extra_args: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
#[serde(tag = "type", content = "params")]
|
||||
pub enum DeploymentImage {
|
||||
/// Deploy any external image.
|
||||
Image {
|
||||
/// The docker image, can be from any registry that works with docker and that the host server can reach.
|
||||
#[serde(default)]
|
||||
image: String,
|
||||
},
|
||||
|
||||
/// Deploy a monitor build.
|
||||
Build {
|
||||
/// The id of the build
|
||||
#[serde(default, alias = "build")]
|
||||
build_id: String,
|
||||
/// Use a custom / older version of the image produced by the build.
|
||||
/// if version is 0.0.0, this means `latest` image.
|
||||
#[serde(default)]
|
||||
version: Version,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for DeploymentImage {
|
||||
fn default() -> Self {
|
||||
Self::Image {
|
||||
image: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Default, PartialEq,
|
||||
)]
|
||||
pub struct Conversion {
|
||||
/// reference on the server.
|
||||
pub local: String,
|
||||
/// reference in the container.
|
||||
pub container: String,
|
||||
}
|
||||
|
||||
/// A summary of a docker container on a server.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ContainerSummary {
|
||||
/// Name of the container.
|
||||
pub name: String,
|
||||
/// Id of the container.
|
||||
pub id: String,
|
||||
/// The image the container is based on.
|
||||
pub image: String,
|
||||
/// The docker labels on the container.
|
||||
pub labels: HashMap<String, String>,
|
||||
/// The state of the container, like `running` or `not_deployed`
|
||||
pub state: DeploymentState,
|
||||
/// The status string of the docker container.
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct DockerContainerStats {
|
||||
#[serde(alias = "Name")]
|
||||
pub name: String,
|
||||
#[serde(alias = "CPUPerc")]
|
||||
pub cpu_perc: String,
|
||||
#[serde(alias = "MemPerc")]
|
||||
pub mem_perc: String,
|
||||
#[serde(alias = "MemUsage")]
|
||||
pub mem_usage: String,
|
||||
#[serde(alias = "NetIO")]
|
||||
pub net_io: String,
|
||||
#[serde(alias = "BlockIO")]
|
||||
pub block_io: String,
|
||||
#[serde(alias = "PIDs")]
|
||||
pub pids: String,
|
||||
}
|
||||
|
||||
/// Variants de/serialized from/to snake_case.
|
||||
///
|
||||
/// Eg.
|
||||
/// - NotDeployed -> not_deployed
|
||||
/// - Restarting -> restarting
|
||||
/// - Running -> running.
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Hash,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Default,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DeploymentState {
|
||||
#[default]
|
||||
Unknown,
|
||||
NotDeployed,
|
||||
Created,
|
||||
Restarting,
|
||||
Running,
|
||||
Removing,
|
||||
Paused,
|
||||
Exited,
|
||||
Dead,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Hash,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Default,
|
||||
)]
|
||||
pub enum RestartMode {
|
||||
#[default]
|
||||
#[serde(rename = "no")]
|
||||
NoRestart,
|
||||
#[serde(rename = "on-failure")]
|
||||
OnFailure,
|
||||
#[serde(rename = "always")]
|
||||
Always,
|
||||
#[serde(rename = "unless-stopped")]
|
||||
UnlessStopped,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Hash,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Default,
|
||||
)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
pub enum TerminationSignal {
|
||||
#[serde(alias = "1")]
|
||||
SigHup,
|
||||
#[serde(alias = "2")]
|
||||
SigInt,
|
||||
#[serde(alias = "3")]
|
||||
SigQuit,
|
||||
#[default]
|
||||
#[serde(alias = "15")]
|
||||
SigTerm,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq,
|
||||
)]
|
||||
pub struct TerminationSignalLabel {
|
||||
pub signal: TerminationSignal,
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
pub struct DeploymentActionState {
|
||||
pub deploying: bool,
|
||||
pub stopping: bool,
|
||||
pub starting: bool,
|
||||
pub removing: bool,
|
||||
pub renaming: bool,
|
||||
}
|
||||
@@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod build;
|
||||
pub mod resource;
|
||||
pub mod deployment;
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Debug, Clone, Default, PartialEq,
|
||||
|
||||
@@ -17,12 +17,12 @@ impl Resolve<Build> for State {
|
||||
&self,
|
||||
Build {
|
||||
build,
|
||||
docker_token,
|
||||
registry_token,
|
||||
replacers,
|
||||
}: Build,
|
||||
_: (),
|
||||
) -> anyhow::Result<Vec<Log>> {
|
||||
docker::build::build(&build, docker_token, replacers).await
|
||||
docker::build::build(&build, registry_token, replacers).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -192,9 +192,9 @@ impl Resolve<Deploy> for State {
|
||||
&self,
|
||||
Deploy {
|
||||
deployment,
|
||||
docker_token,
|
||||
stop_signal,
|
||||
stop_time,
|
||||
registry_token,
|
||||
replacers,
|
||||
}: Deploy,
|
||||
_: (),
|
||||
@@ -207,7 +207,7 @@ impl Resolve<Deploy> for State {
|
||||
stop_time
|
||||
.unwrap_or(deployment.config.termination_timeout)
|
||||
.into(),
|
||||
docker_token,
|
||||
registry_token,
|
||||
replacers,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::anyhow;
|
||||
use anyhow::{anyhow, Context};
|
||||
use monitor_client::entities::{
|
||||
to_monitor_name, update::Log, CloneArgs, LatestCommit,
|
||||
};
|
||||
@@ -35,12 +35,17 @@ impl Resolve<CloneRepo> for State {
|
||||
_: (),
|
||||
) -> anyhow::Result<Vec<Log>> {
|
||||
let CloneArgs { github_account, .. } = &args;
|
||||
let github_token =
|
||||
match (github_token, get_github_token(github_account)) {
|
||||
(Some(token), _) => Some(token),
|
||||
(None, Ok(token)) => token,
|
||||
(None, Err(e)) => return Err(e),
|
||||
};
|
||||
let github_token = match (github_account, github_token) {
|
||||
(None, _) => None,
|
||||
(Some(_), Some(token)) => Some(token),
|
||||
(Some(account), None) => Some(
|
||||
get_github_token(account)
|
||||
.context(
|
||||
"failed to get github token from periphery config",
|
||||
)?
|
||||
.clone(),
|
||||
),
|
||||
};
|
||||
git::clone(args, &periphery_config().repo_dir, github_token).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ use anyhow::Context;
|
||||
use command::run_monitor_command;
|
||||
use monitor_client::entities::{
|
||||
build::{Build, BuildConfig},
|
||||
optional_string, to_monitor_name,
|
||||
get_image_name, optional_string, to_monitor_name,
|
||||
update::Log,
|
||||
EnvironmentVar, Version,
|
||||
};
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{config::periphery_config, helpers::get_docker_token};
|
||||
use crate::config::periphery_config;
|
||||
|
||||
use super::{docker_login, parse_extra_args, parse_labels};
|
||||
|
||||
@@ -18,15 +18,17 @@ pub async fn prune_images() -> Log {
|
||||
run_monitor_command("prune images", command).await
|
||||
}
|
||||
|
||||
#[instrument(skip(docker_token, core_replacers))]
|
||||
#[instrument(skip(registry_token, core_replacers))]
|
||||
pub async fn build(
|
||||
Build {
|
||||
build: &Build,
|
||||
registry_token: Option<String>,
|
||||
core_replacers: Vec<(String, String)>,
|
||||
) -> anyhow::Result<Vec<Log>> {
|
||||
let Build {
|
||||
name,
|
||||
config:
|
||||
BuildConfig {
|
||||
version,
|
||||
docker_account,
|
||||
docker_organization,
|
||||
skip_secret_interp,
|
||||
build_path,
|
||||
dockerfile_path,
|
||||
@@ -34,61 +36,66 @@ pub async fn build(
|
||||
labels,
|
||||
extra_args,
|
||||
use_buildx,
|
||||
image_registry,
|
||||
..
|
||||
},
|
||||
..
|
||||
}: &Build,
|
||||
docker_token: Option<String>,
|
||||
core_replacers: Vec<(String, String)>,
|
||||
) -> anyhow::Result<Vec<Log>> {
|
||||
} = build;
|
||||
|
||||
let mut logs = Vec::new();
|
||||
let docker_token = match (
|
||||
docker_token,
|
||||
get_docker_token(&optional_string(docker_account)),
|
||||
) {
|
||||
(Some(docker_token), _) => Some(docker_token),
|
||||
(None, Ok(docker_token)) => docker_token,
|
||||
(None, Err(e)) => {
|
||||
logs.push(Log::error("build", serialize_error_pretty(&e)));
|
||||
|
||||
// Maybe docker login
|
||||
let should_push = match docker_login(
|
||||
image_registry,
|
||||
registry_token.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(should_push) => should_push,
|
||||
Err(e) => {
|
||||
logs.push(Log::error(
|
||||
"docker login",
|
||||
serialize_error_pretty(
|
||||
&e.context("failed to login to docker registry"),
|
||||
),
|
||||
));
|
||||
return Ok(logs);
|
||||
}
|
||||
};
|
||||
|
||||
// Get paths
|
||||
let name = to_monitor_name(name);
|
||||
let using_account =
|
||||
docker_login(&optional_string(docker_account), &docker_token)
|
||||
.await
|
||||
.context("failed to login to docker")?;
|
||||
let build_dir =
|
||||
periphery_config().repo_dir.join(&name).join(build_path);
|
||||
let dockerfile_path = match optional_string(dockerfile_path) {
|
||||
Some(dockerfile_path) => dockerfile_path.to_owned(),
|
||||
None => "Dockerfile".to_owned(),
|
||||
};
|
||||
|
||||
// Get command parts
|
||||
let build_args = parse_build_args(build_args);
|
||||
let labels = parse_labels(labels);
|
||||
let extra_args = parse_extra_args(extra_args);
|
||||
let buildx = if *use_buildx { " buildx" } else { "" };
|
||||
let image_name = get_image_name(
|
||||
&name,
|
||||
&optional_string(docker_account),
|
||||
&optional_string(docker_organization),
|
||||
);
|
||||
let image_name = get_image_name(build);
|
||||
let image_tags = image_tags(&image_name, version);
|
||||
let docker_push = if using_account {
|
||||
format!(" && docker image push --all-tags {image_name}")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let push_command = should_push
|
||||
.then(|| format!(" && docker image push --all-tags {image_name}"))
|
||||
.unwrap_or_default();
|
||||
|
||||
// Construct command
|
||||
let command = format!(
|
||||
"cd {} && docker{buildx} build{build_args}{extra_args}{labels}{image_tags} -f {dockerfile_path} .{docker_push}",
|
||||
"cd {} && docker{buildx} build{build_args}{extra_args}{labels}{image_tags} -f {dockerfile_path} .{push_command}",
|
||||
build_dir.display()
|
||||
);
|
||||
|
||||
if *skip_secret_interp {
|
||||
let build_log =
|
||||
run_monitor_command("docker build", command).await;
|
||||
info!("finished building docker image");
|
||||
logs.push(build_log);
|
||||
} else {
|
||||
// Interpolate any missing secrets
|
||||
let (command, mut replacers) = svi::interpolate_variables(
|
||||
&command,
|
||||
&periphery_config().secrets,
|
||||
@@ -110,23 +117,8 @@ pub async fn build(
|
||||
svi::replace_in_string(&build_log.stderr, &replacers);
|
||||
logs.push(build_log);
|
||||
}
|
||||
Ok(logs)
|
||||
}
|
||||
|
||||
fn get_image_name(
|
||||
name: &str,
|
||||
docker_account: &Option<String>,
|
||||
docker_organization: &Option<String>,
|
||||
) -> String {
|
||||
match docker_organization {
|
||||
Some(docker_org) => format!("{docker_org}/{name}"),
|
||||
None => match docker_account {
|
||||
Some(docker_account) => {
|
||||
format!("{docker_account}/{name}")
|
||||
}
|
||||
None => name.to_string(),
|
||||
},
|
||||
}
|
||||
Ok(logs)
|
||||
}
|
||||
|
||||
fn image_tags(image_name: &str, version: &Version) -> String {
|
||||
|
||||
@@ -5,14 +5,14 @@ use monitor_client::entities::{
|
||||
Conversion, Deployment, DeploymentConfig, DeploymentImage,
|
||||
DockerContainerStats, RestartMode, TerminationSignal,
|
||||
},
|
||||
optional_string, to_monitor_name,
|
||||
to_monitor_name,
|
||||
update::Log,
|
||||
EnvironmentVar, SearchCombinator,
|
||||
};
|
||||
use run_command::async_run_command;
|
||||
use serror::serialize_error_pretty;
|
||||
|
||||
use crate::{config::periphery_config, helpers::get_docker_token};
|
||||
use crate::config::periphery_config;
|
||||
|
||||
use super::{docker_login, parse_extra_args, parse_labels};
|
||||
|
||||
@@ -173,34 +173,26 @@ async fn pull_image(image: &str) -> Log {
|
||||
run_monitor_command("docker pull", command).await
|
||||
}
|
||||
|
||||
#[instrument(skip(docker_token, core_replacers))]
|
||||
#[instrument(skip(registry_token, core_replacers))]
|
||||
pub async fn deploy(
|
||||
deployment: &Deployment,
|
||||
stop_signal: Option<TerminationSignal>,
|
||||
stop_time: Option<i32>,
|
||||
docker_token: Option<String>,
|
||||
registry_token: Option<String>,
|
||||
core_replacers: Vec<(String, String)>,
|
||||
) -> Log {
|
||||
let docker_token = match (
|
||||
docker_token,
|
||||
get_docker_token(&optional_string(
|
||||
&deployment.config.docker_account,
|
||||
)),
|
||||
) {
|
||||
(Some(token), _) => Some(token),
|
||||
(None, Ok(token)) => token,
|
||||
(None, Err(e)) => {
|
||||
return Log::error("docker login", serialize_error_pretty(&e))
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = docker_login(
|
||||
&optional_string(&deployment.config.docker_account),
|
||||
&docker_token,
|
||||
&deployment.config.image_registry,
|
||||
registry_token.as_deref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
return Log::error("docker login", serialize_error_pretty(&e));
|
||||
return Log::error(
|
||||
"docker login",
|
||||
serialize_error_pretty(
|
||||
&e.context("failed to login to docker registry"),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
let image = if let DeploymentImage::Image { image } =
|
||||
|
||||
@@ -1,40 +1,65 @@
|
||||
use anyhow::anyhow;
|
||||
use command::run_monitor_command;
|
||||
use monitor_client::entities::{update::Log, EnvironmentVar};
|
||||
use monitor_client::entities::{
|
||||
build::{CloudRegistryConfig, ImageRegistry},
|
||||
update::Log,
|
||||
EnvironmentVar,
|
||||
};
|
||||
use run_command::async_run_command;
|
||||
|
||||
use crate::helpers::{get_docker_token, get_github_token};
|
||||
|
||||
pub mod build;
|
||||
pub mod client;
|
||||
pub mod container;
|
||||
pub mod network;
|
||||
|
||||
pub fn get_docker_username_pw(
|
||||
docker_account: &Option<String>,
|
||||
docker_token: &Option<String>,
|
||||
) -> anyhow::Result<Option<(String, String)>> {
|
||||
match docker_account {
|
||||
Some(docker_account) => match docker_token {
|
||||
Some(docker_token) => Ok(Some((docker_account.to_owned(), docker_token.to_owned()))),
|
||||
None => Err(anyhow!(
|
||||
"docker token for account {docker_account} has not been configured on this client"
|
||||
)),
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether build result should be pushed after build
|
||||
pub async fn docker_login(
|
||||
docker_account: &Option<String>,
|
||||
docker_token: &Option<String>,
|
||||
registry: &ImageRegistry,
|
||||
// For local token override from core.
|
||||
registry_token: Option<&str>,
|
||||
) -> anyhow::Result<bool> {
|
||||
let docker_account_u_pw =
|
||||
get_docker_username_pw(docker_account, docker_token)?;
|
||||
if let Some((username, password)) = &docker_account_u_pw {
|
||||
let login = format!("docker login -u {username} -p {password}");
|
||||
async_run_command(&login).await;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
match registry {
|
||||
ImageRegistry::None(_) => Ok(false),
|
||||
ImageRegistry::DockerHub(CloudRegistryConfig {
|
||||
account, ..
|
||||
}) => {
|
||||
if account.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Must configure account for DockerHub registry"
|
||||
));
|
||||
}
|
||||
let registry_token = match registry_token {
|
||||
Some(token) => token,
|
||||
None => get_docker_token(account)?,
|
||||
};
|
||||
async_run_command(&format!(
|
||||
"docker login -u {account} -p {registry_token}",
|
||||
))
|
||||
.await;
|
||||
Ok(true)
|
||||
}
|
||||
ImageRegistry::GithubContainerRegistry(CloudRegistryConfig {
|
||||
account,
|
||||
..
|
||||
}) => {
|
||||
if account.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"Must configure account for GithubContainerRegistry"
|
||||
));
|
||||
}
|
||||
let registry_token = match registry_token {
|
||||
Some(token) => token,
|
||||
None => get_github_token(account)?,
|
||||
};
|
||||
async_run_command(&format!(
|
||||
"docker login ghcr.io -u {account} -p {registry_token}",
|
||||
))
|
||||
.await;
|
||||
Ok(true)
|
||||
}
|
||||
ImageRegistry::Custom(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,21 @@
|
||||
use anyhow::anyhow;
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::config::periphery_config;
|
||||
|
||||
pub fn get_github_token(
|
||||
github_account: &Option<String>,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
match github_account {
|
||||
Some(account) => {
|
||||
match periphery_config().github_accounts.get(account) {
|
||||
Some(token) => Ok(Some(token.to_owned())),
|
||||
None => Err(anyhow!(
|
||||
"did not find token in config for github account {account}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
github_account: &String,
|
||||
) -> anyhow::Result<&'static String> {
|
||||
periphery_config()
|
||||
.github_accounts
|
||||
.get(github_account)
|
||||
.with_context(|| format!("did not find token in config for github account {github_account}"))
|
||||
}
|
||||
|
||||
pub fn get_docker_token(
|
||||
docker_account: &Option<String>,
|
||||
) -> anyhow::Result<Option<String>> {
|
||||
match docker_account {
|
||||
Some(account) => {
|
||||
match periphery_config().docker_accounts.get(account) {
|
||||
Some(token) => Ok(Some(token.to_owned())),
|
||||
None => Err(anyhow!(
|
||||
"did not find token in config for docker account {account}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
docker_account: &String,
|
||||
) -> anyhow::Result<&'static String> {
|
||||
periphery_config()
|
||||
.docker_accounts
|
||||
.get(docker_account)
|
||||
.with_context(|| format!("did not find token in config for docker account {docker_account}"))
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::entities::I64;
|
||||
|
||||
use super::{
|
||||
resource::{Resource, ResourceListItem, ResourceQuery},
|
||||
EnvironmentVar, SystemCommand, Version,
|
||||
EnvironmentVar, NoData, SystemCommand, Version,
|
||||
};
|
||||
|
||||
#[typeshare]
|
||||
@@ -103,18 +103,6 @@ pub struct BuildConfig {
|
||||
#[builder(default)]
|
||||
pub github_account: String,
|
||||
|
||||
/// The dockerhub account used to push the image to dockerhub.
|
||||
/// Empty string means no dockerhub push (server local build).
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub docker_account: String,
|
||||
|
||||
/// The docker organization which the image should be pushed under.
|
||||
/// Empty string means no organization.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub docker_organization: String,
|
||||
|
||||
/// The optional command run after repo clone and before docker build.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
@@ -153,6 +141,11 @@ pub struct BuildConfig {
|
||||
#[builder(default)]
|
||||
pub use_buildx: bool,
|
||||
|
||||
/// Configuration for the registry to push the built image to.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub image_registry: ImageRegistry,
|
||||
|
||||
/// Whether incoming webhooks actually trigger action.
|
||||
#[serde(default = "default_webhook_enabled")]
|
||||
#[builder(default = "default_webhook_enabled()")]
|
||||
@@ -192,8 +185,6 @@ impl Default for BuildConfig {
|
||||
branch: default_branch(),
|
||||
commit: Default::default(),
|
||||
github_account: Default::default(),
|
||||
docker_account: Default::default(),
|
||||
docker_organization: Default::default(),
|
||||
pre_build: Default::default(),
|
||||
build_path: default_build_path(),
|
||||
dockerfile_path: default_dockerfile_path(),
|
||||
@@ -201,11 +192,48 @@ impl Default for BuildConfig {
|
||||
labels: Default::default(),
|
||||
extra_args: Default::default(),
|
||||
use_buildx: Default::default(),
|
||||
image_registry: Default::default(),
|
||||
webhook_enabled: default_webhook_enabled(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for the registry to push the built image to.
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ImageRegistry {
|
||||
/// Don't push the image to any registry
|
||||
None(NoData),
|
||||
/// Push the image to DockerHub
|
||||
DockerHub(CloudRegistryConfig),
|
||||
/// Push the image to the Github Container Registry
|
||||
GithubContainerRegistry(CloudRegistryConfig),
|
||||
/// Todo. Will point to a custom "Registry" resource by id
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl Default for ImageRegistry {
|
||||
fn default() -> Self {
|
||||
Self::None(NoData {})
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for a cloud image registry, like account and organization.
|
||||
#[typeshare]
|
||||
#[derive(
|
||||
Debug, Clone, Default, PartialEq, Serialize, Deserialize,
|
||||
)]
|
||||
pub struct CloudRegistryConfig {
|
||||
/// Specify an account to use with the cloud registry.
|
||||
#[serde(default)]
|
||||
pub account: String,
|
||||
|
||||
/// Optional. Specify an organization to push the image under.
|
||||
/// Empty string means no organization.
|
||||
#[serde(default)]
|
||||
pub organization: String,
|
||||
}
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
pub struct BuildActionState {
|
||||
|
||||
@@ -10,6 +10,7 @@ use strum::{Display, EnumString};
|
||||
use typeshare::typeshare;
|
||||
|
||||
use super::{
|
||||
build::ImageRegistry,
|
||||
resource::{Resource, ResourceListItem, ResourceQuery},
|
||||
EnvironmentVar, Version,
|
||||
};
|
||||
@@ -62,6 +63,17 @@ pub struct DeploymentConfig {
|
||||
#[builder(default)]
|
||||
pub image: DeploymentImage,
|
||||
|
||||
/// Configure the registry used to pull the image from the registry.
|
||||
/// Used with `docker login`.
|
||||
///
|
||||
/// When using attached build as image source:
|
||||
/// - If the field is `None` variant, will use the same ImageRegistry config as the build.
|
||||
/// - Otherwise, it must match the variant of the ImageRegistry build config.
|
||||
/// - Only the account is used, the organization is not needed here
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub image_registry: ImageRegistry,
|
||||
|
||||
/// Whether to skip secret interpolation into the deployment environment variables.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
@@ -138,13 +150,6 @@ pub struct DeploymentConfig {
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub extra_args: Vec<String>,
|
||||
|
||||
/// The docker account the deployment should use to pull the image.
|
||||
/// - If using a custom image, empty string means don't use an account. Only works for public images.
|
||||
/// - If using a monitor build, empty string means to use the same docker account as the build uses.
|
||||
#[serde(default)]
|
||||
#[builder(default)]
|
||||
pub docker_account: String,
|
||||
}
|
||||
|
||||
impl DeploymentConfig {
|
||||
@@ -175,6 +180,7 @@ impl Default for DeploymentConfig {
|
||||
server_id: Default::default(),
|
||||
send_alerts: default_send_alerts(),
|
||||
image: Default::default(),
|
||||
image_registry: Default::default(),
|
||||
skip_secret_interp: Default::default(),
|
||||
redeploy_on_build: Default::default(),
|
||||
term_signal_labels: default_term_signal_labels(),
|
||||
@@ -188,7 +194,6 @@ impl Default for DeploymentConfig {
|
||||
restart: Default::default(),
|
||||
command: Default::default(),
|
||||
extra_args: Default::default(),
|
||||
docker_account: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::str::FromStr;
|
||||
|
||||
use anyhow::Context;
|
||||
use async_timing_util::unix_timestamp_ms;
|
||||
use build::CloudRegistryConfig;
|
||||
use clap::Parser;
|
||||
use derive_empty_traits::EmptyTraits;
|
||||
use serde::{
|
||||
@@ -105,22 +106,44 @@ pub fn optional_string(string: &str) -> Option<String> {
|
||||
pub fn get_image_name(
|
||||
build::Build {
|
||||
name,
|
||||
config:
|
||||
build::BuildConfig {
|
||||
docker_organization,
|
||||
docker_account,
|
||||
..
|
||||
},
|
||||
config: build::BuildConfig { image_registry, .. },
|
||||
..
|
||||
}: &build::Build,
|
||||
) -> String {
|
||||
let name = to_monitor_name(name);
|
||||
if !docker_organization.is_empty() {
|
||||
format!("{docker_organization}/{name}")
|
||||
} else if !docker_account.is_empty() {
|
||||
format!("{docker_account}/{name}")
|
||||
} else {
|
||||
name
|
||||
match image_registry {
|
||||
// TODO
|
||||
build::ImageRegistry::None(_) => name,
|
||||
build::ImageRegistry::DockerHub(CloudRegistryConfig {
|
||||
organization,
|
||||
account,
|
||||
}) => {
|
||||
if !organization.is_empty() {
|
||||
format!("{organization}/{name}")
|
||||
} else if !account.is_empty() {
|
||||
format!("{account}/{name}")
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}
|
||||
build::ImageRegistry::GithubContainerRegistry(
|
||||
CloudRegistryConfig {
|
||||
organization,
|
||||
account,
|
||||
},
|
||||
) => {
|
||||
if !organization.is_empty() {
|
||||
format!("ghcr.io/{organization}/{name}")
|
||||
} else if !account.is_empty() {
|
||||
format!("ghcr.io/{account}/{name}")
|
||||
} else {
|
||||
name
|
||||
}
|
||||
}
|
||||
build::ImageRegistry::Custom(_) => {
|
||||
// TODO
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize};
|
||||
#[response(BuildResponse)]
|
||||
pub struct Build {
|
||||
pub build: monitor_client::entities::build::Build,
|
||||
/// Override docker token with one sent from core.
|
||||
pub docker_token: Option<String>,
|
||||
/// Override registry token with one sent from core.
|
||||
pub registry_token: Option<String>,
|
||||
/// Propogate any secret replacers from core interpolation.
|
||||
pub replacers: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
@@ -101,10 +101,10 @@ pub struct PruneContainers {}
|
||||
#[response(Log)]
|
||||
pub struct Deploy {
|
||||
pub deployment: Deployment,
|
||||
/// Override docker token with one sent from core.
|
||||
pub docker_token: Option<String>,
|
||||
pub stop_signal: Option<TerminationSignal>,
|
||||
pub stop_time: Option<i32>,
|
||||
/// Override registry token with one sent from core.
|
||||
pub registry_token: Option<String>,
|
||||
/// Propogate any secret replacers from core interpolation.
|
||||
pub replacers: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user