Skip to content
Draft
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
12 changes: 11 additions & 1 deletion .github/workflows/generate-diff.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ jobs:
ref: main
path: main

- name: Set ArgoCD Custom Values
run: |
cat > values.yaml << "EOF"
# set whatever helm values you want
configs:
cm:
kustomize.buildOptions: --load-restrictor LoadRestrictionsNone --enable-helm
EOF

- name: Generate Diff
run: |
docker run \
Expand All @@ -33,9 +42,10 @@ jobs:
-v $(pwd)/main:/base-branch \
-v $(pwd)/pull-request:/target-branch \
-v $(pwd)/output:/output \
-v $(pwd)/values.yaml:/argocd-config/values.yaml \
-e TARGET_BRANCH=${{ github.head_ref }} \
-e REPO=${{ github.repository }} \
dagandersen/argocd-diff-preview:v0.0.26
dagandersen/argocd-diff-preview:v0.0.28 --debug

- name: Post diff as comment
run: |
Expand Down
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/
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ The safest way to make changes to you Helm Charts and Kustomize Overlays in your
> -v $(pwd)/target-branch:/target-branch \
> -e TARGET_BRANCH=helm-example-3 \
> -e REPO=dag-andersen/argocd-diff-preview \
> dagandersen/argocd-diff-preview:v0.0.26
> dagandersen/argocd-diff-preview:v0.0.27
> ```
>
> and the output would be something like this:
Expand Down Expand Up @@ -118,7 +118,7 @@ jobs:
-v $(pwd)/output:/output \
-e TARGET_BRANCH=${{ github.head_ref }} \
-e REPO=${{ github.repository }} \
dagandersen/argocd-diff-preview:v0.0.26
dagandersen/argocd-diff-preview:v0.0.27

- name: Post diff as comment
run: |
Expand Down
2 changes: 1 addition & 1 deletion docs/application-selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:
-e TARGET_BRANCH=${{ github.head_ref }} \
-e REPO=${{ github.repository }} \
-e FILES_CHANGED="${{ steps.changed-files.outputs.all_changed_files }}"
dagandersen/argocd-diff-preview:v0.0.26
dagandersen/argocd-diff-preview:v0.0.27
```

## Ignoring individual applications
Expand Down
2 changes: 1 addition & 1 deletion docs/demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ docker run \
-v $(pwd)/target-branch:/target-branch \
-e TARGET_BRANCH=helm-example-3 \
-e REPO=dag-andersen/argocd-diff-preview \
dagandersen/argocd-diff-preview:v0.0.26
dagandersen/argocd-diff-preview:v0.0.27
```

and the output would be something like this:
Expand Down
4 changes: 2 additions & 2 deletions docs/github-actions-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
-v $(pwd)/output:/output \
-e TARGET_BRANCH=${{ github.head_ref }} \
-e REPO=${{ github.repository }} \
dagandersen/argocd-diff-preview:v0.0.26
dagandersen/argocd-diff-preview:v0.0.27

- name: Post diff as comment
run: |
Expand Down Expand Up @@ -88,7 +88,7 @@ In the simple code examples above, we do not provide the cluster with any creden
-v $(pwd)/secrets:/secrets \ ⬅️ Mount the secrets folder
-e TARGET_BRANCH=${{ github.head_ref }} \
-e REPO=${{ github.repository }} \
dagandersen/argocd-diff-preview:v0.0.26
dagandersen/argocd-diff-preview:v0.0.27
```

For more info, see the [Argo CD docs](https://argo-cd.readthedocs.io/en/stable/operator-manual/argocd-repo-creds-yaml/)
4 changes: 2 additions & 2 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
-e TARGET_BRANCH=<branch-a> \
-e BASE_BRANCH=<branch-b> \
-e REPO=<owner>/<repo> \
dagandersen/argocd-diff-preview:v0.0.26
dagandersen/argocd-diff-preview:v0.0.27
```

If base-branch(`BASE_BRANCH`) is not specified it will default to `main`.
Expand All @@ -52,7 +52,7 @@
*Example for downloading and running on macOS:*

```bash
curl -LJO https://github.com/dag-andersen/argocd-diff-preview/releases/download/v0.0.26/argocd-diff-preview-Darwin-x86_64.tar.gz
curl -LJO https://github.com/dag-andersen/argocd-diff-preview/releases/download/v0.0.27/argocd-diff-preview-Darwin-x86_64.tar.gz
tar -xvf argocd-diff-preview-Darwin-x86_64.tar.gz
sudo mv argocd-diff-preview /usr/local/bin
argocd-diff-preview --help
Expand Down
9 changes: 8 additions & 1 deletion docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ FLAGS:
-h, --help
Prints help information

--keep-cluster-alive
Keep cluster alive after the tool finishes

-V, --version
Prints version information

Expand All @@ -22,6 +25,10 @@ OPTIONS:
Argo CD Helm Chart version
[env: ARGOCD_CHART_VERSION=]

--argocd-namespace <argocd-namespace>
Namespace to use for Argo CD
[env: ARGOCD_NAMESPACE=] [default: argocd]

-b, --base-branch <base-branch>
Base branch name
[env: BASE_BRANCH=] [default: main]
Expand Down Expand Up @@ -86,4 +93,4 @@ OPTIONS:
--timeout <timeout>
Set timeout for waiting for Applications to become 'OutOfSync'
[env: TIMEOUT=] [default: 180]
```
```
6 changes: 4 additions & 2 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ pull-repository:
cd base-branch && gh repo clone $(github_org)/$(gitops_repo) -- --depth=1 --branch "$(base_branch)" && cp -r $(gitops_repo)/. . && rm -rf .git && echo "*" > .gitignore && rm -rf $(gitops_repo) && cd -
cd target-branch && gh repo clone $(github_org)/$(gitops_repo) -- --depth=1 --branch "$(target_branch)" && cp -r $(gitops_repo)/. . && rm -rf .git && echo "*" > .gitignore && rm -rf $(gitops_repo) && cd -

docker-build:
docker build . -f $(docker_file) -t image

run-with-cargo: pull-repository
cargo run -- -b "$(base_branch)" \
-t "$(target_branch)" \
Expand All @@ -24,8 +27,7 @@ run-with-cargo: pull-repository
--argocd-namespace "$(argocd_namespace)" \
--files-changed="$(files_changed)"

run-with-docker: pull-repository
docker build . -f $(docker_file) -t image
run-with-docker: pull-repository docker-build
docker run \
--network=host \
-v ~/.kube:/root/.kube \
Expand Down
12 changes: 7 additions & 5 deletions src/argo_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,24 @@ use crate::{parsing::K8sResource, selector::Operator, Selector};
const ANNOTATION_WATCH_PATTERN: &str = "argocd-diff-preview/watch-pattern";
const ANNOTATION_IGNORE: &str = "argocd-diff-preview/ignore";

#[derive(PartialEq)]
#[derive(PartialEq, Clone)]
pub enum ApplicationKind {
Application,
ApplicationSet,
}

#[derive(Clone)]
pub struct ArgoResource {
pub file_name: String,
pub yaml: serde_yaml::Value,
pub kind: ApplicationKind,
pub name: String,
// Where the resource was found
pub file_name: String,
}

impl std::fmt::Display for ArgoResource {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", serde_yaml::to_string(&self.yaml).unwrap())
impl ArgoResource {
pub fn as_string(&self) -> Result<String, Box<dyn Error>> {
Ok(serde_yaml::to_string(&self.yaml)?)
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/argocd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,8 @@ impl ArgoCDInstallation {
}
}
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| {
Expand Down
5 changes: 4 additions & 1 deletion src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,10 @@ pub async fn get_resources(
);

// info about where it was stored
info!("💾 Resources stored in: '{}/<app_name>'", destination_folder);
info!(
"💾 Resources stored in: '{}/<app_name>'",
destination_folder
);

Ok(())
}
Expand Down
106 changes: 79 additions & 27 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use error::{CommandError, CommandOutput};
use log::{debug, error, info};
use regex::Regex;
use selector::Selector;
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
use std::str::FromStr;
Expand Down Expand Up @@ -128,13 +129,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 @@ -194,7 +194,7 @@ async fn run() -> Result<(), Box<dyn Error>> {

let cluster_tool = &opt.local_cluster_tool;

let repo_regex = Regex::new(r"^[a-zA-Z0-9-]+/[a-zA-Z0-9\._-]+$").unwrap();
let repo_regex = Regex::new(r"^[a-zA-Z0-9-]+/[a-zA-Z0-9\._-]+$")?;
if !repo_regex.is_match(repo) {
error!("❌ Invalid repository format. Please use OWNER/REPO");
return Err("Invalid repository format".into());
Expand Down Expand Up @@ -294,6 +294,9 @@ async fn run() -> Result<(), Box<dyn Error>> {
opt.ignore_invalid_watch_pattern,
)?;

let base_apps = unique_names(base_apps, &base_branch);
let target_apps = unique_names(target_apps, &target_branch);

let found_base_apps = !base_apps.is_empty();
let found_target_apps = !target_apps.is_empty();

Expand All @@ -305,21 +308,25 @@ async fn run() -> Result<(), Box<dyn Error>> {
return Ok(());
}

info!(
"💾 Writing applications from '{}' to ./{}",
base_branch.name,
base_branch.app_file()
);
utils::write_to_file(base_branch.app_file(), &applications_to_string(base_apps))?;
info!(
"💾 Writing applications from '{}' to ./{}",
target_branch.name,
target_branch.app_file()
);
utils::write_to_file(
target_branch.app_file(),
&applications_to_string(target_apps),
)?;
{
info!(
"💾 Writing {} applications from '{}' to ./{}",
base_apps.len(),
base_branch.name,
base_branch.app_file()
);
utils::write_to_file(base_branch.app_file(), &applications_to_string(base_apps)?)?;
info!(
"💾 Writing {} applications from '{}' to ./{}",
target_apps.len(),
target_branch.name,
target_branch.app_file()
);
utils::write_to_file(
target_branch.app_file(),
&applications_to_string(target_apps)?,
)?;
}

match cluster_tool {
ClusterTool::Kind => kind::create_cluster(&cluster_name)?,
Expand All @@ -334,7 +341,7 @@ async fn run() -> Result<(), Box<dyn Error>> {
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 @@ -404,6 +411,44 @@ fn clean_output_folder(output_folder: &str) -> Result<(), Box<dyn Error>> {
Ok(())
}

// Give new name for duplicate names in Vec<ArgoResource>
fn unique_names(apps: Vec<ArgoResource>, branch: &Branch) -> Vec<ArgoResource> {
let mut duplicate_names: HashMap<String, Vec<ArgoResource>> = HashMap::new();
apps.into_iter().for_each(|a| {
duplicate_names.entry(a.name.clone()).or_default().push(a);
});
let mut new_vec: Vec<ArgoResource> = vec![];
let mut duplicate_counter = 0;
for (name, apps) in duplicate_names {
if apps.len() > 1 {
duplicate_counter += 1;
debug!(
"Found {} duplicate applications with name: '{}'",
apps.len(),
name
);
let mut sorted_apps = apps.clone();
sorted_apps.sort_by_key(|a| a.as_string().unwrap_or_default());
for (i, app) in sorted_apps.into_iter().enumerate() {
let new_name = format!("{}-{}", name, i + 1);
let mut new_app = app.clone();
new_app.name.clone_from(&new_name);
new_app.yaml["metadata"]["name"] = serde_yaml::Value::String(new_name);
new_vec.push(new_app);
}
} else {
new_vec.push(apps[0].clone());
}
}
if duplicate_counter > 0 {
info!(
"🔍 Found {} duplicate applications names for branch: {}. Suffixing with -1, -2, -3, etc.",
duplicate_counter, branch.name
);
}
new_vec
}

fn cleanup_cluster(tool: ClusterTool, cluster_name: &str) {
match tool {
ClusterTool::Kind if kind::cluster_exists(cluster_name) => {
Expand Down Expand Up @@ -433,9 +478,9 @@ fn apply_manifest(file_name: &str) -> Result<CommandOutput, CommandOutput> {
})
}

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 @@ -445,18 +490,25 @@ 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),
}
}
}
}
Ok(count)
}

pub fn applications_to_string(applications: Vec<ArgoResource>) -> String {
applications
pub fn applications_to_string(applications: Vec<ArgoResource>) -> Result<String, Box<dyn Error>> {
let output = applications
.iter()
.map(|a| a.to_string())
.collect::<Vec<String>>()
.join("---\n")
.map(|a| {
a.as_string().inspect_err(|e| {
error!(
"❌ Failed to convert application '{}' (path: {}) to valid YAML: {}",
a.name, a.file_name, e
);
})
})
.collect::<Result<Vec<String>, Box<dyn Error>>>()?;
Ok(output.join("---\n"))
}
Loading