use clap::{Args, Parser, Subcommand};
use uuid::Uuid;
use crate::output::Format;
#[derive(Debug, Parser)]
#[command(
name = "siam",
version,
about = "Silicon IAM from the command line",
long_about = "Silicon IAM from the command line.\n\n\
Sign in once with `siam login`; the session is stored under \
~/.silicon-iam/ and renewed automatically. Most commands act on an \
organization: pass --org, or set a default with \
`siam config set org <handle>`.",
propagate_version = true,
disable_help_subcommand = false
)]
pub struct Cli {
#[command(flatten)]
pub global: Global,
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Args)]
pub struct Global {
#[arg(long, global = true, env = "SILICON_IAM_URL")]
pub url: Option<String>,
#[arg(long, global = true, env = "SILICON_IAM_PROFILE")]
pub profile: Option<String>,
#[arg(long, global = true, env = "SILICON_IAM_ORG")]
pub org: Option<String>,
#[arg(long, global = true, env = "SILICON_IAM_ENVIRONMENT")]
pub environment: Option<String>,
#[arg(long, global = true)]
pub step_up: Option<String>,
#[arg(long, short = 'o', global = true, value_enum, default_value_t = Format::Text)]
pub output: Format,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Login(LoginArgs),
Logout,
Whoami,
Signup(SignupArgs),
Commands,
#[command(subcommand)]
Org(OrgCommand),
#[command(subcommand)]
Member(MemberCommand),
#[command(subcommand)]
Invite(InviteCommand),
#[command(subcommand)]
Tag(TagCommand),
#[command(subcommand)]
Trust(TrustCommand),
#[command(subcommand)]
Approval(ApprovalCommand),
#[command(subcommand)]
Silicon(SiliconCommand),
#[command(subcommand)]
App(AppCommand),
#[command(subcommand)]
Env(EnvCommand),
#[command(subcommand)]
Session(SessionCommand),
#[command(subcommand)]
Config(ConfigCommand),
#[command(subcommand)]
System(SystemCommand),
}
#[derive(Debug, Args)]
pub struct LoginArgs {
#[arg(long, group = "identity")]
pub email: Option<String>,
#[arg(long, group = "identity")]
pub phone: Option<String>,
#[arg(long, group = "identity")]
pub carbon_id: Option<String>,
#[arg(long)]
pub code: Option<String>,
}
#[derive(Debug, Args)]
pub struct SignupArgs {
#[arg(long)]
pub email: String,
#[arg(long)]
pub phone: String,
#[arg(long)]
pub carbon_id: String,
#[arg(long)]
pub display_name: Option<String>,
#[arg(long)]
pub timezone: Option<String>,
}
#[derive(Debug, Subcommand)]
pub enum OrgCommand {
List(PageArgs),
Create {
handle: String,
#[arg(long)]
name: String,
#[arg(long)]
description: Option<String>,
},
Show {
handle: Option<String>,
},
Update {
handle: Option<String>,
#[arg(long)]
name: Option<String>,
#[arg(long)]
description: Option<String>,
#[arg(long)]
join_method: Option<String>,
},
Available {
handle: String,
},
Transfer {
membership_id: Uuid,
#[arg(long)]
org: Option<String>,
},
}
#[derive(Debug, Subcommand)]
pub enum MemberCommand {
List {
#[arg(long, value_name = "carbon|silicon")]
principal_type: Option<String>,
#[arg(long)]
tag: Option<Uuid>,
#[arg(long)]
status: Option<String>,
#[command(flatten)]
page: PageArgs,
},
Show {
membership_id: Uuid,
},
Authorization {
membership_id: Uuid,
},
Update {
membership_id: Uuid,
#[arg(long)]
reports_to: Option<Uuid>,
#[arg(long)]
profile_photo: Option<String>,
},
Remove {
membership_id: Uuid,
#[arg(long)]
reassign_reports_to: Option<Uuid>,
},
Promote {
membership_id: Uuid,
},
Demote {
membership_id: Uuid,
},
Capabilities {
membership_id: Uuid,
#[arg(long = "capability", value_name = "CAPABILITY")]
capabilities: Vec<String>,
},
Directory {
#[arg(long)]
fields: Option<String>,
#[command(flatten)]
page: PageArgs,
},
Self_ {
#[arg(long)]
fields: Option<String>,
},
}
#[derive(Debug, Subcommand)]
pub enum InviteCommand {
List {
#[arg(long)]
status: Option<String>,
#[command(flatten)]
page: PageArgs,
},
Create {
#[arg(long, group = "identity")]
carbon_id: Option<String>,
#[arg(long, group = "identity")]
email: Option<String>,
#[arg(long)]
job_role: String,
#[arg(long, default_value = "internal")]
boundary: String,
#[arg(long, default_value = "not_trusted")]
level: String,
},
Show {
invite_id: Uuid,
},
Revoke {
invite_id: Uuid,
},
Code {
email: String,
},
Accept {
invite_id: Uuid,
#[arg(long)]
code: String,
},
}
#[derive(Debug, Subcommand)]
pub enum TagCommand {
List(PageArgs),
Create {
name: String,
},
Show {
tag_id: Uuid,
},
Rename {
tag_id: Uuid,
name: String,
},
Delete {
tag_id: Uuid,
},
Members {
tag_id: Uuid,
#[command(flatten)]
page: PageArgs,
},
}
#[derive(Debug, Subcommand)]
pub enum TrustCommand {
Default,
SetDefault {
#[arg(long)]
boundary: String,
#[arg(long)]
level: String,
},
List(PageArgs),
Create {
#[arg(long, group = "subject")]
subject_tag: Option<Uuid>,
#[arg(long, group = "subject")]
subject_membership: Option<Uuid>,
#[arg(long, group = "target")]
target_tag: Option<Uuid>,
#[arg(long, group = "target")]
target_membership: Option<Uuid>,
#[arg(long)]
boundary: String,
#[arg(long)]
level: String,
},
Show {
rule_id: Uuid,
},
Update {
rule_id: Uuid,
#[arg(long)]
boundary: String,
#[arg(long)]
level: String,
},
Delete {
rule_id: Uuid,
},
Evaluate {
#[arg(long)]
subject: Uuid,
#[arg(long)]
target: Uuid,
},
}
#[derive(Debug, Subcommand)]
pub enum ApprovalCommand {
List {
#[arg(long)]
status: Option<String>,
#[arg(long)]
kind: Option<String>,
#[arg(long)]
mine: bool,
#[command(flatten)]
page: PageArgs,
},
Show {
request_id: Uuid,
},
Decide {
request_id: Uuid,
#[arg(long)]
decision: String,
#[arg(long)]
reason: Option<String>,
},
RequestRole {
#[arg(long)]
membership_id: Uuid,
#[arg(long)]
job_role: String,
},
RequestTags {
#[arg(long)]
membership_id: Uuid,
#[arg(long = "add", value_name = "TAG_ID")]
add: Vec<Uuid>,
#[arg(long = "remove", value_name = "TAG_ID")]
remove: Vec<Uuid>,
},
SetRole {
membership_id: Uuid,
job_role: String,
},
SetTags {
membership_id: Uuid,
#[arg(long = "tag", value_name = "TAG_ID")]
tags: Vec<Uuid>,
},
RoleHistory {
membership_id: Uuid,
#[command(flatten)]
page: PageArgs,
},
TagHistory {
membership_id: Uuid,
#[command(flatten)]
page: PageArgs,
},
}
#[derive(Debug, Subcommand)]
pub enum SiliconCommand {
List {
#[arg(long)]
tag: Option<Uuid>,
#[command(flatten)]
page: PageArgs,
},
Create {
handle: String,
#[arg(long)]
job_role: String,
#[arg(long)]
display_name: Option<String>,
#[arg(long)]
reports_to: Option<Uuid>,
#[arg(long = "tag", value_name = "TAG_ID")]
tags: Vec<Uuid>,
},
Show {
silicon_id: String,
},
Update {
silicon_id: String,
#[arg(long)]
display_name: Option<String>,
#[arg(long)]
reports_to: Option<Uuid>,
},
Remove {
silicon_id: String,
#[arg(long)]
reassign_reports_to: Option<Uuid>,
},
RotateRequest {
silicon_id: String,
},
RotateComplete {
silicon_id: String,
request_id: Uuid,
},
Webhook {
silicon_id: String,
},
SetWebhook {
silicon_id: String,
#[arg(long)]
url: String,
},
DeleteWebhook {
silicon_id: String,
},
Subscription {
silicon_id: String,
},
SetSubscription {
silicon_id: String,
#[arg(long, default_value = "all")]
mode: String,
#[arg(long = "topic", value_name = "TOPIC")]
topics: Vec<String>,
#[arg(long = "tag", value_name = "TAG_ID")]
tags: Vec<Uuid>,
},
DeleteSubscription {
silicon_id: String,
},
DeadLetters {
silicon_id: String,
#[command(flatten)]
page: PageArgs,
},
Replay {
silicon_id: String,
#[arg(long = "delivery", value_name = "DELIVERY_ID")]
deliveries: Vec<Uuid>,
},
}
#[derive(Debug, Subcommand)]
pub enum AppCommand {
List {
#[arg(long)]
status: Option<String>,
#[command(flatten)]
page: PageArgs,
},
Create {
app_id: String,
#[arg(long)]
name: String,
#[arg(long)]
org: Option<String>,
#[arg(long)]
webhook_url: String,
#[arg(long = "redirect-uri", value_name = "URI")]
redirect_uri: Vec<String>,
#[arg(long = "scope", value_name = "SCOPE")]
scopes: Vec<String>,
},
Show {
app_id: String,
},
Update {
app_id: String,
#[arg(long)]
name: Option<String>,
},
RotateSecret {
app_id: String,
},
Redirects {
app_id: String,
#[command(flatten)]
page: PageArgs,
},
AddRedirect {
app_id: String,
uri: String,
},
RetireRedirect {
app_id: String,
redirect_uri_id: Uuid,
},
Webhook {
app_id: String,
},
SetWebhook {
app_id: String,
#[arg(long)]
url: String,
},
DeadLetters {
app_id: String,
#[command(flatten)]
page: PageArgs,
},
Replay {
app_id: String,
#[arg(long = "delivery", value_name = "DELIVERY_ID")]
deliveries: Vec<Uuid>,
},
History {
app_id: String,
#[command(flatten)]
page: PageArgs,
},
}
#[derive(Debug, Subcommand)]
pub enum EnvCommand {
List {
#[arg(long)]
status: Option<String>,
#[command(flatten)]
page: PageArgs,
},
Create {
name: String,
#[arg(long)]
description: Option<String>,
},
Show {
environment_id: Uuid,
},
Update {
environment_id: Uuid,
#[arg(long)]
name: Option<String>,
#[arg(long)]
description: Option<String>,
},
Delete {
environment_id: Uuid,
},
Restore {
environment_id: Uuid,
},
Key {
environment_id: Uuid,
},
RotateKey {
environment_id: Uuid,
},
Clean {
environment_id: Option<Uuid>,
},
Current,
}
#[derive(Debug, Subcommand)]
pub enum SessionCommand {
List(PageArgs),
Revoke {
session_id: Uuid,
},
History(PageArgs),
}
#[derive(Debug, Subcommand)]
pub enum ConfigCommand {
Show,
Profiles,
Set {
key: String,
value: String,
},
Unset {
key: String,
},
Use {
profile: String,
},
}
#[derive(Debug, Subcommand)]
pub enum SystemCommand {
Version,
Health,
}
#[derive(Debug, Args, Default)]
pub struct PageArgs {
#[arg(long)]
pub cursor: Option<String>,
#[arg(long)]
pub limit: Option<u16>,
}
impl PageArgs {
#[must_use]
pub fn paging(&self) -> silicon_iam_client::Paging {
let mut paging = silicon_iam_client::Paging::new();
if let Some(cursor) = &self.cursor {
paging = paging.after(cursor.clone());
}
if let Some(limit) = self.limit {
paging = paging.limit(limit);
}
paging
}
}
#[cfg(test)]
mod tests {
use clap::CommandFactory as _;
use super::Cli;
#[test]
fn the_grammar_is_internally_consistent() {
Cli::command().debug_assert();
}
#[test]
fn global_flags_are_accepted_after_the_command_too() {
use clap::Parser as _;
let parsed = Cli::try_parse_from(["siam", "tag", "list", "--org", "acme"]);
assert!(parsed.is_ok(), "{parsed:?}");
let Ok(cli) = parsed else { return };
assert_eq!(cli.global.org.as_deref(), Some("acme"));
}
#[test]
fn login_admits_exactly_one_identity() {
use clap::Parser as _;
assert!(Cli::try_parse_from(["siam", "login", "--email", "a@b.test"]).is_ok());
assert!(
Cli::try_parse_from([
"siam",
"login",
"--email",
"a@b.test",
"--carbon-id",
"someone"
])
.is_err()
);
}
}