sops-batch 0.4.0

SOPS encryption / decryption batch tool
//! SOPS encryption / decryption batch tool
//!
//! A wrapper around sops written in Rust, that allows bulk encryption, decryption or updating of keys, based on a configuration file.
//!
//! # Configuration / Installation
//! 1. Create a `.sops.yaml` file that specifies what keys to use:
//! ```yaml
//! creation_rules:
//!   - key_groups:
//!       - pgp:
//!           - "XXXXX"
//!       - age:
//!           - "XXXXX"
//! ```
//! 2. Create a `.sops-batch.toml` file that specifies what files to act on:
//! ```toml
//! files = [
//!   "foo.yaml",
//!   "bar.json"
//! ]
//! ```
//! 3. [Download](https://gitlab.com/LordGaav/sops-batch/-/releases) and place `sops-batch` in your `$PATH`.
//!     * Linux users download the `*-linux-gnu` version, or the `*-linux-musl` version for a static binary.
//!     * MacOS users download the `*-apple-darwin` version. This should also work on M1.
//!     * Windows users download the `*-windows-gnu` version.
//! 4. Use it: `sops-batch -h`.
//!
//! # Examples
//!
//! See the [examples folder](examples/README.md).
//!
//! # Update
//!
//! `sops-batch` includes a self-update feature, introduced in version `0.3.0`:
//!
//! ```sh
//! $ sops-batch self-update
//! Checking target-arch... x86_64-unknown-linux-gnu
//! Checking current version... v0.0.0
//! Checking latest released version... v0.3.0
//! New release found! v0.0.0 --> v0.3.0
//! New release is *NOT* compatible
//!
//! sops-batch release status:
//!   * Current exe: ".../sops-batch"
//!   * New exe release: "sops-batch_0.3.0_x86_64-unknown-linux-gnu"
//!   * New exe download url: "https://gitlab.com/api/v4/projects/36884529/packages/generic/0.3.0/x86_64-unknown-linux-gnu/sops-batch"
//!
//! The new release will be downloaded/extracted and the existing binary will be replaced.
//! Do you want to continue? [Y/n] y
//! Downloading...
//! [00:00:00] [========================================] 4.82MiB/4.82MiB (0s) Done
//! Extracting archive... Done
//! Replacing binary file... Done
//! Binary updated to version 0.3.0.
//! ```

use clap::{IntoApp, Parser, Subcommand};
use std::path::PathBuf;

mod common;
mod decrypt;
mod encrypt;
mod self_update;
mod updatekeys;

#[derive(Parser)]
#[clap(version, about, long_about = include_str!("../README.md"))]
/// Automatically encrypt, decrypt or updatekeys a set of files using sops.
struct Args {
    #[clap(subcommand)]
    command: Commands,

    /// Enable verbose output
    #[clap(short, long, global = true, parse(from_flag))]
    verbose: bool,

    /// Name of the sops binary
    #[clap(long, global = true, default_value = "sops")]
    sops_binary: String,
}

#[derive(Subcommand)]
enum Commands {
    /// Decrypt files.
    Decrypt {
        /// Configuration file that contains files to work on
        #[clap(short, long, env)]
        files_config: PathBuf,

        /// Allow decryption failures
        #[clap(long, parse(from_flag), env)]
        allow_decryption_fail: bool,
    },
    /// Encrypt files.
    Encrypt {
        /// Configuration file that contains files to work on
        #[clap(short, long, env)]
        files_config: PathBuf,
    },
    /// Update keys.
    UpdateKeys {
        /// Configuration file that contains files to work on
        #[clap(short, long, env)]
        files_config: PathBuf,
    },
    /// Generate shell completions.
    Completions {
        /// Shell to generate completions for
        #[clap(arg_enum)]
        shell: clap_complete_command::Shell,
    },
    /// Automatically update to the latest version.
    SelfUpdate {},
}

fn main() {
    let args = Args::parse();

    let sops_bin = match common::find_sops(args.sops_binary.as_str()) {
        Ok(b) => Some(b),
        Err(err) => {
            eprintln!("{}", err);
            None
        }
    };

    let result = match &args.command {
        Commands::Decrypt {
            files_config,
            allow_decryption_fail,
        } => {
            if sops_bin.is_none() {
                std::process::exit(exitcode::UNAVAILABLE);
            }
            if args.verbose {
                eprintln!(
                    "Decrypt command called, files cfg path {:?}, allow decrypt fail {:?}",
                    files_config, allow_decryption_fail
                );
            }
            decrypt::decrypt(
                files_config.as_path(),
                sops_bin.unwrap().as_path(),
                *allow_decryption_fail,
                args.verbose,
            )
        }
        Commands::Encrypt { files_config } => {
            if sops_bin.is_none() {
                std::process::exit(exitcode::UNAVAILABLE);
            }
            if args.verbose {
                eprintln!("Encrypt command called, files cfg path {:?}", files_config);
            }
            encrypt::encrypt(
                files_config.as_path(),
                sops_bin.unwrap().as_path(),
                args.verbose,
            )
        }
        Commands::UpdateKeys { files_config } => {
            if sops_bin.is_none() {
                std::process::exit(exitcode::UNAVAILABLE);
            }
            if args.verbose {
                eprintln!(
                    "Update keys command called, files cfg path {:?}",
                    files_config
                );
            }
            updatekeys::update_keys(
                files_config.as_path(),
                sops_bin.unwrap().as_path(),
                args.verbose,
            )
        }
        Commands::Completions { shell } => {
            shell.generate(&mut Args::command(), &mut std::io::stdout());
            if args.verbose {
                eprintln!("shell completions generated");
            };
            Ok(())
        }
        Commands::SelfUpdate {} => match self_update::self_update() {
            Ok(ok) => {
                eprintln!("{}", ok);
                Ok(())
            }
            Err(err) => Err(err.to_string()),
        },
    };

    match result {
        Ok(_) => {}
        Err(err) => {
            eprintln!("Command failed: {}", err);
            std::process::exit(exitcode::TEMPFAIL);
        }
    };
}