From 292c6bc88e5bd3753169683cbe7d95e16d14e082 Mon Sep 17 00:00:00 2001 From: Jesse Szwedko Date: Mon, 18 Jan 2021 13:23:38 -0400 Subject: [PATCH 01/19] chore(external docs): Document additional ACLs for aws_s3 sink (#6087) * chore(external docs): Document additional ACLs for aws_s3 sink Added in https://github.com/timberio/vector/pull/3439 --- docs/reference/components/sinks/aws_s3.cue | 14 ++++++++------ docs/reference/urls.cue | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/reference/components/sinks/aws_s3.cue b/docs/reference/components/sinks/aws_s3.cue index e7a34a3a01db1..792e5cba2470a 100644 --- a/docs/reference/components/sinks/aws_s3.cue +++ b/docs/reference/components/sinks/aws_s3.cue @@ -91,12 +91,14 @@ components: sinks: aws_s3: components._aws & { type: string: { default: null enum: { - "private": "Owner gets FULL_CONTROL. No one else has access rights (default)." - "public-read": "Owner gets FULL_CONTROL. The AllUsers group gets READ access." - "public-read-write": "Owner gets FULL_CONTROL. The AllUsers group gets READ and WRITE access. Granting this on a bucket is generally not recommended." - "aws-exec-read": "Owner gets FULL_CONTROL. Amazon EC2 gets READ access to GET an Amazon Machine Image (AMI) bundle from Amazon S3." - "authenticated-read": "Owner gets FULL_CONTROL. The AuthenticatedUsers group gets READ access." - "log-delivery-write": "The LogDelivery group gets WRITE and READ_ACP permissions on the bucket. For more information about logs, see [Amazon S3 Server Access Logging](https://docs.aws.amazon.com/AmazonS3/latest/dev/ServerLogs.html)." + "private": "Owner gets `FULL_CONTROL`. No one else has access rights (default)." + "public-read": "Owner gets `FULL_CONTROL`. The AllUsers group gets `READ` access." + "public-read-write": "Owner gets `FULL_CONTROL`. The AllUsers group gets `READ` and `WRITE` access. Granting this on a bucket is generally not recommended." + "aws-exec-read": "Owner gets `FULL_CONTROL`. Amazon EC2 gets `READ` access to `GET` an Amazon Machine Image (AMI) bundle from Amazon S3." + "authenticated-read": "Owner gets `FULL_CONTROL`. The AuthenticatedUsers group gets `READ` access." + "bucket-owner-read": "Object owner gets `FULL_CONTROL`. Bucket owner gets `READ. access." + "bucket-owner-full-control": "Both the object owner and the bucket owner get `FULL_CONTROL` over the object." + "log-delivery-write": "The LogDelivery group gets `WRITE` and `READ_ACP` permissions on the bucket. For more information about logs, see [Amazon S3 Server Access Logging](\(urls.aws_s3_server_access_logs))." } } } diff --git a/docs/reference/urls.cue b/docs/reference/urls.cue index fefc8c2e31dcd..061502f5cb376 100644 --- a/docs/reference/urls.cue +++ b/docs/reference/urls.cue @@ -67,6 +67,7 @@ urls: { aws_s3_grantee: "\(aws_docs)/AmazonS3/latest/dev/acl-overview.html#specifying-grantee" aws_s3_metadata: "\(aws_docs)/AmazonS3/latest/dev/UsingMetadata.html#object-metadata" aws_s3_regions: "\(aws_docs)/general/latest/gr/rande.html#s3_region" + aws_s3_server_access_logs: "\(aws_docs)/AmazonS3/latest/dev/ServerLogs.html" aws_s3_service_limits: "\(aws_docs)/streams/latest/dev/service-sizes-and-limits.html" aws_s3_sse: "\(aws_docs)/AmazonS3/latest/dev/UsingServerSideEncryption.html" aws_s3_storage_classes: "https://aws.amazon.com/s3/storage-classes/" From 7e838abbe9d9cf6a338dde55a31a6ba9727a425e Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Mon, 18 Jan 2021 21:03:55 +0300 Subject: [PATCH 02/19] chore(external docs): Remove the rawConfig from Helm tutorial (#6116) Signed-off-by: MOZGIII --- docs/reference/installation/_interfaces/helm3.cue | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/reference/installation/_interfaces/helm3.cue b/docs/reference/installation/_interfaces/helm3.cue index b84f33c54d67c..d9c3bc8c60117 100644 --- a/docs/reference/installation/_interfaces/helm3.cue +++ b/docs/reference/installation/_interfaces/helm3.cue @@ -41,9 +41,8 @@ installation: _interfaces: "helm3": { stdout: type: console inputs: ["kubernetes_logs"] - rawConfig: | - target = "stdout" - encoding = "json" + target: "stdout" + encoding: "json" VALUES """# install: #"helm install --namespace \#(_namespace) --create-namespace \#(_release_name) \#(_repo_name)/\#(_chart_name) --values values.yaml"# From f5f7228339fe3362d1cb841324deb87eeef78eb0 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Mon, 18 Jan 2021 21:04:24 +0300 Subject: [PATCH 03/19] fix(kubernetes_logs source): Support for collecting logs from static pods (#6056) --- src/kubernetes/hash_value.rs | 83 ++++++++++++++++++- src/kubernetes/mod.rs | 1 + src/kubernetes/pod_manager_logic.rs | 71 ++++++++++++++++ src/kubernetes/state/evmap.rs | 22 +++++ .../kubernetes_logs/k8s_paths_provider.rs | 57 ++++++++++++- 5 files changed, 229 insertions(+), 5 deletions(-) create mode 100644 src/kubernetes/pod_manager_logic.rs diff --git a/src/kubernetes/hash_value.rs b/src/kubernetes/hash_value.rs index 6a41d9f33e14b..3688910272b45 100644 --- a/src/kubernetes/hash_value.rs +++ b/src/kubernetes/hash_value.rs @@ -4,6 +4,8 @@ use k8s_openapi::{apimachinery::pkg::apis::meta::v1::ObjectMeta, Metadata}; use std::hash::{Hash, Hasher}; use std::ops::Deref; +use super::pod_manager_logic::extract_static_pod_config_hashsum; + /// A wrapper that provides a [`Hash`] implementation for any k8s resource /// object. /// Delegates to object uid for hashing and equality. @@ -20,10 +22,17 @@ where } /// Get the `uid` from the `T`'s [`Metadata`] (if any). + /// + /// If the static pod config hashsum annotation exists in the metadata, it + /// will be used instead of the mirror pod uid. pub fn uid(&self) -> Option<&str> { - let ObjectMeta { ref uid, .. } = self.0.metadata(); - let uid = uid.as_ref()?; - Some(uid.as_str()) + let metadata = self.0.metadata(); + // If static pod config hashsum annotation exists in the metadata - + // use it instead of the uid. + if let Some(config_hashsum) = extract_static_pod_config_hashsum(metadata) { + return Some(config_hashsum); + } + Some(metadata.uid.as_ref()?.as_str()) } } @@ -70,3 +79,71 @@ where &self.0 } } + +#[cfg(test)] +mod tests { + use k8s_openapi::api::core::v1::Pod; + + use super::*; + + #[test] + fn test_uid() { + let cases = vec![ + // No uid or config hashsum. + (Pod::default(), None), + // Has uid, doesn't have a config hashsum. + ( + Pod { + metadata: ObjectMeta { + uid: Some("uid".to_owned()), + ..ObjectMeta::default() + }, + ..Pod::default() + }, + Some("uid"), + ), + // Has both the uid and the config hashsum. + ( + Pod { + metadata: ObjectMeta { + uid: Some("uid".to_owned()), + annotations: Some( + vec![( + "kubernetes.io/config.mirror".to_owned(), + "config-hashsum".to_owned(), + )] + .into_iter() + .collect(), + ), + ..ObjectMeta::default() + }, + ..Pod::default() + }, + Some("config-hashsum"), + ), + // Has only the config hashsum. + ( + Pod { + metadata: ObjectMeta { + annotations: Some( + vec![( + "kubernetes.io/config.mirror".to_owned(), + "config-hashsum".to_owned(), + )] + .into_iter() + .collect(), + ), + ..ObjectMeta::default() + }, + ..Pod::default() + }, + Some("config-hashsum"), + ), + ]; + + for (pod, expected) in cases { + let hash_value = HashValue::new(pod); + assert_eq!(hash_value.uid(), expected); + } + } +} diff --git a/src/kubernetes/mod.rs b/src/kubernetes/mod.rs index f9df1a26cf21c..5be1f97714b65 100644 --- a/src/kubernetes/mod.rs +++ b/src/kubernetes/mod.rs @@ -27,6 +27,7 @@ pub mod hash_value; pub mod instrumenting_watcher; pub mod mock_watcher; pub mod multi_response_decoder; +pub mod pod_manager_logic; pub mod reflector; pub mod resource_version; pub mod state; diff --git a/src/kubernetes/pod_manager_logic.rs b/src/kubernetes/pod_manager_logic.rs new file mode 100644 index 0000000000000..fc4fc05b6839d --- /dev/null +++ b/src/kubernetes/pod_manager_logic.rs @@ -0,0 +1,71 @@ +//! This mod contains bits of logic related to the `kubelet` part called +//! Pod Manager internal implementation. + +use k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta; + +/// Extract the static pod config hashsum from the mirror pod annotations. +/// +/// This part of Kubernetes changed a bit over time, so we're implemeting +/// support up to 1.14, which is an MSKV at this time. +/// +/// See: https://github.com/kubernetes/kubernetes/blob/cea1d4e20b4a7886d8ff65f34c6d4f95efcb4742/pkg/kubelet/pod/mirror_client.go#L80-L81 +pub fn extract_static_pod_config_hashsum(metadata: &ObjectMeta) -> Option<&str> { + let annotations = metadata.annotations.as_ref()?; + annotations + .get("kubernetes.io/config.mirror") + .map(String::as_str) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_extract_static_pod_config_hashsum() { + let cases = vec![ + (ObjectMeta::default(), None), + ( + ObjectMeta { + annotations: Some(vec![].into_iter().collect()), + ..ObjectMeta::default() + }, + None, + ), + ( + ObjectMeta { + annotations: Some( + vec![( + "kubernetes.io/config.mirror".to_owned(), + "config-hashsum".to_owned(), + )] + .into_iter() + .collect(), + ), + ..ObjectMeta::default() + }, + Some("config-hashsum"), + ), + ( + ObjectMeta { + annotations: Some( + vec![ + ( + "kubernetes.io/config.mirror".to_owned(), + "config-hashsum".to_owned(), + ), + ("other".to_owned(), "value".to_owned()), + ] + .into_iter() + .collect(), + ), + ..ObjectMeta::default() + }, + Some("config-hashsum"), + ), + ]; + + for (metadata, expected) in cases { + assert_eq!(extract_static_pod_config_hashsum(&metadata), expected); + } + } +} diff --git a/src/kubernetes/state/evmap.rs b/src/kubernetes/state/evmap.rs index a7fa5c63d9db6..5f496e82b6003 100644 --- a/src/kubernetes/state/evmap.rs +++ b/src/kubernetes/state/evmap.rs @@ -149,6 +149,28 @@ mod tests { assert_eq!(val, Box::new(HashValue::new(pod))); } + #[test] + fn test_kv_static_pod() { + let pod = Pod { + metadata: ObjectMeta { + uid: Some("uid".to_owned()), + annotations: Some( + vec![( + "kubernetes.io/config.mirror".to_owned(), + "config-hashsum".to_owned(), + )] + .into_iter() + .collect(), + ), + ..ObjectMeta::default() + }, + ..Pod::default() + }; + let (key, val) = kv(pod.clone()).unwrap(); + assert_eq!(key, "config-hashsum"); + assert_eq!(val, Box::new(HashValue::new(pod))); + } + #[tokio::test] async fn test_without_debounce() { let (state_reader, state_writer) = evmap::new(); diff --git a/src/sources/kubernetes_logs/k8s_paths_provider.rs b/src/sources/kubernetes_logs/k8s_paths_provider.rs index 5bee0ca94966e..04a35a3bc25dc 100644 --- a/src/sources/kubernetes_logs/k8s_paths_provider.rs +++ b/src/sources/kubernetes_logs/k8s_paths_provider.rs @@ -3,7 +3,7 @@ #![deny(missing_docs)] use super::path_helpers::build_pod_logs_directory; -use crate::kubernetes as k8s; +use crate::kubernetes::{self as k8s, pod_manager_logic::extract_static_pod_config_hashsum}; use evmap::ReadHandle; use file_source::paths_provider::PathsProvider; use k8s_openapi::api::core::v1::Pod; @@ -60,11 +60,38 @@ impl PathsProvider for K8sPathsProvider { } } +/// This function takes a `Pod` resource and returns the path to where the logs +/// for the said `Pod` are expected to be found. +/// +/// In the common case, the effective path is built using the `namespace`, +/// `name` and `uid` of the Pod. However, there's a special case for +/// `Static Pod`s: they keep their logs at the path that consists of config +/// hashsum instead of the `Pod` `uid`. The reason for this is `kubelet` is +/// locally authoritative over those `Pod`s, and the API only has +/// `Monitor Pod`s - the "dummy" entires useful for discovery and association. +/// Their UIDs are generated at the Kubernetes API side, and do not represent +/// the actual config hashsum as one would expect. +/// +/// To work around this, we use the mirror pod annotations (if any) to obtain +/// the effective config hashsum, see the `extract_static_pod_config_hashsum` +/// function that does this. +/// +/// See https://github.com/timberio/vector/issues/6001 +/// See https://github.com/kubernetes/kubernetes/blob/ef3337a443b402756c9f0bfb1f844b1b45ce289d/pkg/kubelet/pod/pod_manager.go#L30-L44 +/// See https://github.com/kubernetes/kubernetes/blob/cea1d4e20b4a7886d8ff65f34c6d4f95efcb4742/pkg/kubelet/pod/mirror_client.go#L80-L81 fn extract_pod_logs_directory(pod: &Pod) -> Option { let metadata = &pod.metadata; let namespace = metadata.namespace.as_ref()?; let name = metadata.name.as_ref()?; - let uid = metadata.uid.as_ref()?; + + let uid = if let Some(static_pod_config_hashsum) = extract_static_pod_config_hashsum(metadata) { + // If there's a static pod config hashsum - use it instead of uid. + static_pod_config_hashsum + } else { + // In the common case - just fallback to the real pod uid. + metadata.uid.as_ref()? + }; + Some(build_pod_logs_directory(&namespace, &name, &uid)) } @@ -174,7 +201,9 @@ mod tests { #[test] fn test_extract_pod_logs_directory() { let cases = vec![ + // Empty pod. (Pod::default(), None), + // Happy path. ( Pod { metadata: ObjectMeta { @@ -187,6 +216,7 @@ mod tests { }, Some("/var/log/pods/sandbox0-ns_sandbox0-name_sandbox0-uid"), ), + // No uid. ( Pod { metadata: ObjectMeta { @@ -198,6 +228,7 @@ mod tests { }, None, ), + // No name. ( Pod { metadata: ObjectMeta { @@ -209,6 +240,7 @@ mod tests { }, None, ), + // No namespace. ( Pod { metadata: ObjectMeta { @@ -220,6 +252,27 @@ mod tests { }, None, ), + // Static pod config hashsum as uid. + ( + Pod { + metadata: ObjectMeta { + namespace: Some("sandbox0-ns".to_owned()), + name: Some("sandbox0-name".to_owned()), + uid: Some("sandbox0-uid".to_owned()), + annotations: Some( + vec![( + "kubernetes.io/config.mirror".to_owned(), + "sandbox0-config-hashsum".to_owned(), + )] + .into_iter() + .collect(), + ), + ..ObjectMeta::default() + }, + ..Pod::default() + }, + Some("/var/log/pods/sandbox0-ns_sandbox0-name_sandbox0-config-hashsum"), + ), ]; for (pod, expected) in cases { From fee348396779b009d28ae5b7fbbf663b7bb46ffe Mon Sep 17 00:00:00 2001 From: Ian Henry Date: Mon, 18 Jan 2021 13:33:02 -0500 Subject: [PATCH 04/19] chore(host_metrics source): Adds cue docs for customizing sysfs and procfs in host_metrics component (#6033) Co-authored-by: Binary Logic Co-authored-by: James Turnbull --- .../components/sources/host_metrics.cue | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/reference/components/sources/host_metrics.cue b/docs/reference/components/sources/host_metrics.cue index 7c9a3cac70b83..bc8f2c457113a 100644 --- a/docs/reference/components/sources/host_metrics.cue +++ b/docs/reference/components/sources/host_metrics.cue @@ -45,6 +45,24 @@ components: sources: host_metrics: { platform_name: null } + env_vars: { + PROCFS_ROOT: { + description: "Sets an arbitrary path to the system's Procfs root. Can be used to expose host metrics from within a container. Unset and uses system `/proc` by default." + type: string: { + default: null + examples: ["/mnt/host/proc"] + } + } + + SYSFS_ROOT: { + description: "Sets an arbitrary path to the system's Sysfs root. Can be used to expose host metrics from within a container. Unset and uses system `/sys` by default." + type: string: { + default: null + examples: ["/mnt/host/sys"] + } + } + } + configuration: { collectors: { description: "The list of host metric collector services to use. Defaults to all collectors." From a2107bb182d471b5a351600d4ff8079de066564b Mon Sep 17 00:00:00 2001 From: Jean Mertz Date: Mon, 18 Jan 2021 23:20:07 +0100 Subject: [PATCH 05/19] feat(remap): diagnostic error messages (#6023) --- Cargo.lock | 12 + lib/remap-cli/src/cmd.rs | 14 +- lib/remap-cli/src/lib.rs | 7 +- lib/remap-cli/src/repl.rs | 31 +- lib/remap-lang/Cargo.toml | 2 + lib/remap-lang/src/diagnostic.rs | 488 ++++++ lib/remap-lang/src/error.rs | 68 +- lib/remap-lang/src/expression.rs | 14 +- lib/remap-lang/src/expression/array.rs | 4 - lib/remap-lang/src/expression/assignment.rs | 53 +- lib/remap-lang/src/expression/function.rs | 63 +- lib/remap-lang/src/expression/if_statement.rs | 89 +- lib/remap-lang/src/expression/not.rs | 6 - lib/remap-lang/src/expression/path.rs | 41 +- lib/remap-lang/src/expression/variable.rs | 44 +- lib/remap-lang/src/lib.rs | 133 +- lib/remap-lang/src/parser.rs | 1353 +++++++++++------ lib/remap-lang/src/path.rs | 36 +- lib/remap-lang/src/program.rs | 298 ++-- lib/remap-lang/src/runtime.rs | 34 +- lib/remap-lang/src/state.rs | 29 + lib/remap-lang/src/value.rs | 1 + lib/remap-tests/src/main.rs | 49 +- ...ent_fallible_for_infallible_expression.vrl | 15 + .../tests/assignment_variable_with_path.vrl | 14 + .../tests/fallible_root_expression.vrl | 16 + .../tests/function_call_abort_infallible.vrl | 15 + .../tests/function_call_internal_error.vrl | 16 + .../function_call_invalid_argument_type.vrl | 16 + .../tests/function_call_missing_argument.vrl | 12 + .../tests/function_call_undefined.vrl | 12 + .../tests/function_call_unknown_keyword.vrl | 12 + .../tests/funtion_call_arity_mismatch.vrl | 15 + .../if_statement_non_boolean_condition.vrl | 18 + lib/remap-tests/tests/invalid_path.vrl | 16 +- lib/remap-tests/tests/regex_invalid.vrl | 16 + scripts/check-style.sh | 1 + src/conditions/remap.rs | 66 +- src/mapping/query/query_value.rs | 1 + src/transforms/remap.rs | 17 +- tests/behavior/transforms/coercer.toml | 4 +- tests/behavior/transforms/filter.toml | 4 +- tests/behavior/transforms/reduce.toml | 14 +- tests/behavior/transforms/remap.toml | 307 ++-- 44 files changed, 2306 insertions(+), 1170 deletions(-) create mode 100644 lib/remap-lang/src/diagnostic.rs create mode 100644 lib/remap-tests/tests/assignment_fallible_for_infallible_expression.vrl create mode 100644 lib/remap-tests/tests/assignment_variable_with_path.vrl create mode 100644 lib/remap-tests/tests/fallible_root_expression.vrl create mode 100644 lib/remap-tests/tests/function_call_abort_infallible.vrl create mode 100644 lib/remap-tests/tests/function_call_internal_error.vrl create mode 100644 lib/remap-tests/tests/function_call_invalid_argument_type.vrl create mode 100644 lib/remap-tests/tests/function_call_missing_argument.vrl create mode 100644 lib/remap-tests/tests/function_call_undefined.vrl create mode 100644 lib/remap-tests/tests/function_call_unknown_keyword.vrl create mode 100644 lib/remap-tests/tests/funtion_call_arity_mismatch.vrl create mode 100644 lib/remap-tests/tests/if_statement_non_boolean_condition.vrl create mode 100644 lib/remap-tests/tests/regex_invalid.vrl diff --git a/Cargo.lock b/Cargo.lock index f0307bfbda773..9a9b4138c77ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -943,6 +943,16 @@ dependencies = [ "tracing 0.1.22", ] +[[package]] +name = "codespan-reporting" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6ce42b8998a383572e0a802d859b1f00c79b7b7474e62fff88ee5c2845d9c13" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "colored" version = "2.0.0" @@ -5353,6 +5363,7 @@ dependencies = [ "bitflags", "bytes 0.5.6", "chrono", + "codespan-reporting", "criterion", "dyn-clone", "paste", @@ -5360,6 +5371,7 @@ dependencies = [ "pest_derive", "regex", "serde", + "termcolor", "thiserror", ] diff --git a/lib/remap-cli/src/cmd.rs b/lib/remap-cli/src/cmd.rs index c6bfc18b46d43..b8a8c5e1bf19c 100644 --- a/lib/remap-cli/src/cmd.rs +++ b/lib/remap-cli/src/cmd.rs @@ -1,5 +1,5 @@ use super::{repl, Error}; -use remap::{state, Object, Program, Runtime, Value}; +use remap::{state, Formatter, Object, Program, Runtime, Value}; use std::collections::BTreeMap; use std::fs::File; use std::io::{self, Read}; @@ -51,7 +51,7 @@ fn run(opts: &Opts) -> Result<(), Error> { repl(objects) } else { for mut object in objects { - let result = execute(&mut object, &program).map(|v| { + let result = execute(&mut object, program.clone()).map(|v| { if opts.print_object { object.to_string() } else { @@ -79,12 +79,16 @@ fn repl(object: Vec) -> Result<(), Error> { Err(Error::ReplFeature) } -fn execute(object: &mut impl Object, program: &str) -> Result { +fn execute(object: &mut impl Object, source: String) -> Result { let state = state::Program::default(); let mut runtime = Runtime::new(state); - let program = Program::new(program, &remap_functions::all(), None, true)?; + let (program, _) = Program::new(source.clone(), &remap_functions::all(), None, true).map_err( + |diagnostics| Error::Parse(Formatter::new(&source, diagnostics).colored().to_string()), + )?; - runtime.execute(object, &program).map_err(Into::into) + runtime + .run(object, &program) + .map_err(|err| Error::Runtime(err.to_string())) } fn read_program(source: Option<&str>, file: Option<&PathBuf>) -> Result { diff --git a/lib/remap-cli/src/lib.rs b/lib/remap-cli/src/lib.rs index 5530856a83f81..1ad764a42af61 100644 --- a/lib/remap-cli/src/lib.rs +++ b/lib/remap-cli/src/lib.rs @@ -9,8 +9,11 @@ pub enum Error { #[error("io error")] Io(#[from] std::io::Error), - #[error("remap error: {0}")] - Remap(#[from] remap::RemapError), + #[error("parse error")] + Parse(String), + + #[error("runtime error")] + Runtime(String), #[error("json error")] Json(#[from] serde_json::Error), diff --git a/lib/remap-cli/src/repl.rs b/lib/remap-cli/src/repl.rs index 2c571ec82bb7c..8a4a622db740d 100644 --- a/lib/remap-cli/src/repl.rs +++ b/lib/remap-cli/src/repl.rs @@ -1,7 +1,7 @@ use crate::Error; use prettytable::{format, Cell, Row, Table}; use regex::Regex; -use remap::{state, Object, Program, Runtime, Value}; +use remap::{state, Formatter, Object, Program, Runtime, Value}; use remap_functions::all as funcs; use rustyline::completion::Completer; use rustyline::error::ReadlineError; @@ -27,6 +27,7 @@ pub(crate) fn run(mut objects: Vec) -> Result<(), Error> { let mut index = 0; let func_docs_regex = Regex::new(r"^help\sdocs\s(\w{1,})$").unwrap(); + let mut compiler_state = state::Compiler::default(); let mut rt = Runtime::new(state::Program::default()); let mut rl = Editor::::new(); rl.set_helper(Some(Repl::new())); @@ -113,7 +114,12 @@ pub(crate) fn run(mut objects: Vec) -> Result<(), Error> { _ => line, }; - let value = resolve(objects.get_mut(index), &mut rt, command); + let value = resolve( + objects.get_mut(index), + &mut rt, + command, + &mut compiler_state, + ); println!("{}\n", value); } Err(ReadlineError::Interrupted) => break, @@ -128,18 +134,29 @@ pub(crate) fn run(mut objects: Vec) -> Result<(), Error> { Ok(()) } -fn resolve(object: Option<&mut impl Object>, runtime: &mut Runtime, program: &str) -> String { +fn resolve( + object: Option<&mut impl Object>, + runtime: &mut Runtime, + program: &str, + state: &mut state::Compiler, +) -> String { let object = match object { None => return Value::Null.to_string(), Some(object) => object, }; - let program = match Program::new(program, &remap_functions::all(), None, true) { - Ok(program) => program, - Err(err) => return err.to_string(), + let program = match Program::new_with_state( + program.to_owned(), + &remap_functions::all(), + None, + true, + state, + ) { + Ok((program, _)) => program, + Err(diagnostics) => return Formatter::new(program, diagnostics).colored().to_string(), }; - match runtime.execute(object, &program) { + match runtime.run(object, &program) { Ok(value) => value.to_string(), Err(err) => err.to_string(), } diff --git a/lib/remap-lang/Cargo.toml b/lib/remap-lang/Cargo.toml index 296fa5200a057..b77db4bb5c6cb 100644 --- a/lib/remap-lang/Cargo.toml +++ b/lib/remap-lang/Cargo.toml @@ -10,6 +10,7 @@ license = "MPL-2.0" bitflags = "1" bytes = "0.5.6" chrono = "0.4" +codespan-reporting = "0.11" dyn-clone = "1" paste = "1" pest = "2" @@ -17,6 +18,7 @@ pest_derive = "2" regex = "1" serde = "1" thiserror = "1" +termcolor = "1" [dev-dependencies] criterion = "0.3" diff --git a/lib/remap-lang/src/diagnostic.rs b/lib/remap-lang/src/diagnostic.rs new file mode 100644 index 0000000000000..24cb27536f7a2 --- /dev/null +++ b/lib/remap-lang/src/diagnostic.rs @@ -0,0 +1,488 @@ +use crate::value::Kind; +use codespan_reporting::diagnostic; +use std::fmt; +use std::ops::{Deref, DerefMut, Range, RangeInclusive}; + +/// A result type in which the `Ok` variant contains `T` and a list of zero or +/// more non-error diagnostics. The `Err` variant contains a list of one or more +/// diagnostics (errors and warnings). +pub type Result = std::result::Result<(T, DiagnosticList), DiagnosticList>; + +/// A formatter to display diagnostics tied to a given source. +pub struct Formatter<'a> { + source: &'a str, + diagnostics: DiagnosticList, + color: bool, +} + +impl<'a> Formatter<'a> { + pub fn new(source: &'a str, diagnostics: impl Into) -> Self { + Self { + source, + diagnostics: diagnostics.into(), + color: false, + } + } + + pub fn colored(mut self) -> Self { + self.color = true; + self + } + + pub fn enable_colors(&mut self, color: bool) { + self.color = color + } +} + +impl<'a> fmt::Display for Formatter<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use codespan_reporting::files::SimpleFile; + use codespan_reporting::term; + use std::str::from_utf8; + use termcolor::Buffer; + + let file = SimpleFile::new("", self.source); + let config = term::Config::default(); + let mut buffer = if self.color { + Buffer::ansi() + } else { + Buffer::no_color() + }; + + f.write_str("\n")?; + + for diagnostic in self.diagnostics.iter() { + term::emit(&mut buffer, &config, &file, &diagnostic.to_owned().into()) + .map_err(|_| fmt::Error)?; + } + + f.write_str(from_utf8(buffer.as_slice()).map_err(|_| fmt::Error)?) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Diagnostic { + severity: Severity, + message: String, + labels: Vec