Skip to content

Commit

Permalink
Merge pull request #8 from molnett/jg/docker-auth
Browse files Browse the repository at this point in the history
feat: support docker login
  • Loading branch information
bittermandel authored Feb 21, 2024
2 parents d0f2bb6 + cc385a9 commit 445ffa4
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 38 deletions.
92 changes: 83 additions & 9 deletions src/commands/auth.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use anyhow::Result;
use std::{
io::Write,
process::{Command, Stdio},
};

use anyhow::{anyhow, Result};
use chrono::Utc;
use clap::Parser;
use clap::{Parser, Subcommand};
use oauth2::{
basic::BasicClient, reqwest::http_client, AuthUrl, ClientId, CsrfToken, TokenResponse, TokenUrl,
};
Expand All @@ -10,12 +15,43 @@ use crate::config::user::Token;

use super::CommandBase;

#[derive(Parser)]
#[derive(Debug)]
#[command(author, version, about, long_about)]
pub struct Auth {}
#[derive(Parser, Debug)]
#[command(
author,
version,
about,
long_about,
subcommand_required = true,
arg_required_else_help = true
)]
pub struct Auth {
#[command(subcommand)]
pub command: Option<Commands>,
}

impl Auth {
pub fn execute(&self, base: &mut CommandBase) -> Result<()> {
match &self.command {
Some(Commands::Login(login)) => login.execute(base),
Some(Commands::Docker(docker)) => docker.execute(base),
None => Ok(()),
}
}
}

#[derive(Subcommand, Debug)]
pub enum Commands {
/// Login to Molnett
Login(Login),

/// Login to Docker Registry using Molnett token
Docker(Docker),
}

#[derive(Parser, Debug)]
pub struct Login {}

impl Login {
pub fn execute(&self, base: &mut CommandBase) -> Result<()> {
let server = Server::http("localhost:0").unwrap();
let local_port = server.server_addr().to_ip().unwrap().port();
Expand All @@ -28,9 +64,7 @@ impl Auth {
AuthUrl::new(format!("{}/oauth2/auth", url)).unwrap(),
Some(TokenUrl::new(format!("{}/oauth2/token", url)).unwrap()),
)
.set_redirect_uri(
oauth2::RedirectUrl::new(redirect_uri.clone()).unwrap(),
);
.set_redirect_uri(oauth2::RedirectUrl::new(redirect_uri.clone()).unwrap());

let (pkce_code_challenge, pkce_verifier) = oauth2::PkceCodeChallenge::new_random_sha256();

Expand Down Expand Up @@ -63,6 +97,8 @@ impl Auth {
if let Some(expires_in) = oauthtoken.expires_in() {
token.expiry =
Some(Utc::now() + chrono::Duration::seconds(expires_in.as_secs() as i64));
} else {
token.expiry = Some(Utc::now() + chrono::Duration::hours(1));
}

base.user_config_mut().write_token(token)?;
Expand All @@ -74,3 +110,41 @@ impl Auth {
Ok(())
}
}

#[derive(Parser, Debug)]
pub struct Docker {}

impl Docker {
pub fn execute(&self, base: &mut CommandBase) -> Result<()> {
let token = base.user_config.get_token().ok_or_else(|| {
anyhow!("Could not get Molnett token. Please run molnctl auth login.")
})?;

if base.user_config.is_token_expired() {
println!("Token expired. Please run molnctl auth login.");
return Ok(());
}

let mut command = Command::new("docker")
.arg("login")
.arg("register.molnett.org")
.arg("--username=x")
.arg("--password-stdin")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;

if let Some(mut stdin) = command.stdin.take() {
stdin.write_all(token.as_bytes())?;
}

let output = command.wait_with_output()?;

if !output.status.success() {
println!("{}", String::from_utf8_lossy(&output.stderr));
} else {
println!("{}", String::from_utf8_lossy(&output.stdout));
}
Ok(())
}
}
29 changes: 10 additions & 19 deletions src/commands/environments.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use super::CommandBase;
use anyhow::{anyhow, Result};
use clap::{Parser, Subcommand};
use super::CommandBase;
use tabled::Table;

#[derive(Parser)]
#[derive(Debug)]
#[derive(Parser, Debug)]
#[command(
author,
version,
Expand All @@ -23,13 +22,12 @@ impl Environments {
match &self.command {
Some(Commands::Create(create)) => create.execute(base),
Some(Commands::List(list)) => list.execute(base),
None => Ok(())
None => Ok(()),
}
}
}

#[derive(Subcommand)]
#[derive(Debug)]
#[derive(Subcommand, Debug)]
pub enum Commands {
/// Create an environment
#[command(arg_required_else_help = true)]
Expand All @@ -39,8 +37,7 @@ pub enum Commands {
List(List),
}

#[derive(Parser)]
#[derive(Debug)]
#[derive(Parser, Debug)]
pub struct Create {
#[arg(help = "Name of the environment to create")]
name: String,
Expand All @@ -60,11 +57,9 @@ impl Create {
.get_token()
.ok_or_else(|| anyhow!("No token found. Please login first."))?;

let response = base.api_client().create_environment(
token,
&self.name,
&org_name
)?;
let response = base
.api_client()
.create_environment(token, &self.name, &org_name)?;

let table = Table::new([response]).to_string();
println!("{}", table);
Expand All @@ -73,8 +68,7 @@ impl Create {
}
}

#[derive(Parser)]
#[derive(Debug)]
#[derive(Parser, Debug)]
pub struct List {
#[arg(long, help = "Organization to list the environments of")]
org: Option<String>,
Expand All @@ -92,10 +86,7 @@ impl List {
.get_token()
.ok_or_else(|| anyhow!("No token found. Please login first."))?;

let response = base.api_client().get_environments(
token,
&org_name
)?;
let response = base.api_client().get_environments(token, &org_name)?;

println!("{:?}", response);

Expand Down
8 changes: 8 additions & 0 deletions src/config/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ impl UserConfig {
pub fn get_token(&self) -> Option<&str> {
self.config.token.as_ref().map(|u| u.access_token.as_str())
}
pub fn is_token_expired(&self) -> bool {
if let Some(token) = &self.config.token {
if let Some(expiry) = token.expiry {
return expiry < chrono::Utc::now();
}
}
true
}
pub fn write_token(&mut self, token: Token) -> Result<(), super::Error> {
self.disk_config.token = Some(token.clone());
self.config.token = Some(token);
Expand Down
27 changes: 17 additions & 10 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
use crate::config::user::UserConfig;
use anyhow::Result;
use camino::Utf8PathBuf;
use clap::{Parser, Subcommand};
use crate::config::user::UserConfig;
use commands::{CommandBase, environments};
use commands::{environments, CommandBase};
mod api;
mod commands;
mod config;


#[derive(Debug)]
#[derive(Parser)]
#[derive(Debug, Parser)]
#[command(
author,
version,
Expand All @@ -18,19 +16,28 @@ mod config;
subcommand_required = true,
arg_required_else_help = true
)]
struct Cli {
#[arg(short, long, value_name = "FILE", env("MOLNETT_CONFIG"), help = "config file, default is $HOME/.config/molnett/config.json")]
pub struct Cli {
#[arg(
short,
long,
value_name = "FILE",
env("MOLNETT_CONFIG"),
help = "config file, default is $HOME/.config/molnett/config.json"
)]
config: Option<Utf8PathBuf>,

#[arg(long, env("MOLNETT_API_URL"), help = "Url of the Molnett API, default is https://api.molnett.org")]
#[arg(
long,
env("MOLNETT_API_URL"),
help = "Url of the Molnett API, default is https://api.molnett.org"
)]
url: Option<String>,

#[command(subcommand)]
command: Option<Commands>,
}

#[derive(Debug)]
#[derive(Subcommand)]
#[derive(Debug, Subcommand)]
enum Commands {
/// Manage organizations
Orgs(commands::orgs::Orgs),
Expand Down

0 comments on commit 445ffa4

Please sign in to comment.