Skip to content
This repository has been archived by the owner on Aug 3, 2023. It is now read-only.

Commit

Permalink
feat: r2 bindings and subcommands (#2146)
Browse files Browse the repository at this point in the history
note: the api-backends for these features are implemented,
but generally available in production yet.

commands: add r2 subcommands to create/delete/list buckets
I opted for sub-commands like wrangler r2 bucket create,
rather than how kv does wrangler kv:namespace create.

bindings: add support for binding a bucket to a worker
this implementation is essentially copy-pasted from the kv binding code,
and functions very similary.

Co-authored-by: Taylor Lee <tlee@cloudflare.com>
  • Loading branch information
taylorlee and taylorlee authored Jan 21, 2022
1 parent 42575ee commit 6408b15
Show file tree
Hide file tree
Showing 20 changed files with 324 additions and 4 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ binary-install = "0.0.3-alpha.1"
chrome-devtools-rs = { version = "0.0.0-alpha.3", features = ["color"] }
chrono = "0.4.19"
clap = "2.33.3"
cloudflare = "0.8.3"
cloudflare = "0.9.0"
colored_json = "2.1.0"
config = { version = "0.11.0", default-features = false, features = ["toml", "json", "yaml", "ini"] }
console = "0.14.1"
Expand Down
6 changes: 6 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod login;
pub mod logout;
pub mod preview;
pub mod publish;
pub mod r2;
pub mod route;
pub mod secret;
pub mod subdomain;
Expand All @@ -27,6 +28,7 @@ pub mod exec {
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;
Expand Down Expand Up @@ -89,6 +91,10 @@ pub enum Command {
#[structopt(name = "kv:bulk", setting = AppSettings::SubcommandRequiredElseHelp)]
KvBulk(kv::KvBulk),

/// Interact with your Workers R2 Buckets
#[structopt(setting = AppSettings::SubcommandRequiredElseHelp)]
R2(r2::R2),

/// List or delete worker routes.
#[structopt(name = "route", setting = AppSettings::SubcommandRequiredElseHelp)]
Route(route::Route),
Expand Down
45 changes: 45 additions & 0 deletions src/cli/r2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use super::Cli;
use crate::commands;
use crate::settings::{global_user::GlobalUser, toml::Manifest};

use anyhow::Result;
use structopt::StructOpt;

#[derive(Debug, Clone, StructOpt)]
#[structopt(rename_all = "lower")]
pub enum R2 {
/// Interact with your Workers R2 Buckets
Bucket(Bucket),
}

#[derive(Debug, Clone, StructOpt)]
#[structopt(rename_all = "lower")]
pub enum Bucket {
/// List existing buckets
List,
/// Create a new bucket
Create {
/// The name for your new bucket
#[structopt(index = 1)]
name: String,
},
/// Delete an existing bucket
Delete {
/// The name of the bucket to delete
/// Note: bucket must be empty
#[structopt(index = 1)]
name: String,
},
}

pub fn r2_bucket(r2: R2, cli_params: &Cli) -> Result<()> {
let user = GlobalUser::new()?;
let manifest = Manifest::new(&cli_params.config)?;
let env = cli_params.environment.as_deref();

match r2 {
R2::Bucket(Bucket::List) => commands::r2::list(&manifest, env, &user),
R2::Bucket(Bucket::Create { name }) => commands::r2::create(&manifest, env, &user, &name),
R2::Bucket(Bucket::Delete { name }) => commands::r2::delete(&manifest, env, &user, &name),
}
}
1 change: 1 addition & 0 deletions src/commands/kv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ mod tests {
binding: "KV".to_string(),
},
],
r2_buckets: Vec::new(),
durable_objects: None,
migrations: None,
name: "test-target".to_string(),
Expand Down
1 change: 1 addition & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod login;
pub mod logout;
mod preview;
pub mod publish;
pub mod r2;
pub mod report;
pub mod route;
pub mod secret;
Expand Down
10 changes: 10 additions & 0 deletions src/commands/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,16 @@ fn validate_target_required_fields_present(target: &Target) -> Result<()> {
}
}

for r2 in &target.r2_buckets {
if r2.binding.is_empty() {
missing_fields.push("r2-bucket binding")
}

if r2.bucket_name.is_empty() {
missing_fields.push("r2-bucket bucket_name")
}
}

let (field_pluralization, is_are) = match missing_fields.len() {
n if n >= 2 => ("fields", "are"),
1 => ("field", "is"),
Expand Down
74 changes: 74 additions & 0 deletions src/commands/r2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use anyhow::Result;

use crate::http;
use crate::settings::global_user::GlobalUser;
use crate::settings::toml::Manifest;
use crate::terminal::message::{Message, StdOut};

use cloudflare::endpoints::r2::{CreateBucket, DeleteBucket, ListBuckets};
use cloudflare::framework::apiclient::ApiClient;

pub fn list(manifest: &Manifest, env: Option<&str>, user: &GlobalUser) -> Result<()> {
let account_id = manifest.get_account_id(env)?;
let client = http::cf_v4_client(user)?;
let result = client.request(&ListBuckets {
account_identifier: &account_id,
});

match result {
Ok(response) => {
let buckets: Vec<String> = response
.result
.buckets
.into_iter()
.map(|b| b.name)
.collect();
println!("{:?}", buckets);
}
Err(e) => println!("{}", e),
}

Ok(())
}

pub fn create(manifest: &Manifest, env: Option<&str>, user: &GlobalUser, name: &str) -> Result<()> {
let account_id = manifest.get_account_id(env)?;
let msg = format!("Creating bucket \"{}\"", name);
StdOut::working(&msg);

let client = http::cf_v4_client(user)?;
let result = client.request(&CreateBucket {
account_identifier: &account_id,
bucket_name: name,
});

match result {
Ok(_) => {
StdOut::success("Success!");
}
Err(e) => print!("{}", e),
}

Ok(())
}

pub fn delete(manifest: &Manifest, env: Option<&str>, user: &GlobalUser, name: &str) -> Result<()> {
let account_id = manifest.get_account_id(env)?;
let msg = format!("Deleting bucket \"{}\"", name);
StdOut::working(&msg);

let client = http::cf_v4_client(user)?;
let result = client.request(&DeleteBucket {
account_identifier: &account_id,
bucket_name: name,
});

match result {
Ok(_) => {
StdOut::success("Success!");
}
Err(e) => print!("{}", e),
}

Ok(())
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ fn run() -> Result<()> {
Command::Subdomain { name } => exec::subdomain(name, &cli_params),
Command::Route(route) => exec::route(route, &cli_params),
Command::Secret(secret) => exec::secret(secret, &cli_params),
Command::R2(r2) => exec::r2_bucket(r2, &cli_params),
Command::KvNamespace(namespace) => exec::kv_namespace(namespace, &cli_params),
Command::KvKey(key) => exec::kv_key(key, &cli_params),
Command::KvBulk(bulk) => exec::kv_bulk(bulk, &cli_params),
Expand Down
8 changes: 8 additions & 0 deletions src/settings/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ pub enum Binding {
name: String,
namespace_id: String,
},
R2Bucket {
name: String,
bucket_name: String,
},
#[serde(rename = "durable_object_namespace")]
DurableObjectsClass {
name: String,
Expand All @@ -37,6 +41,10 @@ impl Binding {
Binding::KvNamespace { name, namespace_id }
}

pub fn new_r2_bucket(name: String, bucket_name: String) -> Binding {
Binding::R2Bucket { name, bucket_name }
}

pub fn new_durable_object_namespace(
name: String,
class_name: String,
Expand Down
2 changes: 2 additions & 0 deletions src/settings/toml/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use serde_with::rust::string_empty_as_none;
use crate::settings::toml::builder::Builder;
use crate::settings::toml::durable_objects::DurableObjects;
use crate::settings::toml::kv_namespace::ConfigKvNamespace;
use crate::settings::toml::r2_bucket::ConfigR2Bucket;
use crate::settings::toml::route::RouteConfig;
use crate::settings::toml::site::Site;
use crate::settings::toml::triggers::Triggers;
Expand All @@ -28,6 +29,7 @@ pub struct Environment {
pub site: Option<Site>,
#[serde(alias = "kv-namespaces")]
pub kv_namespaces: Option<Vec<ConfigKvNamespace>>,
pub r2_buckets: Option<Vec<ConfigR2Bucket>>,
pub vars: Option<HashMap<String, String>>,
pub text_blobs: Option<HashMap<String, PathBuf>>,
pub triggers: Option<Triggers>,
Expand Down
37 changes: 37 additions & 0 deletions src/settings/toml/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use crate::settings::toml::dev::Dev;
use crate::settings::toml::durable_objects::DurableObjects;
use crate::settings::toml::environment::Environment;
use crate::settings::toml::kv_namespace::{ConfigKvNamespace, KvNamespace};
use crate::settings::toml::r2_bucket::{ConfigR2Bucket, R2Bucket};
use crate::settings::toml::route::RouteConfig;
use crate::settings::toml::site::Site;
use crate::settings::toml::target_type::TargetType;
Expand Down Expand Up @@ -63,6 +64,7 @@ pub struct Manifest {
pub env: Option<HashMap<String, Environment>>,
#[serde(alias = "kv-namespaces")]
pub kv_namespaces: Option<Vec<ConfigKvNamespace>>,
pub r2_buckets: Option<Vec<ConfigR2Bucket>>,
// TODO: maybe one day, serde toml support will allow us to serialize sites
// as a TOML inline table (this would prevent confusion with environments too!)
pub site: Option<Site>,
Expand Down Expand Up @@ -374,6 +376,7 @@ impl Manifest {
// to include the name of the environment
name: self.name.clone(), // Inherited
kv_namespaces: get_namespaces(self.kv_namespaces.clone(), preview)?, // Not inherited
r2_buckets: get_buckets(self.r2_buckets.clone(), preview)?, // Not inherited
durable_objects: self.durable_objects.clone(), // Not inherited
migrations: match (preview, &self.migrations) {
(false, Some(migrations)) => Some(Migrations::List {
Expand Down Expand Up @@ -408,6 +411,9 @@ impl Manifest {
// don't inherit kv namespaces because it is an anti-pattern to use the same namespaces across multiple environments
target.kv_namespaces = get_namespaces(environment.kv_namespaces.clone(), preview)?;

// don't inherit r2 buckets because it is an anti-pattern to use the same buckets across multiple environments
target.r2_buckets = get_buckets(environment.r2_buckets.clone(), preview)?;

// don't inherit durable object configuration
target.durable_objects = environment.durable_objects.clone();

Expand Down Expand Up @@ -750,6 +756,37 @@ fn get_namespaces(
}
}

fn get_buckets(r2_buckets: Option<Vec<ConfigR2Bucket>>, preview: bool) -> Result<Vec<R2Bucket>> {
if let Some(buckets) = r2_buckets {
buckets.into_iter().map(|ns| {
if preview {
if let Some(preview_bucket_name) = &ns.preview_bucket_name {
if let Some(bucket_name) = &ns.bucket_name {
if preview_bucket_name == bucket_name {
StdOut::warn("Specifying the same r2 bucket_name for both preview and production sessions may cause bugs in your production worker! Proceed with caution.");
}
}
Ok(R2Bucket {
bucket_name: preview_bucket_name.to_string(),
binding: ns.binding.to_string(),
})
} else {
anyhow::bail!("In order to preview a worker with r2 buckets, you must designate a preview_bucket_name in your configuration file for each r2 bucket you'd like to preview.")
}
} else if let Some(bucket_name) = &ns.bucket_name {
Ok(R2Bucket {
bucket_name: bucket_name.to_string(),
binding: ns.binding,
})
} else {
anyhow::bail!("You must specify the bucket name in the bucket_name field for the bucket with binding \"{}\"", &ns.binding)
}
}).collect()
} else {
Ok(Vec::new())
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
2 changes: 2 additions & 0 deletions src/settings/toml/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod environment;
mod kv_namespace;
mod manifest;
pub mod migrations;
mod r2_bucket;
mod route;
mod site;
pub(crate) mod target;
Expand All @@ -15,6 +16,7 @@ pub use builder::{ModuleRule, UploadFormat};
pub use durable_objects::{DurableObjects, DurableObjectsClass};
pub use kv_namespace::{ConfigKvNamespace, KvNamespace};
pub use manifest::Manifest;
pub use r2_bucket::{ConfigR2Bucket, R2Bucket};
pub use route::{Route, RouteConfig};
pub use site::Site;
pub use target::Target;
Expand Down
34 changes: 34 additions & 0 deletions src/settings/toml/r2_bucket.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use std::fmt;

use serde::{Deserialize, Serialize};

use crate::settings::binding::Binding;

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct ConfigR2Bucket {
pub binding: String,
pub bucket_name: Option<String>,
pub preview_bucket_name: Option<String>,
}

#[derive(Clone, Debug, PartialEq)]
pub struct R2Bucket {
pub binding: String,
pub bucket_name: String,
}

impl fmt::Display for R2Bucket {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"binding: {}, bucket_name: {}",
self.binding, self.bucket_name
)
}
}

impl R2Bucket {
pub fn binding(&self) -> Binding {
Binding::new_r2_bucket(self.binding.clone(), self.bucket_name.clone())
}
}
2 changes: 2 additions & 0 deletions src/settings/toml/target.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::durable_objects::DurableObjects;
use super::kv_namespace::KvNamespace;
pub(crate) use super::manifest::LazyAccountId;
use super::r2_bucket::R2Bucket;
use super::site::Site;
use super::target_type::TargetType;
use super::UsageModel;
Expand All @@ -15,6 +16,7 @@ use std::path::PathBuf;
pub struct Target {
pub account_id: LazyAccountId,
pub kv_namespaces: Vec<KvNamespace>,
pub r2_buckets: Vec<R2Bucket>,
pub durable_objects: Option<DurableObjects>,
pub migrations: Option<Migrations>,
pub name: String,
Expand Down
Loading

0 comments on commit 6408b15

Please sign in to comment.