forked from extern/nushell
Fetch content from S3 (#2328)
* fetch content from s3 resource * remove submodule * fix clippy * update Cargo.lock * fix s3 plugin dependency version
This commit is contained in:
parent
015d2ee050
commit
88555860f3
1508
Cargo.lock
generated
1508
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -40,6 +40,7 @@ nu_plugin_textview = {version = "0.18.1", path = "./crates/nu_plugin_textview",
|
|||||||
nu_plugin_to_bson = {version = "0.18.1", path = "./crates/nu_plugin_to_bson", optional = true}
|
nu_plugin_to_bson = {version = "0.18.1", path = "./crates/nu_plugin_to_bson", optional = true}
|
||||||
nu_plugin_to_sqlite = {version = "0.18.1", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
nu_plugin_to_sqlite = {version = "0.18.1", path = "./crates/nu_plugin_to_sqlite", optional = true}
|
||||||
nu_plugin_tree = {version = "0.18.1", path = "./crates/nu_plugin_tree", optional = true}
|
nu_plugin_tree = {version = "0.18.1", path = "./crates/nu_plugin_tree", optional = true}
|
||||||
|
nu_plugin_s3 = { version = "0.18.1", path = "./crates/nu_plugin_s3", optional=true }
|
||||||
|
|
||||||
crossterm = {version = "0.17.5", optional = true}
|
crossterm = {version = "0.17.5", optional = true}
|
||||||
semver = {version = "0.10.0", optional = true}
|
semver = {version = "0.10.0", optional = true}
|
||||||
@ -76,7 +77,7 @@ default = [
|
|||||||
"term-support",
|
"term-support",
|
||||||
"uuid-support",
|
"uuid-support",
|
||||||
]
|
]
|
||||||
stable = ["default", "binaryview", "match", "tree", "post", "fetch", "clipboard-cli", "trash-support", "start", "starship-prompt", "bson", "sqlite"]
|
stable = ["default", "binaryview", "match", "tree", "post", "fetch", "clipboard-cli", "trash-support", "start", "starship-prompt", "bson", "sqlite", "s3"]
|
||||||
|
|
||||||
# Default
|
# Default
|
||||||
inc = ["semver", "nu_plugin_inc"]
|
inc = ["semver", "nu_plugin_inc"]
|
||||||
@ -94,6 +95,7 @@ sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
|
|||||||
start = ["nu_plugin_start"]
|
start = ["nu_plugin_start"]
|
||||||
trace = ["nu-parser/trace"]
|
trace = ["nu-parser/trace"]
|
||||||
tree = ["nu_plugin_tree"]
|
tree = ["nu_plugin_tree"]
|
||||||
|
s3 = ["nu_plugin_s3"]
|
||||||
|
|
||||||
clipboard-cli = ["nu-cli/clipboard-cli"]
|
clipboard-cli = ["nu-cli/clipboard-cli"]
|
||||||
ctrlc-support = ["nu-cli/ctrlc"]
|
ctrlc-support = ["nu-cli/ctrlc"]
|
||||||
@ -160,6 +162,11 @@ name = "nu_plugin_stable_start"
|
|||||||
path = "src/plugins/nu_plugin_stable_start.rs"
|
path = "src/plugins/nu_plugin_stable_start.rs"
|
||||||
required-features = ["start"]
|
required-features = ["start"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "nu_plugin_stable_s3"
|
||||||
|
path = "src/plugins/nu_plugin_stable_s3.rs"
|
||||||
|
required-features = ["s3"]
|
||||||
|
|
||||||
# Main nu binary
|
# Main nu binary
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "nu"
|
name = "nu"
|
||||||
|
20
crates/nu_plugin_s3/Cargo.toml
Normal file
20
crates/nu_plugin_s3/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "nu_plugin_s3"
|
||||||
|
version = "0.18.1"
|
||||||
|
authors = ["The Nu Project Contributors"]
|
||||||
|
edition = "2018"
|
||||||
|
description = "An S3 plugin for Nushell"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nu-plugin = { path = "../nu-plugin", version = "0.18.1" }
|
||||||
|
nu-protocol = { path = "../nu-protocol", version = "0.18.1" }
|
||||||
|
nu-source = { path = "../nu-source", version = "0.18.1" }
|
||||||
|
nu-errors = { path = "../nu-errors", version = "0.18.1" }
|
||||||
|
futures = { version = "0.3", features = ["compat", "io-compat"] }
|
||||||
|
s3handler = "0.5.0"
|
||||||
|
|
||||||
|
[build-dependencies]
|
9
crates/nu_plugin_s3/README.md
Normal file
9
crates/nu_plugin_s3/README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
Nu Plugin S3
|
||||||
|
---
|
||||||
|
|
||||||
|
An S3 plugin for nu shell, it can load the content of S3 objects and convert into table
|
||||||
|
|
||||||
|
#### Snapshot
|
||||||
|
In following example, the return content from httpbin is saved before as an object in AWS S3.
|
||||||
|
![snapshot](https://raw.githubusercontent.com/yanganto/nu_plugin_s3/master/demo.png)
|
||||||
|
|
BIN
crates/nu_plugin_s3/demo.png
Normal file
BIN
crates/nu_plugin_s3/demo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
134
crates/nu_plugin_s3/src/handler.rs
Normal file
134
crates/nu_plugin_s3/src/handler.rs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{CallInfo, CommandAction, ReturnSuccess, ReturnValue, UntaggedValue, Value};
|
||||||
|
use nu_source::{AnchorLocation, Tag};
|
||||||
|
use s3handler::{CredentialConfig, Handler as S3Handler};
|
||||||
|
|
||||||
|
pub struct Handler {
|
||||||
|
pub resource: Option<Value>,
|
||||||
|
pub tag: Tag,
|
||||||
|
pub has_raw: bool,
|
||||||
|
pub config: CredentialConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler {
|
||||||
|
pub fn new() -> Handler {
|
||||||
|
Handler {
|
||||||
|
tag: Tag::unknown(),
|
||||||
|
config: CredentialConfig {
|
||||||
|
host: String::new(),
|
||||||
|
access_key: String::new(),
|
||||||
|
secret_key: String::new(),
|
||||||
|
user: None,
|
||||||
|
region: None,
|
||||||
|
s3_type: None,
|
||||||
|
secure: None,
|
||||||
|
},
|
||||||
|
resource: None,
|
||||||
|
has_raw: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup(&mut self, call_info: CallInfo) -> ReturnValue {
|
||||||
|
self.resource = {
|
||||||
|
let r = call_info.args.nth(0).ok_or_else(|| {
|
||||||
|
ShellError::labeled_error(
|
||||||
|
"No obj or directory specified",
|
||||||
|
"for command",
|
||||||
|
&call_info.name_tag,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Some(r.clone())
|
||||||
|
};
|
||||||
|
self.tag = call_info.name_tag.clone();
|
||||||
|
self.has_raw = call_info.args.has("raw");
|
||||||
|
|
||||||
|
if let Some(e) = call_info.args.get("endpoint") {
|
||||||
|
self.config.host = e.as_string()?
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"No endpoint provided",
|
||||||
|
"for command",
|
||||||
|
&call_info.name_tag,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(access_key) = call_info.args.get("access_key") {
|
||||||
|
self.config.access_key = access_key.as_string()?
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"No access key provided",
|
||||||
|
"for command",
|
||||||
|
&call_info.name_tag,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(secret_key) = call_info.args.get("secret_key") {
|
||||||
|
self.config.secret_key = secret_key.as_string()?
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::labeled_error(
|
||||||
|
"No secret key provided",
|
||||||
|
"for command",
|
||||||
|
&call_info.name_tag,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(region) = call_info.args.get("region") {
|
||||||
|
self.config.region = Some(region.as_string()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
ReturnSuccess::value(UntaggedValue::nothing().into_untagged_value())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Handler {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn s3_helper(resource: &Value, has_raw: bool, config: &CredentialConfig) -> ReturnValue {
|
||||||
|
let resource_str = resource.as_string()?;
|
||||||
|
let mut handler = S3Handler::from(config);
|
||||||
|
let (output, content_type) = handler
|
||||||
|
.cat(&resource_str)
|
||||||
|
.map_err(|e| ShellError::unexpected(e.to_string()))?;
|
||||||
|
|
||||||
|
let extension = if has_raw {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
fn get_accept_ext(s: String) -> Option<String> {
|
||||||
|
if s.contains("json") {
|
||||||
|
Some("json".to_string())
|
||||||
|
} else if s.contains("xml") {
|
||||||
|
Some("xml".to_string())
|
||||||
|
} else if s.contains("svg") {
|
||||||
|
Some("svg".to_string())
|
||||||
|
} else if s.contains("html") {
|
||||||
|
Some("html".to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the extension could not provide when uploading,
|
||||||
|
// try to use the resource extension.
|
||||||
|
content_type.and_then(get_accept_ext).or_else(|| {
|
||||||
|
resource_str
|
||||||
|
.split('.')
|
||||||
|
.last()
|
||||||
|
.map(String::from)
|
||||||
|
.and_then(get_accept_ext)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(e) = extension {
|
||||||
|
Ok(ReturnSuccess::Action(CommandAction::AutoConvert(
|
||||||
|
UntaggedValue::string(output).into_value(Tag {
|
||||||
|
span: resource.tag.span,
|
||||||
|
anchor: Some(AnchorLocation::Url(resource_str)),
|
||||||
|
}),
|
||||||
|
e,
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
ReturnSuccess::value(UntaggedValue::string(output))
|
||||||
|
}
|
||||||
|
}
|
4
crates/nu_plugin_s3/src/lib.rs
Normal file
4
crates/nu_plugin_s3/src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub mod handler;
|
||||||
|
mod nu;
|
||||||
|
|
||||||
|
pub use handler::Handler;
|
6
crates/nu_plugin_s3/src/main.rs
Normal file
6
crates/nu_plugin_s3/src/main.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use nu_plugin::serve_plugin;
|
||||||
|
use nu_plugin_s3::handler;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
serve_plugin(&mut handler::Handler::new())
|
||||||
|
}
|
60
crates/nu_plugin_s3/src/nu/mod.rs
Normal file
60
crates/nu_plugin_s3/src/nu/mod.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use futures::executor::block_on;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_plugin::Plugin;
|
||||||
|
use nu_protocol::{CallInfo, ReturnValue, Signature, SyntaxShape};
|
||||||
|
|
||||||
|
use crate::handler;
|
||||||
|
use crate::handler::s3_helper;
|
||||||
|
|
||||||
|
impl Plugin for handler::Handler {
|
||||||
|
fn config(&mut self) -> Result<Signature, ShellError> {
|
||||||
|
Ok(Signature::build("s3")
|
||||||
|
.desc("Load S3 resource into a cell, convert to table if possible (avoid by appending '--raw' or '-R')")
|
||||||
|
.required(
|
||||||
|
"RESOURCE",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the RESOURCE to fetch the contents from",
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"endpoint",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"the enpoint info for the S3 resource, i.g., s3.ap-northeast-1.amazonaws.com or 10.1.1.1",
|
||||||
|
Some('e'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"access_key",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"the accessy key when authenticating",
|
||||||
|
Some('a'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"secret_key",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"the secret key when authenticating",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"region",
|
||||||
|
SyntaxShape::Any,
|
||||||
|
"the region of the resource, default will use us-east-1",
|
||||||
|
Some('r'),
|
||||||
|
)
|
||||||
|
.switch("raw", "fetch contents as text rather than a table", Some('R'))
|
||||||
|
.filter())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn begin_filter(&mut self, callinfo: CallInfo) -> Result<Vec<ReturnValue>, ShellError> {
|
||||||
|
self.setup(callinfo)?;
|
||||||
|
Ok(vec![block_on(s3_helper(
|
||||||
|
&self.resource.clone().ok_or_else(|| {
|
||||||
|
ShellError::labeled_error(
|
||||||
|
"internal error: resource not set",
|
||||||
|
"resource not set",
|
||||||
|
&self.tag,
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
self.has_raw,
|
||||||
|
&self.config,
|
||||||
|
))])
|
||||||
|
}
|
||||||
|
}
|
6
src/plugins/nu_plugin_stable_s3.rs
Normal file
6
src/plugins/nu_plugin_stable_s3.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use nu_plugin::serve_plugin;
|
||||||
|
use nu_plugin_s3::Handler;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
serve_plugin(&mut Handler::new());
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user