Skip to content

Commit

Permalink
Implement edgedb instance resize (#1201)
Browse files Browse the repository at this point in the history
  • Loading branch information
elprans authored Feb 1, 2024
1 parent 94512c7 commit 11c9831
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .github/workflows.src/build.inc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
curl -s https://packages.edgedb.com/apt/.jsonindexes/$idx_file > /tmp/$idx_file
fi
out=$(cat /tmp/$idx_file | jq -r --arg REV "$rev" --arg ARCH "<< tgt.arch >>" "$jq_filter")
if [ -n "$out" ]; then
if [ -n "$out" ]; then
echo 'Skip rebuilding existing << tgt.name >>'
val=false
fi
Expand Down
40 changes: 40 additions & 0 deletions src/cloud/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,23 @@ pub struct CloudInstance {
pub status: String,
pub version: String,
pub region: String,
pub tier: CloudTier,
#[serde(skip_serializing_if = "Option::is_none")]
tls_ca: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ui_url: Option<String>,
pub billables: Vec<CloudInstanceResource>,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct CloudInstanceResource {
pub name: String,
pub display_name: String,
pub display_unit: String,
pub display_quota: String,
}


impl CloudInstance {
pub async fn as_credentials(&self, secret_key: &str) -> anyhow::Result<Credentials> {
let config = Builder::new()
Expand Down Expand Up @@ -112,6 +123,14 @@ pub struct CloudInstanceCreate {
// pub default_user: Option<String>,
}

#[derive(Debug, serde::Serialize)]
pub struct CloudInstanceResize {
pub name: String,
pub org: String,
pub requested_resources: Option<Vec<CloudInstanceResourceRequest>>,
pub tier: Option<CloudTier>,
}

#[derive(Debug, serde::Serialize)]
pub struct CloudInstanceUpgrade {
pub name: String,
Expand Down Expand Up @@ -267,6 +286,27 @@ pub async fn create_cloud_instance(
Ok(())
}

#[tokio::main]
pub async fn resize_cloud_instance(
client: &CloudClient,
request: &CloudInstanceResize,
) -> anyhow::Result<()> {
let url = format!("orgs/{}/instances/{}", request.org, request.name);
let operation: CloudOperation = client
.put(url, request)
.await
.or_else(|e| match e.downcast_ref::<ErrorResponse>() {
Some(ErrorResponse { code: reqwest::StatusCode::NOT_FOUND, .. }) => {
anyhow::bail!(
"Instance \"{}/{}\" does not exist.", request.org, request.name);
}
_ => Err(e),
})?;
wait_instance_available_after_operation(
operation, &request.org, &request.name, client).await?;
Ok(())
}

#[tokio::main]
pub async fn upgrade_cloud_instance(
client: &CloudClient,
Expand Down
10 changes: 5 additions & 5 deletions src/portable/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,14 @@ pub fn create(cmd: &Create, opts: &crate::options::Options) -> anyhow::Result<()
))?;
}

if cp.compute_size.is_some() {
if cp.billables.compute_size.is_some() {
return Err(opts.error(
clap::error::ErrorKind::ArgumentConflict,
cformat!("The <bold>--compute-size</bold> option is only applicable to cloud instances."),
))?;
}

if cp.storage_size.is_some() {
if cp.billables.storage_size.is_some() {
return Err(opts.error(
clap::error::ErrorKind::ArgumentConflict,
cformat!("The <bold>--storage-size</bold> option is only applicable to cloud instances."),
Expand Down Expand Up @@ -222,10 +222,10 @@ fn create_cloud(

let server_ver = cloud::versions::get_version(&query, client)?;

let compute_size = cp.and_then(|p| p.compute_size);
let storage_size = cp.and_then(|p| p.storage_size);
let compute_size = cp.and_then(|p| p.billables.compute_size);
let storage_size = cp.and_then(|p| p.billables.storage_size);

let tier = if let Some(tier) = cp.and_then(|p| p.tier) {
let tier = if let Some(tier) = cp.and_then(|p| p.billables.tier) {
tier
} else if compute_size.is_some() || storage_size.is_some() || org.preferred_payment_method.is_some() {
cloud::ops::CloudTier::Pro
Expand Down
2 changes: 2 additions & 0 deletions src/portable/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::portable::install;
use crate::portable::link;
use crate::portable::list_versions;
use crate::portable::project;
use crate::portable::resize;
use crate::portable::revert;
use crate::portable::status;
use crate::portable::uninstall;
Expand Down Expand Up @@ -46,6 +47,7 @@ pub fn instance_main(cmd: &ServerInstanceCommand, options: &Options)
Link(c) => link::link(c, &options),
List(c) if cfg!(windows) => windows::list(c, options),
List(c) => status::list(c, options),
Resize(c) => resize::resize(c, options),
Upgrade(c) => upgrade::upgrade(c, options),
Start(c) => control::start(c),
Stop(c) => control::stop(c),
Expand Down
1 change: 1 addition & 0 deletions src/portable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub mod install;
mod link;
mod list_versions;
mod reset_password;
mod resize;
mod revert;
pub mod status;
mod uninstall;
Expand Down
36 changes: 31 additions & 5 deletions src/portable/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ pub enum InstanceCommand {
Unlink(Unlink),
/// Show logs for an instance
Logs(Logs),
/// Resize a Cloud instance
Resize(Resize),
/// Upgrade installations and instances
Upgrade(Upgrade),
/// Revert a major instance upgrade
Expand Down Expand Up @@ -145,11 +147,7 @@ pub enum InstanceName {
}

#[derive(clap::Args, IntoArgs, Debug, Clone)]
pub struct CloudInstanceParams {
/// The region in which to create the instance (for cloud instances)
#[arg(long)]
pub region: Option<String>,

pub struct CloudInstanceBillables {
/// Cloud instance subscription tier.
#[arg(long, value_name="tier")]
#[arg(value_enum)]
Expand All @@ -166,6 +164,16 @@ pub struct CloudInstanceParams {
pub storage_size: Option<u16>,
}

#[derive(clap::Args, IntoArgs, Debug, Clone)]
pub struct CloudInstanceParams {
/// The region in which to create the instance (for cloud instances)
#[arg(long)]
pub region: Option<String>,

#[command(flatten)]
pub billables: CloudInstanceBillables,
}

#[derive(clap::Args, IntoArgs, Debug, Clone)]
pub struct Create {
#[command(flatten)]
Expand Down Expand Up @@ -443,6 +451,24 @@ pub struct Logs {
pub follow: bool,
}

#[derive(clap::Args, IntoArgs, Debug, Clone)]
pub struct Resize {
#[command(flatten)]
pub cloud_opts: CloudOptions,

/// Instance to resize
#[arg(short='I', long, required=true)]
#[arg(value_hint=ValueHint::Other)] // TODO complete instance name
pub instance: InstanceName,

#[command(flatten)]
pub billables: CloudInstanceBillables,

/// Do not ask questions
#[arg(long)]
pub non_interactive: bool,
}

#[derive(clap::Args, IntoArgs, Debug, Clone)]
pub struct Upgrade {
#[command(flatten)]
Expand Down
159 changes: 159 additions & 0 deletions src/portable/resize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
use color_print::cformat;

use crate::cloud;
use crate::portable::options::{Resize, InstanceName};
use crate::print::echo;
use crate::question;


pub fn resize(cmd: &Resize, opts: &crate::options::Options) -> anyhow::Result<()> {
match &cmd.instance {
InstanceName::Local(_) => {
Err(opts.error(
clap::error::ErrorKind::InvalidValue,
cformat!("Only Cloud instances can be resized."),
))?
},
InstanceName::Cloud { org_slug: org, name } => {
resize_cloud_cmd(cmd, org, name, opts)
},
}
}

fn resize_cloud_cmd(
cmd: &Resize,
org_slug: &str,
name: &str,
opts: &crate::options::Options,
) -> anyhow::Result<()> {
let billables = &cmd.billables;

if billables.tier.is_none()
&& billables.compute_size.is_none()
&& billables.storage_size.is_none()
{
return Err(opts.error(
clap::error::ErrorKind::MissingRequiredArgument,
cformat!("Either <bold>--tier</bold>, <bold>--compute-size</bold>, \
or <bold>--storage-size</bold> must be specified."),
))?;
}

if billables.compute_size.is_some() && billables.storage_size.is_some() {
return Err(opts.error(
clap::error::ErrorKind::MissingRequiredArgument,
cformat!("<bold>--compute-size</bold>, \
and <bold>--storage-size</bold> cannot be modified at the same time."),
))?;
}

let client = cloud::client::CloudClient::new(&opts.cloud_options)?;
client.ensure_authenticated()?;

let inst_name = InstanceName::Cloud {
org_slug: org_slug.to_string(),
name: name.to_string(),
};

let inst = cloud::ops::find_cloud_instance_by_name(name, org_slug, &client)?
.ok_or_else(|| anyhow::anyhow!("instance not found"))?;

let compute_size = billables.compute_size;
let storage_size = billables.storage_size;
let mut resources_display_vec: Vec<String> = vec![];

if let Some(tier) = billables.tier {
if tier == inst.tier && compute_size.is_none() && storage_size.is_none() {
return Err(opts.error(
clap::error::ErrorKind::InvalidValue,
cformat!("Instance \"{org_slug}/{name}\" is already a {tier:?} \
instance."),
))?;
}

if tier == cloud::ops::CloudTier::Free {
if compute_size.is_some() {
return Err(opts.error(
clap::error::ErrorKind::ArgumentConflict,
cformat!("The <bold>--compute-size</bold> option can \
only be specified for Pro instances."),
))?;
}
if storage_size.is_some() {
return Err(opts.error(
clap::error::ErrorKind::ArgumentConflict,
cformat!("The <bold>--storage-size</bold> option can \
only be specified for Pro instances."),
))?;
}
}

if tier != inst.tier {
resources_display_vec.push(format!(
"New Tier: {tier:?}",
));
}
}

let mut req_resources: Vec<cloud::ops::CloudInstanceResourceRequest> = vec![];

if let Some(compute_size) = compute_size {
req_resources.push(
cloud::ops::CloudInstanceResourceRequest{
name: "compute".to_string(),
value: compute_size,
},
);
resources_display_vec.push(format!(
"New Compute Size: {} compute unit{}",
compute_size,
if compute_size == 1 {""} else {"s"},
));
}

if let Some(storage_size) = storage_size {
req_resources.push(
cloud::ops::CloudInstanceResourceRequest{
name: "storage".to_string(),
value: storage_size,
},
);
resources_display_vec.push(format!(
"New Storage Size: {} gigabyte{}",
storage_size,
if storage_size == 1 {""} else {"s"},
));
}

let mut resources_display = resources_display_vec.join("\n");
if resources_display != "" {
resources_display = format!("\n{resources_display}");
}

let prompt = format!(
"Will resize the \"{inst_name}\" Cloud instance as follows:\
\n\
{resources_display}\
\n\nContinue?",
);

if !cmd.non_interactive && !question::Confirm::new(prompt).ask()? {
return Ok(());
}

let request = cloud::ops::CloudInstanceResize {
name: name.to_string(),
org: org_slug.to_string(),
requested_resources: Some(req_resources),
tier: billables.tier,
};
cloud::ops::resize_cloud_instance(&client, &request)?;
echo!(
"EdgeDB Cloud instance",
inst_name,
"has been resized successfuly."
);
echo!("To connect to the instance run:");
echo!(" edgedb -I", inst_name);
return Ok(())
}

0 comments on commit 11c9831

Please sign in to comment.