Skip to content

Commit

Permalink
Feat | Read apps from all namespaces
Browse files Browse the repository at this point in the history
  • Loading branch information
dag-andersen committed Dec 21, 2024
1 parent d722d2f commit a6c47b5
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 46 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ output/
/apps_target_branch.yaml
/apps_base_branch.yaml
venv/
base-branch/
target-branch/
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Overrides done by argocd-diff-preview
# These will happen after the values.yaml file is loaded
# These will happen AFTER the values.yaml file is loaded
notifications:
enabled: false
dex:
Expand Down
9 changes: 9 additions & 0 deletions argocd-config/values-pre.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Overrides done by argocd-diff-preview
# These will happen BEFORE the values.yaml file is loaded
notifications:
enabled: false
dex:
enabled: false
configs:
params:
application.namespaces: "*"
11 changes: 6 additions & 5 deletions src/argo_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct ArgoResource {
pub yaml: serde_yaml::Value,
pub kind: ApplicationKind,
pub name: String,
pub namespace: String,
}

impl std::fmt::Display for ArgoResource {
Expand All @@ -33,11 +34,6 @@ impl PartialEq for ArgoResource {
}

impl ArgoResource {
pub fn set_namespace(mut self, namespace: &str) -> ArgoResource {
self.yaml["metadata"]["namespace"] = serde_yaml::Value::String(namespace.to_string());
self
}

pub fn set_project_to_default(mut self) -> Result<ArgoResource, Box<dyn Error>> {
let spec = match self.kind {
ApplicationKind::Application => self.yaml["spec"].as_mapping_mut(),
Expand Down Expand Up @@ -166,11 +162,16 @@ impl ArgoResource {
_ => None,
})?;

let namespace = k8s_resource.yaml["metadata"]["namespace"]
.as_str()
.unwrap_or("default");

match k8s_resource.yaml["metadata"]["name"].as_str() {
Some(name) => Some(ArgoResource {
kind,
file_name: k8s_resource.file_name,
name: name.to_string(),
namespace: namespace.to_string(),
yaml: k8s_resource.yaml,
}),
_ => None,
Expand Down
53 changes: 34 additions & 19 deletions src/argocd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,23 @@ pub const ARGO_CD_NAMESPACE: &str = "argocd-diff-preview";

const CONFIG_PATH: &str = "argocd-config";

pub fn create_namespace() -> Result<(), Box<dyn Error>> {
run_command(&format!("kubectl create ns {}", ARGO_CD_NAMESPACE)).map_err(|e| {
error!("❌ Failed to create namespace '{}'", ARGO_CD_NAMESPACE);
CommandError::new(e)
})?;
debug!("🦑 Namespace '{}' created successfully", ARGO_CD_NAMESPACE);
Ok(())
pub fn create_namespace(s: &str) -> Result<(), Box<dyn Error>> {
let already_exist = run_command(&format!("kubectl get ns {}", s)).is_ok();

match already_exist {
true => {
debug!("🦑 Namespace {} already exists", s);
Ok(())
}
false => {
run_command(&format!("kubectl create ns {}", s)).map_err(|e| {
error!("❌ Failed to create namespace {}", s);
CommandError::new(e)
})?;
debug!("🦑 Namespace {} created successfully", s);
Ok(())
}
}
}

pub async fn install_argo_cd(options: ArgoCDOptions<'_>) -> Result<(), Box<dyn Error>> {
Expand All @@ -30,24 +40,26 @@ pub async fn install_argo_cd(options: ArgoCDOptions<'_>) -> Result<(), Box<dyn E
options.version.unwrap_or("latest")
);

let (values, values_override) = match std::fs::read_dir(CONFIG_PATH) {
let (values_pre, values, values_post) = match std::fs::read_dir(CONFIG_PATH) {
Ok(dir) => {
debug!("📂 Files in folder 'argocd-config':");
for file in dir {
debug!("- 📄 {:?}", file.unwrap().file_name());
}
let values_exist = std::fs::metadata(format!("{}/values.yaml", CONFIG_PATH))
let values_pre = std::fs::metadata(format!("{}/values-pre.yaml", CONFIG_PATH))
.is_ok()
.then_some(format!("-f {}/values-pre.yaml", CONFIG_PATH));
let values = std::fs::metadata(format!("{}/values.yaml", CONFIG_PATH))
.is_ok()
.then_some(format!("-f {}/values.yaml", CONFIG_PATH));
let values_override_exist =
std::fs::metadata(format!("{}/values-override.yaml", CONFIG_PATH))
.is_ok()
.then_some(format!("-f {}/values-override.yaml", CONFIG_PATH));
(values_exist, values_override_exist)
let values_post = std::fs::metadata(format!("{}/values-post.yaml", CONFIG_PATH))
.is_ok()
.then_some(format!("-f {}/values-post.yaml", CONFIG_PATH));
(values, values_pre, values_post)
}
Err(_e) => {
info!("📂 Folder '{}' doesn't exist. Installing Argo CD Helm Chart with default configuration", CONFIG_PATH);
(None, None)
(None, None, None)
}
};

Expand All @@ -61,13 +73,14 @@ pub async fn install_argo_cd(options: ArgoCDOptions<'_>) -> Result<(), Box<dyn E
"helm install argocd argo/argo-cd -n {} {} {} {}",
ARGO_CD_NAMESPACE,
values.unwrap_or_default(),
values_override.unwrap_or_default(),
values_post.unwrap_or_default(),
options
.version
.map(|a| format!("--version {}", a))
.unwrap_or_default(),
);

debug!("Installing Argo CD Helm Chart...");
run_command(&helm_install_command).map_err(|e| {
error!("❌ Failed to install Argo CD");
CommandError::new(e)
Expand Down Expand Up @@ -118,12 +131,11 @@ pub async fn install_argo_cd(options: ArgoCDOptions<'_>) -> Result<(), Box<dyn E
}
}
let password_encoded = password_encoded.unwrap().stdout;
let password_decoded = BASE64_STANDARD.decode(password_encoded).map_err(|e| {
let password_decoded = BASE64_STANDARD.decode(password_encoded).inspect_err(|e| {
error!("❌ Failed to decode password: {}", e);
e
})?;

String::from_utf8(password_decoded).inspect_err(|e| {
String::from_utf8(password_decoded).inspect_err(|_e| {
error!("❌ failed to convert password to string");
})?
};
Expand Down Expand Up @@ -170,6 +182,9 @@ pub async fn install_argo_cd(options: ArgoCDOptions<'_>) -> Result<(), Box<dyn E
}
}

// Add extra permissions to the default AppProject
let _ = run_command("argocd proj add-source-namespace default *", None);

info!("🦑 Argo CD installed successfully");
Ok(())
}
15 changes: 5 additions & 10 deletions src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,8 @@ pub async fn get_resources(
let start_time = std::time::Instant::now();

loop {
let command = format!(
"kubectl get applications -n {} -oyaml",
ARGO_CD_NAMESPACE
);
let applications: Result<Value, serde_yaml::Error> = match run_command(&command) {
let command = "kubectl get applications --all-namespaces -oyaml";
let applications: Result<Value, serde_yaml::Error> = match run_command(command) {
Ok(o) => serde_yaml::from_str(&o.stdout),
Err(e) => return Err(format!("❌ Failed to get applications: {}", e.stderr).into()),
};
Expand Down Expand Up @@ -224,10 +221,7 @@ pub async fn delete_applications() -> Result<(), Box<dyn Error>> {
loop {
debug!("🗑 Deleting ApplicationSets");

match run_command(&format!(
"kubectl delete applicationsets.argoproj.io --all -n {}",
ARGO_CD_NAMESPACE
)) {
match run_command("kubectl delete applicationsets.argoproj.io --all --all-namespaces") {
Ok(_) => debug!("🗑 Deleted ApplicationSets"),
Err(e) => {
error!("❌ Failed to delete applicationsets: {}", &e.stderr)
Expand All @@ -237,9 +231,10 @@ pub async fn delete_applications() -> Result<(), Box<dyn Error>> {
debug!("🗑 Deleting Applications");

let mut child = spawn_command(
&format!("kubectl delete applications.argoproj.io --all -n {}", ARGO_CD_NAMESPACE),
"kubectl delete applications.argoproj.io --all --all-namespaces",
None,
);

tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
if run_command("kubectl get applications -A --no-headers")
.map(|e| e.stdout.trim().is_empty())
Expand Down
26 changes: 17 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use argo_resource::ArgoResource;
use argocd::create_namespace;
use branch::{Branch, BranchType};
use error::CommandOutput;
use log::{debug, error, info};
Expand Down Expand Up @@ -124,13 +125,12 @@ impl FromStr for ClusterTool {

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
run().await.map_err(|e| {
run().await.inspect_err(|e| {
let opt = Opt::from_args();
error!("❌ {}", e);
if !opt.keep_cluster_alive {
cleanup_cluster(opt.local_cluster_tool, &opt.cluster_name);
}
e
})
}

Expand Down Expand Up @@ -298,6 +298,12 @@ async fn run() -> Result<(), Box<dyn Error>> {
return Ok(());
}

let unique_namespaces = base_apps
.iter()
.map(|a| a.namespace.clone())
.chain(target_apps.iter().map(|a| a.namespace.clone()))
.collect::<std::collections::HashSet<String>>();

fs::write(base_branch.app_file(), applications_to_string(base_apps))?;
fs::write(
target_branch.app_file(),
Expand All @@ -309,15 +315,17 @@ async fn run() -> Result<(), Box<dyn Error>> {
ClusterTool::Minikube => minikube::create_cluster()?,
}

argocd::create_namespace()?;
// Create a namespace for each application
for namespace in unique_namespaces {
create_namespace(&namespace)?;
}

create_folder_if_not_exists(secrets_folder)?;
match apply_folder(secrets_folder) {
Ok(count) if count > 0 => info!("🤫 Applied {} secrets", count),
Ok(_) => info!("🤷 No secrets found in {}", secrets_folder),
Err(e) => {
error!("❌ Failed to apply secrets");
return Err(e);
return Err(e.into());
}
}

Expand Down Expand Up @@ -400,14 +408,14 @@ fn cleanup_cluster(tool: ClusterTool, cluster_name: &str) {
}

fn apply_manifest(file_name: &str) -> Result<CommandOutput, CommandOutput> {
run_command(&format!("kubectl apply -f {}", file_name)).inspect_err(|e| {
run_command(&format!("kubectl apply -f {}", file_name)).inspect_err(|_e| {
error!("❌ Failed to apply manifest: {}", file_name);
})
}

fn apply_folder(folder_name: &str) -> Result<u64, Box<dyn Error>> {
fn apply_folder(folder_name: &str) -> Result<u64, String> {
if !PathBuf::from(folder_name).is_dir() {
return Err(format!("{} is not a directory", folder_name).into());
return Err(format!("{} is not a directory", folder_name));
}
let mut count = 0;
if let Ok(entries) = fs::read_dir(folder_name) {
Expand All @@ -417,7 +425,7 @@ fn apply_folder(folder_name: &str) -> Result<u64, Box<dyn Error>> {
if file_name.ends_with(".yaml") || file_name.ends_with(".yml") {
match apply_manifest(file_name) {
Ok(_) => count += 1,
Err(e) => return Err(e.stderr.into()),
Err(e) => return Err(e.stderr),
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions src/parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ impl Clone for K8sResource {
}
}

pub fn get_applications_for_both_branches<'a>(
pub fn get_applications_for_both_branches(
base_branch: &Branch,
target_branch: &Branch,
regex: &Option<Regex>,
Expand Down Expand Up @@ -195,7 +195,6 @@ fn patch_applications(
.map(|a| {
let app_name = a.name.clone();
let app: Result<ArgoResource, Box<dyn Error>> = a
.set_namespace(ARGO_CD_NAMESPACE)
.remove_sync_policy()
.set_project_to_default()
.and_then(|a| a.point_destination_to_in_cluster())
Expand Down
3 changes: 3 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use std::process::{Child, Stdio};
use std::{fs, process::Command};

use crate::argocd::ARGO_CD_NAMESPACE;
use log::debug;

use crate::error::CommandOutput;

pub fn create_folder_if_not_exists(folder_name: &str) -> Result<(), Box<dyn Error>> {
Expand All @@ -26,6 +28,7 @@ pub fn run_command_in_dir(
command: &str,
current_dir: &str,
) -> Result<CommandOutput, CommandOutput> {
debug!("Running command: {}", command);
let args = command.split_whitespace().collect::<Vec<&str>>();
run_command_from_list(args, Some(current_dir))
}
Expand Down

0 comments on commit a6c47b5

Please sign in to comment.