kubos_system/config.rs
1//
2// Copyright (C) 2018 Kubos Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License")
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16use failure::{bail, Error};
17use serde_derive::Deserialize;
18use std::env;
19use std::fs::File;
20use std::io;
21use std::io::prelude::*;
22
23use toml::Value;
24
25/// The default configuration file path
26pub static DEFAULT_PATH: &str = "/etc/kubos-config.toml";
27
28#[derive(Clone, Debug, Deserialize)]
29/// A simple address consisting of an IP address and port number
30pub struct Address {
31 ip: String,
32 port: u16,
33}
34
35impl Address {
36 /// Returns the IP portion of this address
37 pub fn ip(&self) -> &str {
38 &self.ip
39 }
40
41 /// Returns the port of this address
42 pub fn port(&self) -> u16 {
43 self.port
44 }
45}
46
47/// KubOS config used by either Apps or Services. KubOS config files use the TOML format, and can
48/// may contain multiple named Categories. Typically each category corresponds to an App or Service
49/// name. This allows one config file to store configuration for multiple Apps / Services at a
50/// time.
51///
52/// Example KubOS config files for a Service called `my-service` with an IP/port binding
53/// ```toml
54/// [my-service]
55/// my-property = "value"
56///
57/// [my-service.addr]
58/// ip = 0.0.0.0
59/// port = 8181
60/// ```
61///
62#[derive(Clone, Debug)]
63pub struct Config {
64 addr: Option<Address>,
65 raw: Value,
66}
67
68impl Default for Config {
69 fn default() -> Self {
70 Config {
71 addr: None,
72 raw: Value::String("".to_string()),
73 }
74 }
75}
76
77impl Config {
78 /// Creates and parses configuration data from the system configuration
79 /// file or the path passed as the '-c' or '--config' option to this
80 /// executable.
81 ///
82 /// # Arguments
83 /// `name` - Category name used as a key in the config file
84 pub fn new(name: &str) -> Result<Self, Error> {
85 Self::new_from_path(name, get_config_path()?)
86 }
87
88 /// Creates and parses configuration data from the passed in configuration
89 /// path.
90 /// # Arguments
91 /// `name` - Category name used as a key in the config file
92 /// `path` - Path to configuration file
93 pub fn new_from_path(name: &str, path: String) -> Result<Self, Error> {
94 parse_config_file(name, path)
95 }
96
97 /// Creates and parses configuration data from the passed in configuration
98 /// string.
99 /// # Arguments
100 /// `name` - Category name used as a key in the config
101 /// `config` - Config data as a string
102 pub fn new_from_str(name: &str, config: &str) -> Result<Self, Error> {
103 parse_config_str(name, config)
104 }
105
106 /// Returns the configured hosturl string in the following
107 /// format (using IPv4 addresses) - 0.0.0.0:0000
108 pub fn hosturl(&self) -> Option<String> {
109 self.addr
110 .as_ref()
111 .map(|addr| format!("{}:{}", addr.ip(), addr.port()))
112 }
113
114 /// Returns the category's configuration information
115 /// in the `toml::Value` format.
116 /// This will contain the ip/port if provided, along with any other
117 /// configuration information found in the config file.
118 ///
119 /// ### Examples
120 ///
121 /// ```rust,no_run
122 /// use kubos_system::Config;
123 ///
124 /// let config = Config::new("example-service").unwrap();
125 /// let raw = config.raw();
126 /// let bus = raw["bus"].as_str();
127 /// ```
128 pub fn raw(&self) -> toml::Value {
129 self.raw.clone()
130 }
131
132 /// Performs a get on the raw config data
133 ///
134 /// # Arguments
135 /// `key` - Key of value to get from config
136 pub fn get(&self, key: &str) -> Option<toml::Value> {
137 self.raw.get(key).cloned()
138 }
139
140 //// Get config data encoded as a hexadecimal string
141 ///
142 /// * Arguments
143 /// `key` - Key of value to get from config
144 pub fn get_hex(&self, key: &str) -> Option<u8> {
145 let val = self.raw.get(key)?.as_str()?;
146 let val = val.strip_prefix("0x").unwrap_or(val);
147
148 u8::from_str_radix(val, 16).ok()
149 }
150}
151
152fn get_config_path() -> Result<String, Error> {
153 // Manually check for a "-c {config-path}" command line argument specifying a custom config
154 // file path to use.
155 // Doing it this way so that entities which use this module (apps, services) can have any
156 // number of additional command arguments
157 let mut args = env::args();
158
159 // Navigate to the "-c" option
160 let config_arg_pos = args.position(|arg| arg == "-c");
161
162 if let Some(_pos) = config_arg_pos {
163 // The config path will be the arg immediately after "-c"
164 match args.next() {
165 Some(path) => Ok(path),
166 None => bail!("The '-c' arg was specified, but no path value was provided"),
167 }
168 } else {
169 // The "-c" arg wasn't specified, so we can go ahead with the default
170 Ok(DEFAULT_PATH.to_string())
171 }
172}
173
174fn get_file_data(path: String) -> Result<String, io::Error> {
175 let mut contents = String::new();
176 let mut file = File::open(path)?;
177 file.read_to_string(&mut contents)?;
178 Ok(contents)
179}
180
181fn parse_config_file(name: &str, path: String) -> Result<Config, Error> {
182 let contents = get_file_data(path)?;
183 parse_config_str(name, &contents)
184}
185
186fn parse_config_str(name: &str, contents: &str) -> Result<Config, Error> {
187 let data: Value = toml::from_str(contents)?;
188 let mut config = Config::default();
189
190 if let Some(data) = data.get(name) {
191 if let Some(address) = data.get("addr") {
192 config.addr = Some(address.clone().try_into()?);
193 }
194 config.raw = data.clone();
195 } else {
196 bail!("Failed to find {} in config", name);
197 }
198
199 Ok(config)
200}