Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SM-1097] Add support for manpage generation #175

Merged
merged 7 commits into from
May 1, 2024
Merged
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
32 changes: 32 additions & 0 deletions .github/workflows/build-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -435,3 +435,35 @@ jobs:
name: THIRDPARTY.html
path: ./crates/bws/THIRDPARTY.html
if-no-files-found: error

manpages:
name: Generate manpages
runs-on: ubuntu-22.04
needs:
- setup
steps:
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

- name: Install rust
uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable
with:
toolchain: stable

- name: Cache cargo registry
uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3
with:
key: cargo-cli-manpage

- name: Generate manpages
run: |
cargo check -p bws --message-format json > build.json
OUT_DIR=$(jq -r --slurp '.[] | select (.reason == "build-script-executed") | select(.package_id|contains("crates/bws")) .out_dir' build.json)
mv $OUT_DIR/manpages .

- name: Upload artifact
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: manpages
path: ./manpages/*
if-no-files-found: error
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions crates/bws/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ tokio = { version = "1.36.0", features = ["rt-multi-thread", "macros"] }
toml = "0.8.10"
uuid = { version = "^1.7.0", features = ["serde"] }

[build-dependencies]
bitwarden-cli = { workspace = true }
clap = { version = "4.5.1", features = ["derive", "string"] }
clap_complete = "4.5.0"
clap_mangen = "0.2.20"
uuid = { version = "^1.7.0" }

[dev-dependencies]
tempfile = "3.10.0"

Expand Down
12 changes: 12 additions & 0 deletions crates/bws/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,15 @@ To use a configuration file, utilize docker
```bash
docker run --rm -it -v "$HOME"/.bws:/home/app/.bws bitwarden/bws --help
```

## How to build manpages

The manpages get built during compilation of the `bws` crate through the use of a build script. The
output path of this build script can be located as follows:

```
MANPAGES_DIR=$(cargo build -p bws --message-format json | jq -r --slurp '.[] | select (.reason == "build-script-executed") | select(.package_id|contains("crates/bws")) .out_dir')
```

After running the provided commands, the built manpages should be located in
`$MANPAGES_DIR/manpages`
14 changes: 14 additions & 0 deletions crates/bws/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
include!("src/cli.rs");

fn main() -> Result<(), std::io::Error> {
use std::{env, fs, path::Path};

let out_dir = env::var_os("OUT_DIR").expect("OUT_DIR exists");
let path = Path::new(&out_dir).join("manpages");
fs::create_dir_all(&path).expect("OUT_DIR is writable");

let cmd = <Cli as clap::CommandFactory>::command();
clap_mangen::generate_to(cmd, &path)?;

Ok(())
}
228 changes: 228 additions & 0 deletions crates/bws/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
use std::path::PathBuf;

use bitwarden_cli::Color;
use clap::{ArgGroup, Parser, Subcommand, ValueEnum};
use clap_complete::Shell;
use uuid::Uuid;

pub(crate) const ACCESS_TOKEN_KEY_VAR_NAME: &str = "BWS_ACCESS_TOKEN";
pub(crate) const CONFIG_FILE_KEY_VAR_NAME: &str = "BWS_CONFIG_FILE";
pub(crate) const PROFILE_KEY_VAR_NAME: &str = "BWS_PROFILE";
pub(crate) const SERVER_URL_KEY_VAR_NAME: &str = "BWS_SERVER_URL";

pub(crate) const DEFAULT_CONFIG_FILENAME: &str = "config";
pub(crate) const DEFAULT_CONFIG_DIRECTORY: &str = ".bws";

#[allow(non_camel_case_types)]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub(crate) enum ProfileKey {
server_base,
server_api,
server_identity,
state_file_dir,
}

#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
#[allow(clippy::upper_case_acronyms)]
pub(crate) enum Output {
JSON,
YAML,
Env,
Table,
TSV,
None,
}

#[derive(Parser, Debug)]
#[command(name = "bws", version, about = "Bitwarden Secrets CLI", long_about = None)]
pub(crate) struct Cli {
// Optional as a workaround for https://github.com/clap-rs/clap/issues/3572
#[command(subcommand)]
pub(crate) command: Option<Commands>,

#[arg(short = 'o', long, global = true, value_enum, default_value_t = Output::JSON, help="Output format")]
pub(crate) output: Output,

Check warning on line 44 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L43-L44

Added lines #L43 - L44 were not covered by tests

#[arg(short = 'c', long, global = true, value_enum, default_value_t = Color::Auto, help="Use colors in the output")]
pub(crate) color: Color,

Check warning on line 47 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L46-L47

Added lines #L46 - L47 were not covered by tests

#[arg(short = 't', long, global = true, env = ACCESS_TOKEN_KEY_VAR_NAME, hide_env_values = true, help="Specify access token for the service account")]
pub(crate) access_token: Option<String>,

#[arg(
short = 'f',
long,
global = true,
env = CONFIG_FILE_KEY_VAR_NAME,
help = format!("[default: ~/{}/{}] Config file to use", DEFAULT_CONFIG_DIRECTORY, DEFAULT_CONFIG_FILENAME)
)]
pub(crate) config_file: Option<PathBuf>,

#[arg(short = 'p', long, global = true, env = PROFILE_KEY_VAR_NAME, help="Profile to use from the config file")]
pub(crate) profile: Option<String>,

#[arg(short = 'u', long, global = true, env = SERVER_URL_KEY_VAR_NAME, help="Override the server URL from the config file")]
pub(crate) server_url: Option<String>,
}

#[derive(Subcommand, Debug)]
pub(crate) enum Commands {
#[command(long_about = "Configure the CLI", arg_required_else_help(true))]
Config {
name: Option<ProfileKey>,
value: Option<String>,

#[arg(short = 'd', long)]
delete: bool,

Check warning on line 76 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L76

Added line #L76 was not covered by tests
},

#[command(long_about = "Generate shell completion files")]
Completions { shell: Option<Shell> },

#[command(long_about = "Commands available on Projects")]
Project {
#[command(subcommand)]
cmd: ProjectCommand,
},
#[command(long_about = "Commands available on Secrets")]
Secret {
#[command(subcommand)]
cmd: SecretCommand,
},
#[command(long_about = "Create a single item (deprecated)", hide(true))]
Create {
#[command(subcommand)]
cmd: CreateCommand,
},
#[command(long_about = "Delete one or more items (deprecated)", hide(true))]
Delete {
#[command(subcommand)]
cmd: DeleteCommand,
},
#[command(long_about = "Edit a single item (deprecated)", hide(true))]
Edit {
#[command(subcommand)]
cmd: EditCommand,
},
#[command(long_about = "Retrieve a single item (deprecated)", hide(true))]
Get {
#[command(subcommand)]
cmd: GetCommand,
},
#[command(long_about = "List items (deprecated)", hide(true))]
List {
#[command(subcommand)]
cmd: ListCommand,
},
}

#[derive(Subcommand, Debug)]
pub(crate) enum SecretCommand {
Create {
key: String,
value: String,

Check warning on line 123 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L122-L123

Added lines #L122 - L123 were not covered by tests

#[arg(help = "The ID of the project this secret will be added to")]
project_id: Uuid,

Check warning on line 126 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L126

Added line #L126 was not covered by tests

#[arg(long, help = "An optional note to add to the secret")]
note: Option<String>,
},
Delete {
secret_ids: Vec<Uuid>,

Check warning on line 132 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L132

Added line #L132 was not covered by tests
},
#[clap(group = ArgGroup::new("edit_field").required(true).multiple(true))]
Edit {
secret_id: Uuid,

Check warning on line 136 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L136

Added line #L136 was not covered by tests
#[arg(long, group = "edit_field")]
key: Option<String>,
#[arg(long, group = "edit_field")]
value: Option<String>,
#[arg(long, group = "edit_field")]
note: Option<String>,
#[arg(long, group = "edit_field")]
project_id: Option<Uuid>,
},
Get {
secret_id: Uuid,

Check warning on line 147 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L147

Added line #L147 was not covered by tests
},
List {
project_id: Option<Uuid>,
},
}

#[derive(Subcommand, Debug)]
pub(crate) enum ProjectCommand {
Create {
name: String,

Check warning on line 157 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L157

Added line #L157 was not covered by tests
},
Delete {
project_ids: Vec<Uuid>,

Check warning on line 160 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L160

Added line #L160 was not covered by tests
},
Edit {
project_id: Uuid,

Check warning on line 163 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L163

Added line #L163 was not covered by tests
#[arg(long, group = "edit_field")]
name: String,

Check warning on line 165 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L165

Added line #L165 was not covered by tests
},
Get {
project_id: Uuid,

Check warning on line 168 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L168

Added line #L168 was not covered by tests
},
List,
}

#[derive(Subcommand, Debug)]
pub(crate) enum ListCommand {
Projects,
Secrets { project_id: Option<Uuid> },
}

#[derive(Subcommand, Debug)]
pub(crate) enum GetCommand {
Project { project_id: Uuid },
Secret { secret_id: Uuid },

Check warning on line 182 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L181-L182

Added lines #L181 - L182 were not covered by tests
}

#[derive(Subcommand, Debug)]
pub(crate) enum CreateCommand {
Project {
name: String,

Check warning on line 188 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L188

Added line #L188 was not covered by tests
},
Secret {
key: String,
value: String,

Check warning on line 192 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L191-L192

Added lines #L191 - L192 were not covered by tests

#[arg(long, help = "An optional note to add to the secret")]
note: Option<String>,

#[arg(long, help = "The ID of the project this secret will be added to")]
project_id: Uuid,

Check warning on line 198 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L198

Added line #L198 was not covered by tests
},
}

#[derive(Subcommand, Debug)]
pub(crate) enum EditCommand {
#[clap(group = ArgGroup::new("edit_field").required(true).multiple(true))]
Project {
project_id: Uuid,

Check warning on line 206 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L206

Added line #L206 was not covered by tests
#[arg(long, group = "edit_field")]
name: String,

Check warning on line 208 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L208

Added line #L208 was not covered by tests
},
#[clap(group = ArgGroup::new("edit_field").required(true).multiple(true))]
Secret {
secret_id: Uuid,

Check warning on line 212 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L212

Added line #L212 was not covered by tests
#[arg(long, group = "edit_field")]
key: Option<String>,
#[arg(long, group = "edit_field")]
value: Option<String>,
#[arg(long, group = "edit_field")]
note: Option<String>,
#[arg(long, group = "edit_field")]
project_id: Option<Uuid>,
},
}

#[derive(Subcommand, Debug)]
pub(crate) enum DeleteCommand {
Project { project_ids: Vec<Uuid> },
Secret { secret_ids: Vec<Uuid> },

Check warning on line 227 in crates/bws/src/cli.rs

View check run for this annotation

Codecov / codecov/patch

crates/bws/src/cli.rs#L226-L227

Added lines #L226 - L227 were not covered by tests
}
Loading
Loading