Skip to content
Closed
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
132 changes: 103 additions & 29 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,11 @@ enum Commands {
#[arg(short, long)]
target: Option<String>,
},
/// Log in to Connect (shortcut for `connect auth login`)
///
/// Your builds then identify themselves to the package feeds, which raises
/// your rate limit and gives access to private feeds.
Login(LoginArgs),
/// Avocado Connect platform commands (auth, upload)
Connect {
#[command(subcommand)]
Expand Down Expand Up @@ -1410,27 +1415,39 @@ enum ConfigCommands {
},
}

/// Arguments shared by `avocado login` and `avocado connect auth login`.
#[derive(clap::Args)]
struct LoginArgs {
/// API URL (defaults to https://connect.peridio.com or AVOCADO_CONNECT_URL env var)
#[arg(long)]
url: Option<String>,
/// Profile name (defaults to "default")
#[arg(long)]
profile: Option<String>,
/// Use an existing API token instead of browser login
#[arg(long)]
token: Option<String>,
/// Organization id (UUID) to scope the new token to. Required for
/// non-interactive multi-org logins; ignored when --token is set.
#[arg(long)]
org: Option<String>,
/// Output format
#[arg(long, value_enum, default_value_t = OutputFormat::Human)]
output: OutputFormat,
}

impl LoginArgs {
async fn run(self) -> Result<()> {
ConnectAuthLoginCommand::new(self.url, self.profile, self.token, self.org, self.output)
.execute()
.await
}
}

#[derive(Subcommand)]
enum ConnectAuthCommands {
/// Login to the Connect platform
Login {
/// API URL (defaults to https://connect.peridio.com or AVOCADO_CONNECT_URL env var)
#[arg(long)]
url: Option<String>,
/// Profile name (defaults to "default")
#[arg(long)]
profile: Option<String>,
/// Use an existing API token instead of browser login
#[arg(long)]
token: Option<String>,
/// Organization id (UUID) to scope the new token to. Required for
/// non-interactive multi-org logins; ignored when --token is set.
#[arg(long)]
org: Option<String>,
/// Output format
#[arg(long, value_enum, default_value_t = OutputFormat::Human)]
output: OutputFormat,
},
Login(LoginArgs),
/// Logout from the Connect platform
Logout {
/// Profile name (defaults to the active default profile)
Expand Down Expand Up @@ -3748,19 +3765,10 @@ async fn main() -> Result<()> {
Ok(())
}
},
Commands::Login(args) => args.run().await,
Commands::Connect { command } => match command {
ConnectCommands::Auth { command } => match command {
ConnectAuthCommands::Login {
url,
profile,
token,
org,
output,
} => {
let cmd = ConnectAuthLoginCommand::new(url, profile, token, org, output);
cmd.execute().await?;
Ok(())
}
ConnectAuthCommands::Login(args) => args.run().await,
ConnectAuthCommands::Logout { profile, output } => {
let cmd = ConnectAuthLogoutCommand { profile, output };
cmd.execute().await?;
Expand Down Expand Up @@ -5358,6 +5366,72 @@ mod tests {
])));
}

/// `avocado login` is a shortcut for `connect auth login`: one argument
/// struct, one command. Both are pure Connect-API calls and must not route
/// to the VM, which could auto-start it for a login.
#[test]
fn login_is_a_shortcut_for_connect_auth_login() {
let parse = |args: &[&str]| {
Cli::try_parse_from(args)
.expect("args should parse")
.command
};
let short = parse(&[
"avocado",
"login",
"--token",
"t",
"--profile",
"p",
"--org",
"o",
"--url",
"https://connect.example",
"--output",
"json",
]);
let long = parse(&[
"avocado",
"connect",
"auth",
"login",
"--token",
"t",
"--profile",
"p",
"--org",
"o",
"--url",
"https://connect.example",
"--output",
"json",
]);
let Commands::Login(a) = &short else {
panic!("`avocado login` must parse to Commands::Login");
};
let Commands::Connect {
command:
ConnectCommands::Auth {
command: ConnectAuthCommands::Login(b),
},
} = &long
else {
panic!("`connect auth login` must parse to ConnectAuthCommands::Login");
};
// Every shared field, not a sample. The point of the shared struct is that
// the two spellings cannot diverge; a test covering three of five fields
// would not notice the other two diverging.
for args in [a, b] {
assert_eq!(args.token.as_deref(), Some("t"));
assert_eq!(args.profile.as_deref(), Some("p"));
assert_eq!(args.org.as_deref(), Some("o"));
assert_eq!(args.url.as_deref(), Some("https://connect.example"));
assert!(matches!(args.output, OutputFormat::Json));
}
assert!(!needs_vm_routing(&short));
assert!(!needs_vm_routing(&long));
}

/// The engine-driving `container dev` subcommands must route, or
/// `ensure_routed_for_process` never runs for them, `DOCKER_HOST` is never
/// set, and `HostTopology::sync_mode` cannot select the VM push path however
Expand Down
Loading