use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use vantage_aws::models::ecs::{
clusters_table, services_table, task_definitions_table, tasks_table,
};
use vantage_aws::models::iam::{
Role, User, access_keys_table, groups_table as iam_groups_table, instance_profiles_table,
policies_table, roles_table, users_table,
};
use vantage_aws::models::logs::{events_table, groups_table, streams_table};
use vantage_aws::{AwsAccount, eq};
use vantage_cli_util::{print_table, render_records};
use vantage_dataset::prelude::{ReadableDataSet, ReadableValueSet};
const TRAVERSE_GROUP: &str = "/ecs/ba-nginx";
#[derive(Parser)]
#[command(
name = "vantage-aws-cli",
about = "vantage-aws CloudWatch + ECS + IAM demo"
)]
struct Cli {
#[arg(long, global = true)]
region: Option<String>,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
LogGroups {
#[arg(long)]
prefix: Option<String>,
},
LogStreams { log_group_name: String },
LogEvents { log_group_name: String },
TraverseLogEvents,
TraverseLogStreams,
ListClusters,
ListServices { cluster: String },
ListTasks {
cluster: String,
#[arg(long)]
service: Option<String>,
#[arg(long)]
family: Option<String>,
#[arg(long)]
status: Option<String>,
},
ListTaskDefs {
#[arg(long)]
family_prefix: Option<String>,
},
ListUsers {
#[arg(long)]
path_prefix: Option<String>,
},
ListGroups {
#[arg(long)]
path_prefix: Option<String>,
},
ListRoles {
#[arg(long)]
path_prefix: Option<String>,
},
ListPolicies {
#[arg(long)]
scope: Option<String>,
#[arg(long)]
only_attached: Option<String>,
#[arg(long)]
path_prefix: Option<String>,
},
ListAccessKeys {
#[arg(long)]
user: Option<String>,
},
ListInstanceProfiles {
#[arg(long)]
path_prefix: Option<String>,
},
TraverseUserGroups { user: String },
TraverseUserPolicies { user: String },
TraverseUserAccessKeys { user: String },
TraverseRolePolicies { role: String },
TraverseRoleProfiles { role: String },
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
if let Some(region) = &cli.region {
unsafe { std::env::set_var("AWS_REGION", region) };
}
let aws = AwsAccount::from_default().context(
"Set AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_REGION, or configure ~/.aws/credentials [default]",
)?;
match cli.command {
Command::LogGroups { prefix } => {
let mut t = groups_table(aws);
if let Some(p) = prefix {
t.add_condition(eq("logGroupNamePrefix", p));
}
print_table(&t).await?;
}
Command::LogStreams { log_group_name } => {
let mut t = streams_table(aws);
t.add_condition(eq("logGroupName", log_group_name));
print_table(&t).await?;
}
Command::LogEvents { log_group_name } => {
let mut t = events_table(aws);
t.add_condition(eq("logGroupName", log_group_name));
print_table(&t).await?;
}
Command::TraverseLogEvents => {
let mut log_groups = groups_table(aws);
log_groups.add_condition(eq("logGroupNamePrefix", TRAVERSE_GROUP));
print_table(&log_groups).await?;
println!("\n→ events via with_many(\"events\"):\n");
let events_any = log_groups.get_ref("events")?;
let records = events_any.list_values().await?;
render_records(&records, Some("eventId"));
}
Command::TraverseLogStreams => {
let mut log_groups = groups_table(aws);
log_groups.add_condition(eq("logGroupNamePrefix", TRAVERSE_GROUP));
print_table(&log_groups).await?;
println!("\n→ streams via with_many(\"streams\"):\n");
let streams_any = log_groups.get_ref("streams")?;
let records = streams_any.list_values().await?;
render_records(&records, Some("logStreamName"));
}
Command::ListClusters => {
let t = clusters_table(aws);
print_table(&t).await?;
}
Command::ListServices { cluster } => {
let mut t = services_table(aws);
t.add_condition(eq("cluster", cluster));
print_table(&t).await?;
}
Command::ListTasks {
cluster,
service,
family,
status,
} => {
let mut t = tasks_table(aws);
t.add_condition(eq("cluster", cluster));
if let Some(s) = service {
t.add_condition(eq("serviceName", s));
}
if let Some(f) = family {
t.add_condition(eq("family", f));
}
if let Some(s) = status {
t.add_condition(eq("desiredStatus", s));
}
print_table(&t).await?;
}
Command::ListTaskDefs { family_prefix } => {
let mut t = task_definitions_table(aws);
if let Some(p) = family_prefix {
t.add_condition(eq("familyPrefix", p));
}
print_table(&t).await?;
}
Command::ListUsers { path_prefix } => {
let mut t = users_table(aws);
if let Some(p) = path_prefix {
t.add_condition(eq("PathPrefix", p));
}
print_table(&t).await?;
}
Command::ListGroups { path_prefix } => {
let mut t = iam_groups_table(aws);
if let Some(p) = path_prefix {
t.add_condition(eq("PathPrefix", p));
}
print_table(&t).await?;
}
Command::ListRoles { path_prefix } => {
let mut t = roles_table(aws);
if let Some(p) = path_prefix {
t.add_condition(eq("PathPrefix", p));
}
print_table(&t).await?;
}
Command::ListPolicies {
scope,
only_attached,
path_prefix,
} => {
let mut t = policies_table(aws);
if let Some(s) = scope {
t.add_condition(eq("Scope", s));
}
if let Some(o) = only_attached {
t.add_condition(eq("OnlyAttached", o));
}
if let Some(p) = path_prefix {
t.add_condition(eq("PathPrefix", p));
}
print_table(&t).await?;
}
Command::ListAccessKeys { user } => {
let mut t = access_keys_table(aws);
if let Some(u) = user {
t.add_condition(eq("UserName", u));
}
print_table(&t).await?;
}
Command::ListInstanceProfiles { path_prefix } => {
let mut t = instance_profiles_table(aws);
if let Some(p) = path_prefix {
t.add_condition(eq("PathPrefix", p));
}
print_table(&t).await?;
}
Command::TraverseUserGroups { user } => {
let target = find_user(aws.clone(), &user).await?;
print_table(&target.ref_groups(aws)).await?;
}
Command::TraverseUserPolicies { user } => {
let target = find_user(aws.clone(), &user).await?;
print_table(&target.ref_attached_policies(aws)).await?;
}
Command::TraverseUserAccessKeys { user } => {
let target = find_user(aws.clone(), &user).await?;
print_table(&target.ref_access_keys(aws)).await?;
}
Command::TraverseRolePolicies { role } => {
let target = find_role(aws.clone(), &role).await?;
print_table(&target.ref_attached_policies(aws)).await?;
}
Command::TraverseRoleProfiles { role } => {
let target = find_role(aws.clone(), &role).await?;
print_table(&target.ref_instance_profiles(aws)).await?;
}
}
Ok(())
}
async fn find_user(aws: AwsAccount, name: &str) -> Result<User> {
users_table(aws)
.get(name.to_string())
.await?
.with_context(|| format!("IAM user {name:?} not found in this account"))
}
async fn find_role(aws: AwsAccount, name: &str) -> Result<Role> {
roles_table(aws)
.get(name.to_string())
.await?
.with_context(|| format!("IAM role {name:?} not found in this account"))
}