Skip to content

Commit

Permalink
Add feature to skip upgrade if no changes detected (#379)
Browse files Browse the repository at this point in the history
* Implement no-upgrade-if-unchanged based on the --detailed-exit-code from
helm-diff

* Revert config.yaml

* Add the comma causing the linting error from catgo fmt

* Remove the trailing { for cargo linting

* Align the comment for cargo linting

* Align the comment for  cargo linting

* Code formatting for cargo linting

* Add the ',' for cargo linting

* Remainder of cargo linting

* Resolve CR

* Add -b --bypass-skip-upgrade-on-no-changes #clap flag

* Based on result of char cargo test, change chars to strings

* "Rust auto-linting, revert config.yaml context"

* Implement --yes || -y for -b Upgrade suboption for CI environments

* Implement -b -y || -n (--bypass-skip-upgrade-on-no-changes --no is the
implicit case

* Cargo linting fix

* Update documentation

* Fix some clippy errors introduced - partial reversion in Main function

* Add the UpgradeControl enum

* Suppress exit_code 2

* Suppress the single exit_code warning for unused variables in text.rs

* Remove UI prompt intererfered with TUI. Alter the way specific debug
messages are written to avoid interfering with TUI mode
  • Loading branch information
tylermaginnis authored Aug 27, 2024
1 parent c6837fc commit 2f66a84
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 25 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,35 @@ Only do this if you really want to deploy:
cargo run -- --vdir ./example/helm-values upgrade
```

## Additional Options

### Bypass Upgrade on No Changes

The `-b` or `--bypass-upgrade-on-no-changes` option allows you to bypass the upgrade process if no changes are detected. This can be useful to save time and resources when you are confident that no changes have been made to the release values.

### Suboptions

#### Yes

The `-y` or `--yes` suboption can be used in conjunction with the `--bypass-upgrade-on-no-changes` option to automatically proceed with the upgrade even if no changes are detected. This is useful for automated scripts where manual intervention is not possible.

#### No

The `-n` or `--no` suboption can be used in conjunction with the `--bypass-upgrade-on-no-changes` option to automatically skip the upgrade if no changes are detected. This is useful when you want to ensure that upgrades are only performed when necessary without manual intervention.

Example usage:

```sh
# Bypass upgrade on no changes and automatically proceed with the upgrade
cargo run -- --vdir ./example/helm-values upgrade --bypass-upgrade-on-no-changes --yes
cargo run -- --vdir ./example/helm-values upgrade -b -y


# Bypass upgrade on no changes and automatically skip the upgrade
cargo run -- --vdir ./example/helm-values upgrade --bypass-upgrade-on-no-changes --no
cargo run -- --vdir ./example/helm-values upgrade -b -y
```

## Config layout

You can have zero or more environments.
Expand Down
4 changes: 3 additions & 1 deletion src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub struct CommandSuccess {
pub stdout: String,
pub stderr: String,
pub duration: Duration,
pub exit_code: i32, // This field stores the exit code of the command to determine if it was successful or not, and whether or not there were any diffs.
}

impl CommandSuccess {
Expand Down Expand Up @@ -176,7 +177,7 @@ impl CommandLine {
let kind = match output {
Err(err) => Err(CommandErrorKind::FailedToStart { err }),
Ok(output) => {
if output.status.success() {
if output.status.success() || exit_code == 2 {
Ok(())
} else {
Err(CommandErrorKind::BadExitCode {})
Expand All @@ -189,6 +190,7 @@ impl CommandLine {
cmd: self.clone(),
stdout,
stderr,
exit_code,
duration,
}),
Err(kind) => Err(CommandError {
Expand Down
64 changes: 56 additions & 8 deletions src/helm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ pub struct HelmResult {
pub installation: Arc<Installation>,
pub result: CommandResult,
pub command: Command,
// The exit code contains information about whether there were any diffs.
pub exit_code: i32,
}

impl HelmResult {
Expand All @@ -130,6 +132,7 @@ impl HelmResult {
installation: installation.clone(),
result,
command,
exit_code: 0,
}
}

Expand Down Expand Up @@ -366,20 +369,35 @@ pub async fn template(
}
}

// The DiffResult struct is used to store the exit code of the diff command.
pub enum DiffResult {
NoChanges,
Changes,
Errors,
Unknown,
}
async fn log_debug_message(tx: &MultiOutput, message: &str) {
tx.send(Message::Log(log!(Level::DEBUG, message))).await;
}

/// Run the helm diff command.
pub async fn diff(
installation: &Arc<Installation>,
helm_repos: &HelmReposLock,
tx: &MultiOutput,
) -> Result<()> {
) -> Result<DiffResult> {
// Retrieve the helm chart for the given installation.
let chart = helm_repos.get_helm_chart(&installation.chart_reference)?;
// Get the chart arguments from the chart.
let (chart, mut chart_args) = get_args_from_chart(&chart);

// Construct the arguments for the helm diff command.
let mut args = vec![
"diff".into(),
"upgrade".into(),
installation.name.clone().into(),
chart,
"--detailed-exitcode".into(), // This flag ensures that the exit code will indicate if there are changes or errors.
"--context=3".into(),
"--no-color".into(),
"--allow-unreleased".into(),
Expand All @@ -389,22 +407,52 @@ pub async fn diff(
installation.clone().context.clone().into(),
];

// Append additional arguments from the installation configuration.
args.append(&mut add_values_files(installation));
args.append(&mut chart_args);
args.append(&mut get_template_parameters(installation));

// Create a CommandLine instance with the helm path and the constructed arguments.
let command_line = CommandLine(helm_path(), args);
// Run the command and await the result.
let result = command_line.run().await;
let has_errors = result.is_err();
let i_result = HelmResult::from_result(installation, result, Command::Diff);
// Check if there were any errors in the command execution.
let _has_errors = result.is_err();
// Create a HelmResult instance from the command result.
let mut i_result = HelmResult::from_result(installation, result, Command::Diff);

// Evaluate the detailed exit code - any non-zero exit code indicates changes (1) or errors (2).
let diff_result = if let Ok(command_success) = &i_result.result {
i_result.exit_code = command_success.exit_code;
match command_success.exit_code {
0 => {
log_debug_message(tx, "No changes detected!").await; // Exit code 0 indicates no changes.
DiffResult::NoChanges
}
1 => {
log_debug_message(tx, "Errors encountered!").await; // Exit code 1 indicates errors.
DiffResult::Errors
}
2 => {
log_debug_message(tx, "Changes detected!").await; // Exit code 2 indicates changes.
DiffResult::Changes
}
_ => {
log_debug_message(tx, "Unknown exit code").await; // Any other exit code is considered unknown.
DiffResult::Unknown
}
}
} else {
log_debug_message(tx, "Other exception encountered").await; // If the command result is an error, return Unknown.
DiffResult::Unknown
};

// Wrap the HelmResult in an Arc and send it via the MultiOutput channel.
let i_result = Arc::new(i_result);
tx.send(Message::InstallationResult(i_result)).await;

if has_errors {
Err(anyhow::anyhow!("diff operation failed"))
} else {
Ok(())
}
// Return the diff result.
Ok(diff_result)
}

/// Run the helm upgrade command.
Expand Down
Loading

0 comments on commit 2f66a84

Please sign in to comment.