-
Notifications
You must be signed in to change notification settings - Fork 113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce config map format and parsing logic #1004
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,10 +24,13 @@ | |
//! --grpc-tls-certificate=<CERTIFICATE_PATH> \ | ||
//! --root-tls-certificate=<CERTIFICATE_PATH> | ||
|
||
use log::info; | ||
use anyhow::anyhow; | ||
use core::str::FromStr; | ||
use log::{debug, info}; | ||
use oak_runtime::{configure_and_run, proto::oak::application::ApplicationConfiguration}; | ||
use prost::Message; | ||
use std::{ | ||
collections::HashMap, | ||
fs::{read_to_string, File}, | ||
io::Read, | ||
sync::{ | ||
|
@@ -37,11 +40,15 @@ use std::{ | |
}; | ||
use structopt::StructOpt; | ||
|
||
use oak_runtime::proto::oak::application::node_configuration::ConfigType::{ | ||
GrpcClientConfig, GrpcServerConfig, | ||
#[cfg(test)] | ||
mod tests; | ||
|
||
use oak_runtime::proto::oak::application::{ | ||
node_configuration::ConfigType::{GrpcClientConfig, GrpcServerConfig}, | ||
ConfigMap, | ||
}; | ||
|
||
#[derive(StructOpt, Clone)] | ||
#[derive(StructOpt, Clone, Debug)] | ||
#[structopt(about = "Oak Loader")] | ||
pub struct Opt { | ||
#[structopt(long, help = "Application configuration file.")] | ||
|
@@ -70,24 +77,76 @@ pub struct Opt { | |
introspect_port: u16, | ||
#[structopt(long, help = "Starts the Runtime without an introspection server.")] | ||
no_introspect: bool, | ||
#[structopt( | ||
long, | ||
help = "Configuration files to expose to the Oak Application, each in key=filename format." | ||
)] | ||
config_files: Vec<ConfigEntry>, | ||
} | ||
|
||
fn read_file(filename: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> { | ||
/// A specification of a configuration entry as human readable key and a path to a file whose | ||
/// contents constitutes the actual value. | ||
#[derive(Clone, Debug, PartialEq, Eq)] | ||
pub struct ConfigEntry { | ||
key: String, | ||
filename: String, | ||
} | ||
|
||
impl FromStr for ConfigEntry { | ||
type Err = anyhow::Error; | ||
fn from_str(v: &str) -> Result<Self, Self::Err> { | ||
let parts = v.split('=').collect::<Vec<_>>(); | ||
if parts.len() != 2 { | ||
return Err(anyhow!("could not parse config entry: {}", v)); | ||
} | ||
Ok(ConfigEntry { | ||
key: parts[0].to_string(), | ||
filename: parts[1].to_string(), | ||
}) | ||
} | ||
} | ||
|
||
fn read_file(filename: &str) -> anyhow::Result<Vec<u8>> { | ||
let mut file = File::open(filename) | ||
.map_err(|error| format!("Failed to open file <{}>: {:?}", filename, error))?; | ||
.map_err(|error| anyhow!("Failed to open file <{}>: {:?}", filename, error))?; | ||
let mut buffer = Vec::new(); | ||
file.read_to_end(&mut buffer) | ||
.map_err(|error| format!("Failed to read file <{}>: {:?}", filename, error))?; | ||
.map_err(|error| anyhow!("Failed to read file <{}>: {:?}", filename, error))?; | ||
Ok(buffer) | ||
} | ||
|
||
fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
fn parse_config_files(config_entries: &[ConfigEntry]) -> anyhow::Result<HashMap<String, Vec<u8>>> { | ||
let mut file_map = HashMap::new(); | ||
for config_entry in config_entries { | ||
if file_map.contains_key(&config_entry.key) { | ||
return Err(anyhow!("duplicate config entry key: {}", config_entry.key)); | ||
} | ||
let file_content = read_file(&config_entry.filename)?; | ||
file_map.insert(config_entry.key.to_string(), file_content); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps log a warning if the key already exists and the value is being overwritten? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, done and added a test. |
||
} | ||
Ok(file_map) | ||
} | ||
|
||
pub fn parse_config_map(config_files: &[ConfigEntry]) -> anyhow::Result<ConfigMap> { | ||
Ok(ConfigMap { | ||
items: parse_config_files(config_files)?, | ||
}) | ||
} | ||
|
||
fn main() -> anyhow::Result<()> { | ||
if cfg!(feature = "oak_debug") { | ||
simple_logger::init_by_env(); | ||
} else { | ||
eprintln!("No debugging output configured at build time"); | ||
} | ||
let opt = Opt::from_args(); | ||
debug!("parsed opts: {:?}", opt); | ||
|
||
let config_map = parse_config_map(&opt.config_files)?; | ||
// We only log the keys here, since the values may be secret. | ||
debug!("parsed config map entries: {:?}", config_map.items.keys()); | ||
// TODO(#689): Pass the `config_map` object to the Runtime instance, and make it available to | ||
// the running Oak Application. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not clear on how this will be used. Is the intention to make this available to wasm nodes? Or to dynamically replace tokens in the application configuration? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The intention is to make it available to the first node of an Oak application, which can then take it apart and distribute it to further nodes if it needs to. It will be easier after #917 is merged, so I'll wait for that before doing the rest of the work. |
||
|
||
// Load application configuration. | ||
let app_config_data = read_file(&opt.application)?; | ||
|
@@ -129,7 +188,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { | |
// Start the Runtime from the given config. | ||
info!("starting Runtime, config {:?}", runtime_config); | ||
let (runtime, initial_handle) = configure_and_run(app_config, runtime_config) | ||
.map_err(|status| format!("status {:?}", status))?; | ||
.map_err(|status| anyhow!("status {:?}", status))?; | ||
info!( | ||
"initial node {:?} with write handle {:?}", | ||
runtime.node_id, initial_handle | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
// | ||
// Copyright 2020 The Project Oak Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// | ||
|
||
use super::*; | ||
use maplit::hashmap; | ||
|
||
#[test] | ||
fn parse_config_entry_ok() { | ||
assert_eq!( | ||
ConfigEntry { | ||
key: "foo".to_string(), | ||
filename: "/dev/null".to_string() | ||
}, | ||
"foo=/dev/null" | ||
.parse::<ConfigEntry>() | ||
.expect("could not parse config entry") | ||
); | ||
} | ||
|
||
#[test] | ||
fn parse_config_entry_multiple_equals_err() { | ||
assert_eq!( | ||
false, | ||
"foo=/dev/null=/foo/bar".parse::<ConfigEntry>().is_ok() | ||
); | ||
} | ||
|
||
#[test] | ||
fn parse_config_entry_missing_equals_err() { | ||
assert_eq!(false, "/dev/null".parse::<ConfigEntry>().is_ok()); | ||
} | ||
|
||
#[test] | ||
fn parse_config_map_multiple_ok() { | ||
let result = parse_config_map(&[ | ||
ConfigEntry { | ||
key: "foo".to_string(), | ||
filename: "/dev/null".to_string(), | ||
}, | ||
ConfigEntry { | ||
key: "authors".to_string(), | ||
filename: "../../../../AUTHORS".to_string(), | ||
}, | ||
]); | ||
assert_eq!( | ||
ConfigMap { | ||
items: hashmap! { | ||
"foo".to_string() => vec![], | ||
"authors".to_string() => b"# This is the list of Project Oak authors for copyright purposes. | ||
# | ||
# This does not necessarily list everyone who has contributed code, since in | ||
# some cases, their employer may be the copyright holder. To see the full list | ||
# of contributors, see the revision history in source control. | ||
Google LLC | ||
".iter().cloned().collect(), | ||
}, | ||
}, | ||
result.expect("could not parse config") | ||
); | ||
} | ||
|
||
#[test] | ||
fn parse_config_map_dev_null_ok() { | ||
let result = parse_config_map(&[ConfigEntry { | ||
key: "foo".to_string(), | ||
filename: "/dev/null".to_string(), | ||
}]); | ||
assert_eq!( | ||
ConfigMap { | ||
items: hashmap! { | ||
"foo".to_string() => vec![], | ||
}, | ||
}, | ||
result.expect("could not parse config") | ||
); | ||
} | ||
|
||
#[test] | ||
fn parse_config_map_duplicate_key_err() { | ||
let result = parse_config_map(&[ | ||
ConfigEntry { | ||
key: "foo".to_string(), | ||
filename: "/dev/null".to_string(), | ||
}, | ||
ConfigEntry { | ||
key: "foo".to_string(), | ||
filename: "/dev/null".to_string(), | ||
}, | ||
]); | ||
assert_eq!(false, result.is_ok()); | ||
} | ||
|
||
#[test] | ||
fn parse_config_map_non_existing_file_err() { | ||
let result = parse_config_map(&[ConfigEntry { | ||
key: "foo".to_string(), | ||
filename: "/non-existing-file".to_string(), | ||
}]); | ||
assert_eq!(false, result.is_ok()); | ||
} | ||
|
||
#[test] | ||
fn parse_config_map_directory_err() { | ||
let result = parse_config_map(&[ConfigEntry { | ||
key: "foo".to_string(), | ||
// Directory. | ||
filename: "/etc".to_string(), | ||
}]); | ||
assert_eq!(false, result.is_ok()); | ||
} | ||
|
||
#[test] | ||
fn parse_config_map_no_permission_err() { | ||
let result = parse_config_map(&[ConfigEntry { | ||
key: "foo".to_string(), | ||
// File only readable by the root user. | ||
filename: "/etc/sudoers".to_string(), | ||
}]); | ||
assert_eq!(false, result.is_ok()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should also be added to
oak/server/rust/oak_loader/BUILD
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.