pub mod build;
pub mod config;
pub mod dev;
pub mod generate;
pub mod init;
pub mod kv;
pub mod login;
pub mod logout;
pub mod preview;
pub mod publish;
pub mod r2;
pub mod route;
pub mod secret;
pub mod subdomain;
pub mod tail;
pub mod whoami;
pub mod exec {
pub use super::build::build;
pub use super::config::configure;
pub use super::dev::dev;
pub use super::generate::generate;
pub use super::init::init;
pub use super::kv::kv_bulk;
pub use super::kv::kv_key;
pub use super::kv::kv_namespace;
pub use super::login::login;
pub use super::logout::logout;
pub use super::preview::preview;
pub use super::publish::publish;
pub use super::r2::r2_bucket;
pub use super::route::route;
pub use super::secret::secret;
pub use super::subdomain::subdomain;
pub use super::tail::tail;
pub use super::whoami::whoami;
}
use std::net::IpAddr;
use std::path::PathBuf;
use std::str::FromStr;
use crate::commands::dev::Protocol;
use crate::commands::tail::websocket::TailFormat;
use crate::preview::HttpMethod;
use crate::settings::toml::migrations::{
DurableObjectsMigration, Migration, MigrationTag, Migrations, RenameClass, TransferClass,
};
use crate::settings::toml::TargetType;
use clap::AppSettings;
use structopt::StructOpt;
use url::Url;
#[derive(Debug, Clone, StructOpt)]
#[structopt(
name = "wrangler",
author = "The Wrangler Team <wrangler@cloudflare.com>",
setting = AppSettings::ArgRequiredElseHelp,
setting = AppSettings::DeriveDisplayOrder,
setting = AppSettings::VersionlessSubcommands,
)]
pub struct Cli {
#[structopt(long, global = true)]
pub verbose: bool,
#[structopt(long, short = "c", default_value = "wrangler.toml", global = true)]
pub config: PathBuf,
#[structopt(name = "env", long, short = "e", global = true)]
pub environment: Option<String>,
#[structopt(subcommand)]
pub command: Command,
}
#[derive(Debug, Clone, StructOpt)]
pub enum Command {
#[structopt(name = "kv:namespace", setting = AppSettings::SubcommandRequiredElseHelp)]
KvNamespace(kv::KvNamespace),
#[structopt(name = "kv:key", setting = AppSettings::SubcommandRequiredElseHelp)]
KvKey(kv::KvKey),
#[structopt(name = "kv:bulk", setting = AppSettings::SubcommandRequiredElseHelp)]
KvBulk(kv::KvBulk),
#[structopt(setting = AppSettings::SubcommandRequiredElseHelp)]
R2(r2::R2),
#[structopt(name = "route", setting = AppSettings::SubcommandRequiredElseHelp)]
Route(route::Route),
#[structopt(name = "secret", setting = AppSettings::SubcommandRequiredElseHelp)]
Secret(secret::Secret),
Generate {
#[structopt(index = 1, default_value = "worker")]
name: String,
#[structopt(index = 2)]
template: Option<String>,
#[structopt(name = "type", long, short = "t")]
target_type: Option<TargetType>,
#[structopt(long, short = "s")]
site: bool,
},
Init {
#[structopt(index = 1)]
name: Option<String>,
#[structopt(name = "type", long, short = "t")]
target_type: Option<TargetType>,
#[structopt(long, short = "s")]
site: bool,
},
Build,
Preview {
#[structopt(index = 1, default_value = "get")]
method: HttpMethod,
#[structopt(short = "u", long, default_value = "https://example.com")]
url: Url,
#[structopt(index = 2)]
body: Option<String>,
#[structopt(long)]
watch: bool,
#[structopt(long)]
headless: bool,
},
Dev {
#[structopt(long, short = "h")]
host: Option<String>,
#[structopt(long, short = "i")]
ip: Option<IpAddr>,
#[structopt(long, short = "p")]
port: Option<u16>,
#[structopt(name = "local-protocol")]
local_protocol: Option<Protocol>,
#[structopt(name = "upstream-protocol")]
upstream_protocol: Option<Protocol>,
#[structopt(long)]
inspect: bool,
#[structopt(long)]
unauthenticated: bool,
},
#[structopt(name = "publish")]
Publish {
#[structopt(long, hidden = true)]
release: bool,
#[structopt(possible_value = "json")]
output: Option<String>,
#[structopt(flatten)]
migration: AdhocMigration,
},
#[structopt(name = "config")]
Config {
#[structopt(name = "api-key", long)]
api_key: bool,
#[structopt(name = "no-verify", long)]
no_verify: bool,
},
#[structopt(name = "subdomain")]
Subdomain {
#[structopt(name = "name", index = 1)]
name: Option<String>,
},
#[structopt(name = "whoami")]
Whoami,
#[structopt(name = "tail")]
Tail {
#[structopt(index = 1)]
name: Option<String>,
#[structopt(long, short = "f", default_value = "json", possible_values = &["json", "pretty"])]
format: TailFormat,
#[structopt(long)]
once: bool,
#[structopt(long = "sampling-rate", default_value = "1")]
sampling_rate: f64,
#[structopt(long, possible_values = &["ok", "error", "canceled"])]
status: Vec<String>,
#[structopt(long)]
method: Vec<String>,
#[structopt(long)]
header: Vec<String>,
#[structopt(long = "ip-address", parse(try_from_str = parse_ip_address))]
ip_address: Vec<String>,
#[structopt(long)]
search: Option<String>,
#[structopt(hidden = true)]
url: Option<Url>,
#[structopt(hidden = true, long = "port", short = "p")]
tunnel_port: Option<u16>,
#[structopt(hidden = true, long = "metrics")]
metrics_port: Option<u16>,
},
#[structopt(name = "login")]
Login {
#[structopt(name = "scopes", long, possible_values = login::SCOPES_LIST.as_ref())]
scopes: Vec<String>,
#[structopt(name = "scopes-list", long)]
scopes_list: bool,
},
#[structopt(name = "logout")]
Logout,
}
#[derive(Debug, Clone, StructOpt)]
pub struct AdhocMigration {
#[structopt(name = "new-class", long, number_of_values = 1)]
new_class: Vec<String>,
#[structopt(name = "delete-class", long, number_of_values = 1)]
delete_class: Vec<String>,
#[structopt(name = "rename-class", long, number_of_values = 2, value_names(&["from class", "to class"]))]
rename_class: Vec<String>,
#[structopt(name = "transfer-class", long, number_of_values = 3, value_names(&["from script", "from class", "to class"]))]
transfer_class: Vec<String>,
#[structopt(name = "old-tag", long)]
old_tag: Option<String>,
#[structopt(name = "new-tag", long)]
new_tag: Option<String>,
}
impl AdhocMigration {
pub fn into_migrations(self) -> Option<Migrations> {
let migration = DurableObjectsMigration {
new_classes: self.new_class,
deleted_classes: self.delete_class,
renamed_classes: self
.rename_class
.chunks_exact(2)
.map(|chunk| {
let (from, to) = if let [from, to] = chunk {
(from.clone(), to.clone())
} else {
unreachable!("Chunks exact returned a slice with a length not equal to 2")
};
RenameClass { from, to }
})
.collect(),
transferred_classes: self
.transfer_class
.chunks_exact(3)
.map(|chunk| {
let (from_script, from, to) = if let [from_script, from, to] = chunk {
(from_script.clone(), from.clone(), to.clone())
} else {
unreachable!("Chunks exact returned a slice with a length not equal to 3")
};
TransferClass {
from,
from_script,
to,
}
})
.collect(),
};
let is_migration_empty = migration.new_classes.is_empty()
&& migration.deleted_classes.is_empty()
&& migration.renamed_classes.is_empty()
&& migration.transferred_classes.is_empty();
if !is_migration_empty || self.old_tag.is_some() || self.new_tag.is_some() {
let migration = if !is_migration_empty {
Some(Migration {
durable_objects: migration,
})
} else {
None
};
Some(Migrations::Adhoc {
script_tag: MigrationTag::Unknown,
provided_old_tag: self.old_tag,
new_tag: self.new_tag,
migration,
})
} else {
None
}
}
}
fn parse_ip_address(input: &str) -> Result<String, anyhow::Error> {
match input {
"self" => Ok(String::from("self")),
address => match IpAddr::from_str(address) {
Ok(_) => Ok(address.to_owned()),
Err(err) => anyhow::bail!("{}: {}", err, input),
},
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rename_class(tag: &str) -> RenameClass {
RenameClass {
from: format!("renameFrom{}", tag),
to: format!("renameTo{}", tag),
}
}
fn transfer_class(tag: &str) -> TransferClass {
TransferClass {
from: format!("transferFromClass{}", tag),
from_script: format!("transferFromScript{}", tag),
to: format!("transferToClass{}", tag),
}
}
#[test]
fn adhoc_migration_parsing() {
let command = Cli::from_iter(&[
"wrangler",
"publish",
"--old-tag",
"oldTag",
"--new-tag",
"newTag",
"--new-class",
"newA",
"--new-class",
"newB",
"--delete-class",
"deleteA",
"--delete-class",
"deleteB",
"--rename-class",
"renameFromA",
"renameToA",
"--rename-class",
"renameFromB",
"renameToB",
"--transfer-class",
"transferFromScriptA",
"transferFromClassA",
"transferToClassA",
"--transfer-class",
"transferFromScriptB",
"transferFromClassB",
"transferToClassB",
])
.command;
if let Command::Publish { migration, .. } = command {
assert_eq!(
migration.into_migrations(),
Some(Migrations::Adhoc {
script_tag: MigrationTag::Unknown,
provided_old_tag: Some(String::from("oldTag")),
new_tag: Some(String::from("newTag")),
migration: Some(Migration {
durable_objects: DurableObjectsMigration {
new_classes: vec![String::from("newA"), String::from("newB")],
deleted_classes: vec![String::from("deleteA"), String::from("deleteB")],
renamed_classes: vec![rename_class("A"), rename_class("B")],
transferred_classes: vec![transfer_class("A"), transfer_class("B")],
}
})
})
);
} else {
assert!(false, "Unkown command {:?}", command)
}
}
}