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

feat: support for async deployments #40

Merged
merged 2 commits into from
Dec 12, 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
54 changes: 54 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ serde_yaml = "0.9.25"
tabled = "0.14.0"
term = "0.7.0"
thiserror = "1.0.48"
time = { version = "0.3.36", features = ["serde", "serde-well-known"] }
tiny_http = "0.12.0"
tracing = "0.1.37"
tungstenite = { git = "https://github.com/snapview/tungstenite-rs", rev = "0fa4197", features = [
Expand Down
19 changes: 10 additions & 9 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@ impl APIClient {
}
}

pub fn get_org(
&self,
token: &str,
org_name: &str,
) -> anyhow::Result<Organization> {
pub fn get_org(&self, token: &str, org_name: &str) -> anyhow::Result<Organization> {
let url = format!("{}/orgs/{}", self.base_url, org_name);
let response = self.get(&url, token)?;
match response.status() {
Expand Down Expand Up @@ -83,6 +79,7 @@ impl APIClient {
) -> anyhow::Result<ListServicesResponse> {
let url = format!("{}/orgs/{}/envs/{}/svcs", self.base_url, org_name, env_name);
let response: String = self.get(&url, token)?.error_for_status()?.text()?;
println!("{}", response.clone());
serde_json::from_str(response.as_str()).with_context(|| "Failed to deserialize response")
}

Expand Down Expand Up @@ -116,12 +113,16 @@ impl APIClient {
&self,
token: &str,
org_name: &str,
) -> anyhow::Result<Vec<String>> {
) -> anyhow::Result<ListEnvironmentsResponse> {
let url = format!("{}/orgs/{}/envs", self.base_url, org_name);
let response = self.get(&url, token)?;
match response.status() {
StatusCode::OK => Ok(serde_json::from_str(&response.text()?)
.with_context(|| "Failed to deserialize environments")?),
StatusCode::OK => {
let text = &response.text()?;
println!("{}", text);
Ok(serde_json::from_str(text)
.with_context(|| "Failed to deserialize environments")?)
}
StatusCode::UNAUTHORIZED => Err(anyhow!("Unauthorized, please login first")),
StatusCode::NOT_FOUND => Err(anyhow!("Organization does not exist")),
_ => Err(anyhow!(
Expand Down Expand Up @@ -186,7 +187,7 @@ impl APIClient {
org_name: &str,
env_name: &str,
service: Service,
) -> anyhow::Result<Service> {
) -> anyhow::Result<DeployServiceResponse> {
let url = format!("{}/orgs/{}/envs/{}/svcs", self.base_url, org_name, env_name);
let body = serde_json::to_string(&service)?;
let response = self.post_str(&url, token, body)?;
Expand Down
36 changes: 30 additions & 6 deletions src/api/types.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::fmt::{Display, Formatter, Result};
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter, Result};
use tabled::Tabled;
use time::OffsetDateTime;

#[derive(Serialize, Deserialize, Debug, Tabled)]
pub struct Organization {
Expand All @@ -15,16 +16,28 @@ pub struct ListOrganizationResponse {
pub organizations: Vec<Organization>,
}

#[derive(Serialize, Deserialize, Debug, Tabled)]
pub struct Environment {
pub name: String,
pub organization_id: String,
pub created_at: String,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ListEnvironmentsResponse {
pub environments: Vec<Environment>,
}

#[derive(Serialize, Deserialize, Debug, Tabled)]
pub struct CreateEnvironmentResponse {
pub name: String,
#[serde(default, skip_serializing_if = "is_default")]
pub copy_from: DisplayOption<String>
pub copy_from: DisplayOption<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ListServicesResponse {
pub services: Vec<Service>
pub services: Vec<Service>,
}

#[derive(Serialize, Deserialize, Debug, Tabled, Clone, PartialEq)]
Expand All @@ -35,12 +48,23 @@ pub struct Service {
#[serde(default, skip_serializing_if = "is_default")]
pub env: DisplayOption<DisplayHashMap>,
#[serde(default, skip_serializing_if = "is_default")]
pub secrets: DisplayOption<DisplayHashMap>
pub secrets: DisplayOption<DisplayHashMap>,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct DeployServiceResponse {
pub id: String,
pub status: String,
#[serde(with = "time::serde::rfc3339::option")]
pub start_time: Option<OffsetDateTime>,
#[serde(with = "time::serde::rfc3339::option")]
pub end_time: Option<OffsetDateTime>,
pub error: Option<String>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ListSecretsResponse {
pub secrets: Vec<Secret>
pub secrets: Vec<Secret>,
}

#[derive(Serialize, Deserialize, Debug, Tabled)]
Expand All @@ -61,7 +85,7 @@ fn is_default<T: Default + PartialEq>(t: &T) -> bool {
impl Display for DisplayOption<DisplayHashMap> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
if self.0.is_none() {
return Ok(())
return Ok(());
}

let hashmap = self.0.as_ref().unwrap();
Expand Down
49 changes: 25 additions & 24 deletions src/commands/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,37 +76,38 @@ impl Login {
println!("Browse to: {}", auth_url);

println!("Listening on {}", server.server_addr());
for request in server.incoming_requests() {
let url = request.url();
let request = server
.incoming_requests()
.next()
.expect("server shutting down");
let url = request.url();

let code = url.split("?code=").collect::<Vec<&str>>()[1];
let code = url.split("?code=").collect::<Vec<&str>>()[1];

let oauthtoken = client
.exchange_code(oauth2::AuthorizationCode::new(code.to_string()))
.set_pkce_verifier(pkce_verifier)
.request(http_client)
.unwrap();
let oauthtoken = client
.exchange_code(oauth2::AuthorizationCode::new(code.to_string()))
.set_pkce_verifier(pkce_verifier)
.request(http_client)
.unwrap();

let mut token = Token::new();
let mut token = Token::new();

token.access_token = oauthtoken.access_token().secret().to_string();
if let Some(refresh_token) = oauthtoken.refresh_token() {
token.refresh_token = Some(refresh_token.secret().to_string());
}
// TODO: the api returns "expiry":"2024-01-01T11:03:53.485518152+01:00"
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));
}
token.access_token = oauthtoken.access_token().secret().to_string();
if let Some(refresh_token) = oauthtoken.refresh_token() {
token.refresh_token = Some(refresh_token.secret().to_string());
}
// TODO: the api returns "expiry":"2024-01-01T11:03:53.485518152+01:00"
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)?;
base.user_config_mut().write_token(token)?;

request.respond(Response::from_string("Success! You can close this tab now"))?;
request.respond(Response::from_string("Success! You can close this tab now"))?;

return Ok(());
}
Ok(())
}
}
Expand Down
31 changes: 15 additions & 16 deletions src/commands/orgs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ use tabled::Table;

use super::CommandBase;

#[derive(Parser)]
#[derive(Debug)]
#[derive(Parser, Debug)]
#[command(
author,
version,
Expand All @@ -22,16 +21,15 @@ pub struct Orgs {
impl Orgs {
pub fn execute(&self, base: &mut CommandBase) -> Result<()> {
match &self.command {
Some(Commands::List(list)) => list.execute(&base),
Some(Commands::Create(create)) => create.execute(&base),
Some(Commands::List(list)) => list.execute(base),
Some(Commands::Create(create)) => create.execute(base),
Some(Commands::Switch(switch)) => switch.execute(base),
None => Ok(()),
}
}
}

#[derive(Subcommand)]
#[derive(Debug)]
#[derive(Subcommand, Debug)]
pub enum Commands {
/// List your orgs
List(List),
Expand All @@ -41,8 +39,7 @@ pub enum Commands {
Switch(Switch),
}

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

impl List {
Expand All @@ -61,8 +58,7 @@ impl List {
}
}

#[derive(Parser)]
#[derive(Debug)]
#[derive(Parser, Debug)]
pub struct Create {
#[clap(short, long)]
name: Option<String>,
Expand Down Expand Up @@ -123,11 +119,11 @@ impl CreatePlanBuilder {
.unwrap();

self.name = input;
return self;
} else {
self.name = name.unwrap().to_string();
return self;
}

self
}

pub fn billing_email(mut self, billing_email: Option<&str>) -> Self {
Expand All @@ -143,7 +139,8 @@ impl CreatePlanBuilder {
.unwrap();

self.billing_email = input;
return self;

self
}

fn verify(&self) -> Result<()> {
Expand All @@ -165,8 +162,7 @@ impl CreatePlan {
}
}

#[derive(Parser)]
#[derive(Debug)]
#[derive(Parser, Debug)]
pub struct Switch {
#[arg(help = "Name of the org to switch to")]
org: Option<String>,
Expand All @@ -188,7 +184,10 @@ impl Switch {
if org_names.contains(&arg_org.as_str()) {
arg_org
} else {
return Err(anyhow!("organization {} does not exist or you do not have access to it", arg_org))
return Err(anyhow!(
"organization {} does not exist or you do not have access to it",
arg_org
));
}
} else {
let selection = FuzzySelect::with_theme(&dialoguer::theme::ColorfulTheme::default())
Expand Down
Loading
Loading