├── .dockerignore
├── tests
└── fixtures
│ ├── projects
│ ├── rendered
│ │ ├── project-1
│ │ │ ├── Web.EMPTY.config
│ │ │ ├── Web.TEST.yaml
│ │ │ ├── Web.EMPTY.yaml
│ │ │ ├── Web.ENVTYPE.yaml
│ │ │ ├── Web.TEST2.yaml
│ │ │ ├── Web.template.yaml
│ │ │ ├── Web.TEST.config
│ │ │ ├── Web.TEST2.config
│ │ │ ├── Web.ENVTYPE.config
│ │ │ └── Web.template.config
│ │ └── project-2
│ │ │ ├── Web.EMPTY.config
│ │ │ ├── Web.TEST.config
│ │ │ ├── Web.TEST2.config
│ │ │ ├── Web.ENVTYPE.config
│ │ │ └── Web.template.config
│ └── templates
│ │ ├── .gitignore
│ │ ├── project-3
│ │ └── .template.yaml.swp
│ │ ├── project-4
│ │ ├── template.yaml
│ │ ├── template.config
│ │ └── template.properties
│ │ ├── project-1
│ │ ├── Web.template.yaml
│ │ └── Web.template.config
│ │ └── project-2
│ │ └── Web.template.config
│ └── configs
│ ├── envTypes
│ └── alpha.json
│ ├── config.TEST.json
│ ├── config.TEST2.json
│ ├── config.EMPTY.json
│ └── config.ENVTYPE.json
├── src
├── storage
│ ├── mod.rs
│ ├── cache.rs
│ ├── lru.rs
│ └── sqlite.rs
├── app
│ ├── mod.rs
│ ├── datadogstatsd.rs
│ ├── fetch_actor.rs
│ ├── config.rs
│ ├── cli.rs
│ ├── head_actor.rs
│ └── server.rs
├── lib.rs
├── transform
│ ├── helper_lowercase.rs
│ ├── helper_url_rm_slash.rs
│ ├── helper_yaml_string.rs
│ ├── helper_url_add_slash.rs
│ ├── helper_equal.rs
│ ├── helper_or.rs
│ ├── helper_url_rm_path.rs
│ ├── helper_comma_delimited_list.rs
│ └── mod.rs
├── error.rs
├── main.rs
├── template.rs
├── git.rs
└── config.rs
├── .vscode
└── settings.json
├── .cargo
└── config
├── README.md.skt.md
├── .gitignore
├── Cargo.toml
├── Dockerfile
├── .github
└── workflows
│ ├── publish.yml
│ └── ci.yml
├── README.md
├── CHANGELOG.md
└── LICENSE
/.dockerignore:
--------------------------------------------------------------------------------
1 | target
--------------------------------------------------------------------------------
/tests/fixtures/projects/rendered/project-1/Web.EMPTY.config:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/fixtures/projects/rendered/project-2/Web.EMPTY.config:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/storage/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod cache;
2 | pub mod lru;
3 | pub mod sqlite;
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "WhiteSource Advise.Diff.BaseBranch": "master"
3 | }
4 |
--------------------------------------------------------------------------------
/tests/fixtures/projects/templates/.gitignore:
--------------------------------------------------------------------------------
1 | *.*
2 | !*.template.*
3 | !.gitignore
4 | !template.*
--------------------------------------------------------------------------------
/tests/fixtures/projects/templates/project-3/.template.yaml.swp:
--------------------------------------------------------------------------------
1 | I'm just here to test that .swp files don't get picked up
2 |
--------------------------------------------------------------------------------
/src/app/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod cli;
2 | pub mod config;
3 | pub mod datadogstatsd;
4 | mod fetch_actor;
5 | mod head_actor;
6 | pub mod server;
7 |
--------------------------------------------------------------------------------
/tests/fixtures/configs/envTypes/alpha.json:
--------------------------------------------------------------------------------
1 | {
2 | "EnvironmentType": "alpha",
3 | "ConfigData": {
4 | "EnvironmentType": "alpha"
5 | }
6 | }
--------------------------------------------------------------------------------
/.cargo/config:
--------------------------------------------------------------------------------
1 | [target.x86_64-pc-windows-msvc]
2 | rustflags = ["-Ctarget-feature=+crt-static"]
3 |
4 | [build]
5 | rustflags = ["--cfg", "tokio_unstable"]
--------------------------------------------------------------------------------
/tests/fixtures/projects/templates/project-4/template.yaml:
--------------------------------------------------------------------------------
1 | #testing that templates starting with `template` and ending with `.yaml` get picked up by the regex
--------------------------------------------------------------------------------
/tests/fixtures/projects/templates/project-4/template.config:
--------------------------------------------------------------------------------
1 | #testing that templates starting with `template` and ending with `.config` get picked up by the regex
--------------------------------------------------------------------------------
/tests/fixtures/projects/templates/project-4/template.properties:
--------------------------------------------------------------------------------
1 | #testing that templates starting with `template` and ending with `.properties` get picked up by the regex
--------------------------------------------------------------------------------
/tests/fixtures/projects/rendered/project-1/Web.TEST.yaml:
--------------------------------------------------------------------------------
1 | Database:
2 | Endpoint: host-name\\TEST\"
3 | remove-slash: https://slash.com
4 | add-slash: https://nonslash.com/
5 | no-slash: no-protocol.no-slash.com
6 | remove-path: https://path.com/path
7 | trailing-slash-remove-path: https://trailing-path.com/path
8 | lowercase: uppercase
9 |
--------------------------------------------------------------------------------
/tests/fixtures/projects/rendered/project-1/Web.EMPTY.yaml:
--------------------------------------------------------------------------------
1 | Database:
2 | Endpoint: host-name\\TEST\"
3 | remove-slash: https://slash.com
4 | add-slash: https://nonslash.com/
5 | no-slash: no-protocol.no-slash.com
6 | remove-path: https://path.com/path
7 | trailing-slash-remove-path: https://trailing-path.com/path
8 | lowercase: uppercase
9 |
--------------------------------------------------------------------------------
/tests/fixtures/projects/rendered/project-1/Web.ENVTYPE.yaml:
--------------------------------------------------------------------------------
1 | Database:
2 | Endpoint: host-name\\TEST\"
3 | remove-slash: https://slash.com
4 | add-slash: https://nonslash.com/
5 | no-slash: no-protocol.no-slash.com
6 | remove-path: https://path.com/path
7 | trailing-slash-remove-path: https://trailing-path.com/path
8 | lowercase: uppercase
9 |
--------------------------------------------------------------------------------
/tests/fixtures/projects/rendered/project-1/Web.TEST2.yaml:
--------------------------------------------------------------------------------
1 | Database:
2 | Endpoint: host-name\\TEST\"
3 | remove-slash: https://slash.com
4 | add-slash: https://nonslash.com/
5 | no-slash: no-protocol.no-slash.com
6 | remove-path: https://path.com/path
7 | trailing-slash-remove-path: https://trailing-path.com/path
8 | lowercase: uppercase
9 |
--------------------------------------------------------------------------------
/README.md.skt.md:
--------------------------------------------------------------------------------
1 | ```rust,skt-helpers
2 | extern crate hogan;
3 | #[macro_use]
4 | extern crate serde_json;
5 |
6 | fn main() {{
7 | let handlebars = hogan::transform::handlebars();
8 |
9 | {}
10 |
11 | let rendered = handlebars.render_template(template, &config);
12 | assert!(rendered.is_ok());
13 | assert_eq!(&rendered.unwrap(), transformed);
14 | }}
15 | ```
--------------------------------------------------------------------------------
/tests/fixtures/projects/rendered/project-1/Web.template.yaml:
--------------------------------------------------------------------------------
1 | Database:
2 | Endpoint: {{yaml-string DB.Endpoint}}
3 | remove-slash: {{url-rm-slash SlashService.endpoint}}
4 | add-slash: {{url-add-slash NonSlashService.endpoint}}
5 | no-slash: {{url-add-slash NonSlashService.notAnEndpoint}}
6 | remove-path: {{url-rm-path PathService.endpoint}}
7 | trailing-slash-remove-path: {{url-rm-path PathService.trailingSlash}}
8 | lowercase: {{lowercase UpperCaseString}}
9 |
--------------------------------------------------------------------------------
/tests/fixtures/projects/templates/project-1/Web.template.yaml:
--------------------------------------------------------------------------------
1 | Database:
2 | Endpoint: {{yaml-string DB.Endpoint}}
3 | remove-slash: {{url-rm-slash SlashService.endpoint}}
4 | add-slash: {{url-add-slash NonSlashService.endpoint}}
5 | no-slash: {{url-add-slash NonSlashService.notAnEndpoint}}
6 | remove-path: {{url-rm-path PathService.endpoint}}
7 | trailing-slash-remove-path: {{url-rm-path PathService.trailingSlash}}
8 | lowercase: {{lowercase UpperCaseString}}
9 |
--------------------------------------------------------------------------------
/tests/fixtures/projects/rendered/project-1/Web.TEST.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tests/fixtures/projects/rendered/project-2/Web.TEST.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tests/fixtures/projects/rendered/project-1/Web.TEST2.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tests/fixtures/projects/rendered/project-2/Web.TEST2.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tests/fixtures/projects/rendered/project-1/Web.ENVTYPE.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tests/fixtures/projects/rendered/project-2/Web.ENVTYPE.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/tests/fixtures/projects/rendered/project-1/Web.template.config:
--------------------------------------------------------------------------------
1 | {{#if ConfigEnabled}}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{/if}}
--------------------------------------------------------------------------------
/tests/fixtures/projects/rendered/project-2/Web.template.config:
--------------------------------------------------------------------------------
1 | {{#if ConfigEnabled}}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{/if}}
--------------------------------------------------------------------------------
/tests/fixtures/projects/templates/project-1/Web.template.config:
--------------------------------------------------------------------------------
1 | {{#if ConfigEnabled}}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{/if}}
--------------------------------------------------------------------------------
/tests/fixtures/projects/templates/project-2/Web.template.config:
--------------------------------------------------------------------------------
1 | {{#if ConfigEnabled}}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | {{/if}}
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | #[macro_use]
2 | extern crate log;
3 | #[macro_use]
4 | extern crate serde_derive;
5 |
6 | pub mod config;
7 | pub mod error;
8 | pub mod git;
9 | pub mod template;
10 | pub mod transform;
11 |
12 | use regex::Regex;
13 | use std::path::{Path, PathBuf};
14 | use walkdir::{DirEntry, WalkDir};
15 |
16 | pub fn find_file_paths(path: &Path, filter: Regex) -> Box> {
17 | fn match_filter(entry: &DirEntry, filter: &Regex) -> bool {
18 | entry
19 | .file_name()
20 | .to_str()
21 | .map(|s| filter.is_match(s))
22 | .unwrap_or(false)
23 | }
24 |
25 | println!("Finding Files: {:?}", path);
26 | println!("regex: /{}/", filter);
27 |
28 | Box::new(
29 | WalkDir::new(path)
30 | .into_iter()
31 | .filter_map(|e| e.ok())
32 | .filter(|e| e.file_type().is_file())
33 | .filter(move |e| match_filter(e, &filter))
34 | .map(|e| e.path().to_path_buf()),
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/tests/fixtures/configs/config.TEST.json:
--------------------------------------------------------------------------------
1 | {
2 | "Environment": "TEST",
3 | "ConfigData": {
4 | "ConfigEnabled": true,
5 | "Region": {
6 | "Key": "TEST"
7 | },
8 | "DB": {
9 | "Endpoint": "host-name\\TEST\""
10 | },
11 | "Memcache": {
12 | "Servers": [
13 | {
14 | "Endpoint": "192.168.1.100",
15 | "Port": "1122"
16 | },
17 | {
18 | "Endpoint": "192.168.1.101",
19 | "Port": "1122"
20 | },
21 | {
22 | "Endpoint": "192.168.1.102",
23 | "Port": "1122"
24 | }
25 | ]
26 | },
27 | "NonSlashService": {
28 | "endpoint": "https://nonslash.com",
29 | "notAnEndpoint": "no-protocol.no-slash.com"
30 | },
31 | "SlashService": {
32 | "endpoint": "https://slash.com/"
33 | },
34 | "PathService": {
35 | "endpoint": "https://path.com/path/remove-this",
36 | "trailingSlash": "https://trailing-path.com/path/should-still-remove/"
37 | },
38 | "UpperCaseString": "UPPERCASE"
39 | }
40 | }
--------------------------------------------------------------------------------
/tests/fixtures/configs/config.TEST2.json:
--------------------------------------------------------------------------------
1 | {
2 | "Environment": "TEST2",
3 | "ConfigData": {
4 | "ConfigEnabled": true,
5 | "Region": {
6 | "Key": "TEST2"
7 | },
8 | "DB": {
9 | "Endpoint": "host-name\\TEST\""
10 | },
11 | "Memcache": {
12 | "Servers": [
13 | {
14 | "Endpoint": "192.168.1.100",
15 | "Port": "1122"
16 | },
17 | {
18 | "Endpoint": "192.168.1.101",
19 | "Port": "1122"
20 | },
21 | {
22 | "Endpoint": "192.168.1.102",
23 | "Port": "1122"
24 | }
25 | ]
26 | },
27 | "NonSlashService": {
28 | "endpoint": "https://nonslash.com",
29 | "notAnEndpoint": "no-protocol.no-slash.com"
30 | },
31 | "SlashService": {
32 | "endpoint": "https://slash.com/"
33 | },
34 | "PathService": {
35 | "endpoint": "https://path.com/path/remove-this",
36 | "trailingSlash": "https://trailing-path.com/path/should-still-remove/"
37 | },
38 | "UpperCaseString": "UPPERCASE"
39 | }
40 | }
--------------------------------------------------------------------------------
/tests/fixtures/configs/config.EMPTY.json:
--------------------------------------------------------------------------------
1 | {
2 | "Environment": "EMPTY",
3 | "ConfigData": {
4 | "ConfigEnabled": false,
5 | "Region": {
6 | "Key": "EMPTY"
7 | },
8 | "DB": {
9 | "Endpoint": "host-name\\TEST\""
10 | },
11 | "Memcache": {
12 | "Servers": [
13 | {
14 | "Endpoint": "192.168.1.100",
15 | "Port": "1122"
16 | },
17 | {
18 | "Endpoint": "192.168.1.101",
19 | "Port": "1122"
20 | },
21 | {
22 | "Endpoint": "192.168.1.102",
23 | "Port": "1122"
24 | }
25 | ]
26 | },
27 | "NonSlashService": {
28 | "endpoint": "https://nonslash.com",
29 | "notAnEndpoint": "no-protocol.no-slash.com"
30 | },
31 | "SlashService": {
32 | "endpoint": "https://slash.com/"
33 | },
34 | "PathService": {
35 | "endpoint": "https://path.com/path/remove-this",
36 | "trailingSlash": "https://trailing-path.com/path/should-still-remove/"
37 | },
38 | "UpperCaseString": "UPPERCASE"
39 | }
40 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 |
5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
7 | # Cargo.lock
8 |
9 | # These are backup files generated by rustfmt
10 | **/*.rs.bk
11 |
12 | # Avoid checking in db files
13 | *.db
14 | *.json
15 |
16 | # Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode
17 | # Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode
18 |
19 | ### VisualStudioCode ###
20 | .vscode/*
21 | !.vscode/settings.json
22 | !.vscode/tasks.json
23 | !.vscode/launch.json
24 | !.vscode/extensions.json
25 | !.vscode/*.code-snippets
26 |
27 | # Local History for Visual Studio Code
28 | .history/
29 |
30 | # Built Visual Studio Code Extensions
31 | *.vsix
32 |
33 | ### VisualStudioCode Patch ###
34 | # Ignore all local history of files
35 | .history
36 | .ionide
37 |
38 | # End of https://www.toptal.com/developers/gitignore/api/visualstudiocode
39 |
--------------------------------------------------------------------------------
/tests/fixtures/configs/config.ENVTYPE.json:
--------------------------------------------------------------------------------
1 | {
2 | "Environment": "ENVTYPE",
3 | "EnvironmentType": "alpha",
4 | "ConfigData": {
5 | "ConfigEnabled": true,
6 | "Region": {
7 | "Key": "ENVTYPE"
8 | },
9 | "DB": {
10 | "Endpoint": "host-name\\TEST\""
11 | },
12 | "Memcache": {
13 | "Servers": [
14 | {
15 | "Endpoint": "192.168.1.100",
16 | "Port": "1122"
17 | },
18 | {
19 | "Endpoint": "192.168.1.101",
20 | "Port": "1122"
21 | },
22 | {
23 | "Endpoint": "192.168.1.102",
24 | "Port": "1122"
25 | }
26 | ]
27 | },
28 | "NonSlashService": {
29 | "endpoint": "https://nonslash.com",
30 | "notAnEndpoint": "no-protocol.no-slash.com"
31 | },
32 | "SlashService": {
33 | "endpoint": "https://slash.com/"
34 | },
35 | "PathService": {
36 | "endpoint": "https://path.com/path/remove-this",
37 | "trailingSlash": "https://trailing-path.com/path/should-still-remove/"
38 | },
39 | "UpperCaseString": "UPPERCASE"
40 | }
41 | }
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [[bin]]
2 | name = 'hogan'
3 | path = 'src/main.rs'
4 | doc = false
5 |
6 | [package]
7 | name = 'hogan'
8 | version = '0.15.0'
9 | authors = [
10 | 'Jonathan Morley ',
11 | 'Josh Comer ',
12 | ]
13 | edition = '2021'
14 |
15 | [dependencies]
16 | actix-web = '4.3'
17 | anyhow = '1.0'
18 | bincode = '1.3'
19 | compression = '0.1'
20 | dogstatsd = '0.7'
21 | futures = '0.3'
22 | handlebars = '4.3'
23 | itertools = '0.10'
24 | json-patch = '0.3'
25 | lazy_static = '1'
26 | log = '0.4'
27 | lru = '0.9'
28 | parking_lot = '0.12'
29 | riker = '0.4'
30 | riker-patterns = '0.4'
31 | serde_derive = '1.0'
32 | serde_json = '1.0'
33 | shellexpand = '3.0'
34 | stderrlog = '0.5'
35 | structopt = '0.3'
36 | tempfile = '3'
37 | thiserror = '1.0'
38 | url = '2'
39 | walkdir = '2'
40 | zip = '0.6'
41 |
42 | [dependencies.rusqlite]
43 | version = '0.28'
44 | features = ['bundled']
45 |
46 | [dependencies.git2]
47 | version = '0.16'
48 | features = ['vendored-openssl']
49 |
50 | [dependencies.regex]
51 | version = '1.7'
52 | default-features = false
53 |
54 | [dependencies.serde]
55 | version = '1.0'
56 | features = ['rc']
57 |
58 | [dependencies.uuid]
59 | version = '1.2'
60 | features = ['v4']
61 |
62 | [dev-dependencies]
63 | assert_cmd = '2.0'
64 | dir-diff = '0.3'
65 | fs_extra = '1'
66 | predicates = '2.1'
67 |
--------------------------------------------------------------------------------
/src/transform/helper_lowercase.rs:
--------------------------------------------------------------------------------
1 | use handlebars::*;
2 | use serde_json::value::Value as Json;
3 |
4 | #[derive(Clone, Copy)]
5 | pub struct LowercaseHelper;
6 |
7 | impl HelperDef for LowercaseHelper {
8 | fn call<'reg: 'rc, 'rc, 'ctx>(
9 | &self,
10 | h: &Helper<'reg, 'rc>,
11 | _: &'reg Handlebars,
12 | _: &'ctx Context,
13 | _: &mut RenderContext<'reg, 'ctx>,
14 | out: &mut dyn Output,
15 | ) -> HelperResult {
16 | let value = h
17 | .param(0)
18 | .ok_or_else(|| RenderError::new("Param not found for helper \"lowercase\""))?;
19 |
20 | match *value.value() {
21 | Json::String(ref s) => {
22 | out.write(&s.to_lowercase())?;
23 | Ok(())
24 | }
25 | Json::Null => Ok(()),
26 | _ => Err(RenderError::new(format!(
27 | "Param type is not string for helper \"lowercase\": {:?}",
28 | value
29 | ))),
30 | }
31 | }
32 | }
33 |
34 | #[cfg(test)]
35 | mod test {
36 | use super::*;
37 | use crate::transform::test::test_against_configs;
38 |
39 | #[test]
40 | fn test_lowercase() {
41 | let mut handlebars = Handlebars::new();
42 | handlebars.register_helper("lowercase", Box::new(LowercaseHelper));
43 |
44 | test_against_configs(&handlebars, "{{lowercase UpperCaseString}}", "uppercase");
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # We need to use the Rust build image, because
2 | # we need the Rust compile and Cargo tooling
3 | FROM clux/muslrust:stable as build
4 |
5 | # Install cmake as it is not included in muslrust, but is needed by libssh2-sys
6 | RUN apt-get update && apt-get install -y \
7 | cmake \
8 | --no-install-recommends && \
9 | rm -rf /var/lib/apt/lists/*
10 |
11 | WORKDIR /app
12 | # Creates a dummy project used to grab dependencies
13 | RUN USER=root cargo init --bin
14 |
15 | # Copies over *only* your manifests
16 | COPY ./Cargo.* ./
17 |
18 | # Builds your dependencies and removes the
19 | # fake source code from the dummy project
20 | RUN cargo build --release
21 | RUN rm src/*.rs
22 | RUN rm target/x86_64-unknown-linux-musl/release/hogan
23 |
24 | # Copies only your actual source code to
25 | # avoid invalidating the cache at all
26 | COPY ./src ./src
27 |
28 | # Builds again, this time it'll just be
29 | # your actual source files being built
30 | RUN cargo build --release
31 |
32 | FROM alpine:latest as certs
33 | RUN apk --update add ca-certificates
34 |
35 | # Create a new stage with a minimal image
36 | # because we already have a binary built
37 | FROM alpine:latest
38 |
39 | RUN apk --no-cache add git openssh
40 |
41 | # Copies standard SSL certs from the "build" stage
42 | COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
43 |
44 | # Copies the binary from the "build" stage
45 | COPY --from=build /app/target/x86_64-unknown-linux-musl/release/hogan /bin/
46 |
47 | # Configures the startup!
48 | ENTRYPOINT ["/bin/hogan"]
49 |
--------------------------------------------------------------------------------
/src/error.rs:
--------------------------------------------------------------------------------
1 | use thiserror::Error;
2 |
3 | #[derive(Debug, Error, Clone)]
4 | pub enum HoganError {
5 | #[error("There was an error with the underlying git repository. {msg}")]
6 | GitError { msg: String },
7 | #[error("The requested SHA {sha} was not found in the git repo")]
8 | UnknownSHA { sha: String },
9 | #[error("The requested branch {branch} was not found in the git repo")]
10 | UnknownBranch { branch: String },
11 | #[error("The requested environment {env} was not found in {sha}")]
12 | UnknownEnvironment { sha: String, env: String },
13 | #[error("There was a problem with the provided template")]
14 | InvalidTemplate { msg: String, env: String },
15 | #[error("The request was malformed")]
16 | BadRequest,
17 | #[error("Request timed out due to internal congestion")]
18 | InternalTimeout,
19 | #[error("An error occurred parsing configuration {param}: {msg}")]
20 | InvalidConfiguration { param: String, msg: String },
21 | #[error("An unknown error occurred. {msg}")]
22 | UnknownError { msg: String },
23 | }
24 |
25 | impl From for HoganError {
26 | fn from(e: git2::Error) -> Self {
27 | HoganError::GitError {
28 | msg: e.message().to_owned(),
29 | }
30 | }
31 | }
32 |
33 | impl From for HoganError {
34 | fn from(e: anyhow::Error) -> Self {
35 | match e.downcast() {
36 | Ok(e) => e,
37 | Err(e) => {
38 | warn!("Bad cast to a HoganError {:?}", e);
39 | HoganError::UnknownError {
40 | msg: format!("Error {:?}", e),
41 | }
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/transform/helper_url_rm_slash.rs:
--------------------------------------------------------------------------------
1 | use handlebars::*;
2 | use serde_json::value::Value as Json;
3 |
4 | #[derive(Clone, Copy)]
5 | pub struct UrlRmSlashHelper;
6 |
7 | impl HelperDef for UrlRmSlashHelper {
8 | // Removes the trailing slash on an endpoint
9 | fn call<'reg: 'rc, 'rc, 'ctx>(
10 | &self,
11 | h: &Helper<'reg, 'rc>,
12 | _: &'reg Handlebars,
13 | _: &'ctx Context,
14 | _: &mut RenderContext<'reg, 'ctx>,
15 | out: &mut dyn Output,
16 | ) -> HelperResult {
17 | let value = h
18 | .param(0)
19 | .ok_or_else(|| RenderError::new("Param not found for helper \"url-rm-slash\""))?;
20 |
21 | match *value.value() {
22 | Json::String(ref s) => {
23 | if s.ends_with('/') {
24 | out.write(&s[..s.len() - 1])?;
25 | } else {
26 | out.write(s)?;
27 | }
28 |
29 | Ok(())
30 | }
31 | Json::Null => Ok(()),
32 | _ => Err(RenderError::new(format!(
33 | "Param type is not string for helper \"url-rm-slash\": {:?}",
34 | value,
35 | ))),
36 | }
37 | }
38 | }
39 |
40 | #[cfg(test)]
41 | mod test {
42 | use super::*;
43 | use crate::transform::test::test_against_configs;
44 |
45 | #[test]
46 | fn test_url_rm_slash() {
47 | let mut handlebars = Handlebars::new();
48 | handlebars.register_helper("url-rm-slash", Box::new(UrlRmSlashHelper));
49 |
50 | test_against_configs(
51 | &handlebars,
52 | "{{url-rm-slash SlashService.endpoint}}",
53 | "https://slash.com",
54 | );
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | #[macro_use]
2 | extern crate log;
3 | #[macro_use]
4 | extern crate lazy_static;
5 |
6 | use crate::app::cli;
7 | use crate::app::config::{App, AppCommand};
8 | use crate::app::server;
9 | use anyhow::{Context, Result};
10 |
11 | use structopt::StructOpt;
12 |
13 | mod app;
14 | mod storage;
15 |
16 | fn main() -> Result<()> {
17 | let opt = App::from_args();
18 |
19 | stderrlog::new()
20 | .module(module_path!())
21 | .verbosity(opt.verbosity + 2)
22 | .timestamp(stderrlog::Timestamp::Millisecond)
23 | .init()
24 | .with_context(|| "Error initializing logging")?;
25 |
26 | match opt.cmd {
27 | AppCommand::Transform {
28 | templates_path,
29 | environments_regex,
30 | templates_regex,
31 | common,
32 | ignore_existing,
33 | } => {
34 | cli::cli(
35 | templates_path,
36 | environments_regex,
37 | templates_regex,
38 | common,
39 | ignore_existing,
40 | )?;
41 | }
42 | AppCommand::Server {
43 | common,
44 | port,
45 | address,
46 | environments_regex,
47 | datadog,
48 | environment_pattern,
49 | db_path,
50 | fetch_poller,
51 | allow_fetch,
52 | db_max_age,
53 | cache_size,
54 | } => {
55 | server::start_up_server(
56 | common,
57 | port,
58 | address,
59 | environments_regex,
60 | datadog,
61 | environment_pattern,
62 | db_path,
63 | cache_size,
64 | fetch_poller,
65 | allow_fetch,
66 | db_max_age,
67 | )?;
68 | }
69 | }
70 |
71 | Ok(())
72 | }
73 |
--------------------------------------------------------------------------------
/src/transform/helper_yaml_string.rs:
--------------------------------------------------------------------------------
1 | use handlebars::*;
2 | use serde_json::value::Value as Json;
3 |
4 | #[derive(Clone, Copy)]
5 | pub struct YamlStringHelper;
6 |
7 | impl HelperDef for YamlStringHelper {
8 | // Escapes strings to that they can be safely used inside yaml (And JSON for that matter).
9 | fn call<'reg: 'rc, 'rc, 'ctx>(
10 | &self,
11 | h: &Helper<'reg, 'rc>,
12 | _: &'reg Handlebars,
13 | _: &'ctx Context,
14 | _: &mut RenderContext<'reg, 'ctx>,
15 | out: &mut dyn Output,
16 | ) -> HelperResult {
17 | let value = h
18 | .param(0)
19 | .ok_or_else(|| RenderError::new("Param not found for helper \"yaml-string\""))?;
20 |
21 | match *value.value() {
22 | ref s @ Json::String(_) => {
23 | let mut stringified = serde_json::to_string(&s).unwrap();
24 | if stringified.starts_with('"') {
25 | stringified.remove(0);
26 | }
27 |
28 | if stringified.ends_with('"') {
29 | stringified.pop();
30 | }
31 |
32 | out.write(&stringified)?;
33 |
34 | Ok(())
35 | }
36 | Json::Null => Ok(()),
37 | _ => Err(RenderError::new(format!(
38 | "Param type is not string for helper \"yaml-string\": {:?}",
39 | value,
40 | ))),
41 | }
42 | }
43 | }
44 |
45 | #[cfg(test)]
46 | mod test {
47 | use super::*;
48 | use crate::transform::test::test_against_configs;
49 |
50 | #[test]
51 | fn test_yaml_string() {
52 | let mut handlebars = Handlebars::new();
53 | handlebars.register_helper("yaml-string", Box::new(YamlStringHelper));
54 |
55 | test_against_configs(
56 | &handlebars,
57 | "{{yaml-string DB.Endpoint}}",
58 | r#"host-name\\TEST\""#,
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/transform/helper_url_add_slash.rs:
--------------------------------------------------------------------------------
1 | use handlebars::*;
2 | use serde_json::value::Value as Json;
3 | use url::Url;
4 |
5 | #[derive(Clone, Copy)]
6 | pub struct UrlAddSlashHelper;
7 |
8 | impl HelperDef for UrlAddSlashHelper {
9 | // Adds the trailing slashes on an endpoint
10 | fn call<'reg: 'rc, 'rc, 'ctx>(
11 | &self,
12 | h: &Helper<'reg, 'rc>,
13 | _: &'reg Handlebars,
14 | _: &'ctx Context,
15 | _: &mut RenderContext<'reg, 'ctx>,
16 | out: &mut dyn Output,
17 | ) -> HelperResult {
18 | let value = h
19 | .param(0)
20 | .ok_or_else(|| RenderError::new("Param not found for helper \"url-add-slash\""))?;
21 |
22 | match *value.value() {
23 | Json::String(ref s) => {
24 | let output = if Url::parse(s).is_ok() && !s.ends_with('/') {
25 | format!("{}/", s)
26 | } else {
27 | s.clone()
28 | };
29 |
30 | out.write(&output)?;
31 |
32 | Ok(())
33 | }
34 | Json::Null => Ok(()),
35 | _ => Err(RenderError::new(format!(
36 | "Param type is not string for helper \"url-add-slash\": {:?}",
37 | value,
38 | ))),
39 | }
40 | }
41 | }
42 |
43 | #[cfg(test)]
44 | mod test {
45 | use super::*;
46 | use crate::transform::test::test_against_configs;
47 |
48 | #[test]
49 | fn test_url_add_slash() {
50 | let mut handlebars = Handlebars::new();
51 | handlebars.register_helper("url-add-slash", Box::new(UrlAddSlashHelper));
52 |
53 | let templates = vec![
54 | (
55 | "{{url-add-slash NonSlashService.endpoint}}",
56 | "https://nonslash.com/",
57 | ),
58 | (
59 | "{{url-add-slash NonSlashService.notAnEndpoint}}",
60 | "no-protocol.no-slash.com",
61 | ),
62 | ];
63 |
64 | for (template, expected) in templates {
65 | test_against_configs(&handlebars, template, expected)
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/transform/helper_equal.rs:
--------------------------------------------------------------------------------
1 | use handlebars::*;
2 |
3 | #[derive(Clone, Copy)]
4 | pub struct EqualHelper;
5 |
6 | impl HelperDef for EqualHelper {
7 | fn call<'reg: 'rc, 'rc, 'ctx>(
8 | &self,
9 | h: &Helper<'reg, 'rc>,
10 | r: &'reg Handlebars,
11 | ctx: &'ctx Context,
12 | rc: &mut RenderContext<'reg, 'ctx>,
13 | out: &mut dyn Output,
14 | ) -> HelperResult {
15 | let lvalue = h
16 | .param(0)
17 | .ok_or_else(|| RenderError::new("Left param not found for helper \"equal\""))?
18 | .value();
19 | let rvalue = h
20 | .param(1)
21 | .ok_or_else(|| RenderError::new("Right param not found for helper \"equal\""))?
22 | .value();
23 |
24 | let comparison = lvalue == rvalue;
25 |
26 | if h.is_block() {
27 | let template = if comparison {
28 | h.template()
29 | } else {
30 | h.inverse()
31 | };
32 |
33 | match template {
34 | Some(t) => t.render(r, ctx, rc, out),
35 | None => Ok(()),
36 | }
37 | } else {
38 | if comparison {
39 | out.write(&comparison.to_string())?;
40 | }
41 |
42 | Ok(())
43 | }
44 | }
45 | }
46 |
47 | #[cfg(test)]
48 | mod test {
49 | use super::*;
50 | use crate::transform::test::test_against_configs;
51 |
52 | #[test]
53 | fn test_equal() {
54 | let mut handlebars = Handlebars::new();
55 | handlebars.register_helper("equal", Box::new(EqualHelper));
56 | handlebars.register_helper("eq", Box::new(EqualHelper));
57 |
58 | let templates = vec![
59 | (r#"{{#equal Region.Key "TEST"}}Foo{{/equal}}"#, "Foo"),
60 | (r#"{{#equal Region.Key null}}{{else}}Bar{{/equal}}"#, "Bar"),
61 | (r#"{{#eq Region.Key "TEST"}}Foo{{/eq}}"#, "Foo"),
62 | (r#"{{#eq Region.Key null}}{{else}}Bar{{/eq}}"#, "Bar"),
63 | (r#"{{#if (equal Region.Key "TEST")}}Foo{{/if}}"#, "Foo"),
64 | (
65 | r#"{{#if (equal Region.Key null)}}{{else}}Bar{{/if}}"#,
66 | "Bar",
67 | ),
68 | (r#"{{#if (eq Region.Key "TEST")}}Foo{{/if}}"#, "Foo"),
69 | (r#"{{#if (eq Region.Key null)}}{{else}}Bar{{/if}}"#, "Bar"),
70 | ];
71 |
72 | for (template, expected) in templates {
73 | test_against_configs(&handlebars, template, expected)
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 |
3 | on:
4 | release:
5 | types: [created]
6 |
7 | env:
8 | RUST_LOG: info
9 | RUST_BACKTRACE: 1
10 |
11 | jobs:
12 | publish_github:
13 | name: Publish to GitHub Releases
14 | permissions:
15 | contents: write
16 | packages: write
17 | repository-projects: read
18 | deployments: write
19 | actions: read
20 | issues: read
21 | statuses: write
22 | strategy:
23 | matrix:
24 | target:
25 | # Rustc's Tier 1 platforms
26 | # https://doc.rust-lang.org/nightly/rustc/platform-support.html#tier-1-with-host-tools
27 | # Windows gnu is not currently working
28 | # - i686-pc-windows-gnu
29 | - i686-pc-windows-msvc
30 | - i686-unknown-linux-gnu
31 | - x86_64-apple-darwin
32 | # Windows gnu is not currently working
33 | # - x86_64-pc-windows-gnu
34 | - x86_64-pc-windows-msvc
35 | - x86_64-unknown-linux-gnu
36 | # Select tier 2 platforms
37 | # https://doc.rust-lang.org/nightly/rustc/platform-support.html#tier-2-with-host-tools
38 | - aarch64-apple-darwin
39 | # Windows ARM is not currently working
40 | # - aarch64-pc-windows-msvc
41 | - x86_64-unknown-linux-musl
42 | # Testability according to cross
43 | # https://github.com/rust-embedded/cross#supported-targets
44 | include:
45 | # - target: i686-pc-windows-gnu
46 | # os: windows-latest
47 | - target: i686-pc-windows-msvc
48 | os: windows-latest
49 | - target: i686-unknown-linux-gnu
50 | os: ubuntu-latest
51 | - target: x86_64-apple-darwin
52 | os: macos-latest
53 | # - target: x86_64-pc-windows-gnu
54 | # os: windows-latest
55 | - target: x86_64-pc-windows-msvc
56 | os: windows-latest
57 | - target: x86_64-unknown-linux-gnu
58 | os: ubuntu-latest
59 | - target: aarch64-apple-darwin
60 | os: macos-latest
61 | # - target: aarch64-pc-windows-msvc
62 | # os: windows-latest
63 | - target: x86_64-unknown-linux-musl
64 | os: ubuntu-latest
65 | runs-on: ${{ matrix.os }}
66 | steps:
67 | - name: Checkout
68 | uses: actions/checkout@v3
69 | - name: Install Rust
70 | uses: actions-rs/toolchain@v1
71 | with:
72 | toolchain: stable
73 | target: ${{ matrix.target }}
74 | profile: minimal
75 | - name: Rust Cache
76 | uses: Swatinem/rust-cache@v2.2.1
77 | with:
78 | key: ${{ matrix.target }}
79 | - name: Archive Release
80 | uses: taiki-e/upload-rust-binary-action@v1
81 | with:
82 | bin: hogan
83 | target: ${{ matrix.target }}
84 | env:
85 | # (required) GitHub token for creating GitHub Releases.
86 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
87 |
--------------------------------------------------------------------------------
/src/transform/helper_or.rs:
--------------------------------------------------------------------------------
1 | use handlebars::*;
2 |
3 | #[derive(Clone, Copy)]
4 | pub struct OrHelper;
5 |
6 | impl HelperDef for OrHelper {
7 | fn call<'reg: 'rc, 'rc, 'ctx>(
8 | &self,
9 | h: &Helper<'reg, 'rc>,
10 | r: &'reg Handlebars,
11 | ctx: &'ctx Context,
12 | rc: &mut RenderContext<'reg, 'ctx>,
13 | out: &mut dyn Output,
14 | ) -> HelperResult {
15 | if h.params().len() < 2 {
16 | return Err(RenderError::new("'or' requires at least 2 parameters"));
17 | }
18 |
19 | let comparison = h
20 | .params()
21 | .iter()
22 | .any(|p| p.value().as_str().map_or(false, |v| !v.is_empty()));
23 |
24 | if h.is_block() {
25 | let template = if comparison {
26 | h.template()
27 | } else {
28 | h.inverse()
29 | };
30 |
31 | match template {
32 | Some(t) => t.render(r, ctx, rc, out),
33 | None => Ok(()),
34 | }
35 | } else {
36 | if comparison {
37 | out.write(&comparison.to_string())?;
38 | }
39 |
40 | Ok(())
41 | }
42 | }
43 | }
44 |
45 | #[cfg(test)]
46 | mod test {
47 | use super::*;
48 | use crate::transform::helper_equal::EqualHelper;
49 | use crate::transform::test::test_against_configs;
50 | use crate::transform::test::test_error_against_configs;
51 |
52 | #[test]
53 | fn test_or() {
54 | let mut handlebars = Handlebars::new();
55 | handlebars.register_helper("eq", Box::new(EqualHelper));
56 | handlebars.register_helper("or", Box::new(OrHelper));
57 |
58 | let templates = vec![
59 | (
60 | r#"{{#or (eq Region.Key "TEST") (eq Region.Key "TEST2")}}Foo{{/or}}"#,
61 | "Foo",
62 | ),
63 | (
64 | r#"{{#or (eq Region.Key null) (eq Region.Key "NO")}}{{else}}Bar{{/or}}"#,
65 | "Bar",
66 | ),
67 | (
68 | r#"{{#if (or (eq Region.Key "TEST") (eq Region.Key "TEST2"))}}Foo{{/if}}"#,
69 | "Foo",
70 | ),
71 | (
72 | r#"{{#if (or (eq Region.Key null) (eq Region.Key "NO"))}}{{else}}Bar{{/if}}"#,
73 | "Bar",
74 | ),
75 | (
76 | r#"{{#or (eq Region.Key "NO") (eq Region.Key "TEST2") (eq Region.Key "TEST")}}Foo{{/or}}"#,
77 | "Foo",
78 | ),
79 | ];
80 |
81 | let error_templates = vec![(
82 | r#"{{#or (eq Region.Key "NO") }}Foo{{/or}}"#,
83 | "'or' requires at least 2 parameters",
84 | )];
85 |
86 | for (template, expected) in templates {
87 | test_against_configs(&handlebars, template, expected)
88 | }
89 |
90 | for (template, expected) in error_templates {
91 | test_error_against_configs(&handlebars, template, expected)
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/transform/helper_url_rm_path.rs:
--------------------------------------------------------------------------------
1 | use handlebars::*;
2 | use serde_json::value::Value as Json;
3 | use url::Url;
4 |
5 | #[derive(Clone, Copy)]
6 | pub struct UrlRmPathHelper;
7 |
8 | impl HelperDef for UrlRmPathHelper {
9 | // Removes the last slash plus content to the end of the string
10 | fn call<'reg: 'rc, 'rc, 'ctx>(
11 | &self,
12 | h: &Helper<'reg, 'rc>,
13 | _: &'reg Handlebars,
14 | _: &'ctx Context,
15 | _: &mut RenderContext<'reg, 'ctx>,
16 | out: &mut dyn Output,
17 | ) -> HelperResult {
18 | let value = h
19 | .param(0)
20 | .ok_or_else(|| RenderError::new("Param not found for helper \"url-rm-path\""))?;
21 |
22 | match *value.value() {
23 | Json::String(ref s) => {
24 | let url = if s.ends_with('/') {
25 | &s[..s.len() - 1]
26 | } else {
27 | s
28 | };
29 |
30 | match Url::parse(url) {
31 | Ok(ref mut url) => {
32 | if let Ok(ref mut paths) = url.path_segments_mut() {
33 | paths.pop();
34 | }
35 |
36 | let mut url_str = url.as_str();
37 | if url_str.ends_with('/') {
38 | url_str = &url_str[..url_str.len() - 1];
39 | }
40 |
41 | out.write(url_str)?;
42 |
43 | Ok(())
44 | }
45 | _ => {
46 | out.write(s)?;
47 | Ok(())
48 | }
49 | }
50 | }
51 | Json::Null => Ok(()),
52 | _ => Err(RenderError::new(format!(
53 | "Param type is not string for helper \"url-rm-path\": {:?}",
54 | value
55 | ))),
56 | }
57 | }
58 | }
59 |
60 | #[cfg(test)]
61 | mod test {
62 | use super::*;
63 | use crate::transform::test::test_against_configs;
64 |
65 | #[test]
66 | fn test_url_rm_path() {
67 | let mut handlebars = Handlebars::new();
68 | handlebars.register_helper("url-rm-path", Box::new(UrlRmPathHelper));
69 |
70 | let templates = vec![
71 | (
72 | "{{url-rm-path PathService.endpoint}}",
73 | "https://path.com/path",
74 | ),
75 | (
76 | "{{url-rm-path PathService.trailingSlash}}",
77 | "https://trailing-path.com/path",
78 | ),
79 | ];
80 |
81 | for (template, expected) in templates {
82 | test_against_configs(&handlebars, template, expected)
83 | }
84 | }
85 |
86 | #[test]
87 | fn test_double_url_rm_path() {
88 | let mut handlebars = Handlebars::new();
89 | handlebars.register_helper("url-rm-path", Box::new(UrlRmPathHelper));
90 |
91 | test_against_configs(
92 | &handlebars,
93 | "{{url-rm-path (url-rm-path PathService.trailingSlash)}}",
94 | "https://trailing-path.com",
95 | );
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/transform/helper_comma_delimited_list.rs:
--------------------------------------------------------------------------------
1 | use handlebars::*;
2 | use itertools::join;
3 | use serde_json::value::Value as Json;
4 |
5 | #[derive(Clone, Copy)]
6 | pub struct CommaDelimitedListHelper;
7 |
8 | impl HelperDef for CommaDelimitedListHelper {
9 | // Change an array of items into a comma seperated list with formatting
10 | // Usage: {{#comma-list array}}{{elementAttribute}}:{{attribute2}}{{/comma-list}}
11 | fn call<'reg: 'rc, 'rc, 'ctx>(
12 | &self,
13 | h: &Helper<'reg, 'rc>,
14 | r: &'reg Handlebars,
15 | ctx: &'ctx Context,
16 | rc: &mut RenderContext<'reg, 'ctx>,
17 | out: &mut dyn Output,
18 | ) -> HelperResult {
19 | let value = h
20 | .param(0)
21 | .ok_or_else(|| RenderError::new("Param not found for helper \"comma-list\""))?;
22 |
23 | match h.template() {
24 | Some(template) => match *value.value() {
25 | Json::Array(ref list) => {
26 | let mut render_list = Vec::new();
27 |
28 | for (i, item) in list.iter().enumerate() {
29 | let mut local_rc = rc.clone();
30 | let block_rc = local_rc.block_mut().unwrap();
31 | if let Some(inner_path) = value.context_path() {
32 | let block_path = block_rc.base_path_mut();
33 | block_path.append(&mut inner_path.to_owned());
34 | block_path.push(i.to_string());
35 | }
36 |
37 | if let Some(block_param) = h.block_param() {
38 | let mut new_block = BlockContext::new();
39 | let mut block_params = BlockParams::new();
40 | block_params.add_value(block_param, to_json(item))?;
41 | new_block.set_block_params(block_params);
42 | local_rc.push_block(new_block);
43 |
44 | render_list.push(template.renders(r, ctx, &mut local_rc)?);
45 | } else {
46 | render_list.push(template.renders(r, ctx, &mut local_rc)?);
47 | }
48 | }
49 | out.write(&join(&render_list, ","))?;
50 |
51 | Ok(())
52 | }
53 | Json::Null => Ok(()),
54 | _ => Err(RenderError::new(format!(
55 | "Param type is not array for helper \"comma-list\": {:?}",
56 | value
57 | ))),
58 | },
59 | None => Ok(()),
60 | }
61 | }
62 | }
63 |
64 | #[cfg(test)]
65 | mod test {
66 | use super::*;
67 | use crate::transform::test::test_against_configs;
68 |
69 | #[test]
70 | fn test_comma_list() {
71 | let mut handlebars = Handlebars::new();
72 | handlebars.register_helper("comma-list", Box::new(CommaDelimitedListHelper));
73 |
74 | test_against_configs(
75 | &handlebars,
76 | "{{#comma-list Memcache.Servers}}{{Endpoint}}:{{Port}}{{/comma-list}}",
77 | "192.168.1.100:1122,192.168.1.101:1122,192.168.1.102:1122",
78 | );
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/transform/mod.rs:
--------------------------------------------------------------------------------
1 | use handlebars::Handlebars;
2 |
3 | mod helper_comma_delimited_list;
4 | mod helper_equal;
5 | mod helper_lowercase;
6 | mod helper_or;
7 | mod helper_url_add_slash;
8 | mod helper_url_rm_path;
9 | mod helper_url_rm_slash;
10 | mod helper_yaml_string;
11 |
12 | use self::helper_comma_delimited_list::CommaDelimitedListHelper;
13 | use self::helper_equal::EqualHelper;
14 | use self::helper_lowercase::LowercaseHelper;
15 | use self::helper_or::OrHelper;
16 | use self::helper_url_add_slash::UrlAddSlashHelper;
17 | use self::helper_url_rm_path::UrlRmPathHelper;
18 | use self::helper_url_rm_slash::UrlRmSlashHelper;
19 | use self::helper_yaml_string::YamlStringHelper;
20 |
21 | //This fn was changed here https://github.com/sunng87/handlebars-rust/pull/366 which added additional characters to the list
22 | //To maintain backwards compatibility we are reverting to the original default escape fn
23 | pub fn old_escape_html(s: &str) -> String {
24 | let mut output = String::new();
25 |
26 | for c in s.chars() {
27 | match c {
28 | '<' => output.push_str("<"),
29 | '>' => output.push_str(">"),
30 | '"' => output.push_str("""),
31 | '&' => output.push_str("&"),
32 | _ => output.push(c),
33 | }
34 | }
35 | output
36 | }
37 |
38 | pub fn handlebars<'a>(strict: bool) -> Handlebars<'a> {
39 | let mut handlebars = Handlebars::new();
40 | handlebars.set_strict_mode(strict);
41 | handlebars.register_helper("comma-list", Box::new(CommaDelimitedListHelper));
42 | handlebars.register_helper("equal", Box::new(EqualHelper));
43 | handlebars.register_helper("eq", Box::new(EqualHelper));
44 | handlebars.register_helper("lowercase", Box::new(LowercaseHelper));
45 | handlebars.register_helper("or", Box::new(OrHelper));
46 | handlebars.register_helper("url-add-slash", Box::new(UrlAddSlashHelper));
47 | handlebars.register_helper("url-rm-path", Box::new(UrlRmPathHelper));
48 | handlebars.register_helper("url-rm-slash", Box::new(UrlRmSlashHelper));
49 | handlebars.register_helper("yaml-string", Box::new(YamlStringHelper));
50 | handlebars.register_escape_fn(old_escape_html);
51 | handlebars
52 | }
53 |
54 | #[cfg(test)]
55 | mod test {
56 | use super::*;
57 | use serde_json::{self, Value};
58 |
59 | fn config_fixture() -> Value {
60 | let mut config: Value = serde_json::from_str(&include_str!(
61 | "../../tests/fixtures/configs/config.TEST.json"
62 | ))
63 | .unwrap();
64 | config["ConfigData"].take()
65 | }
66 |
67 | pub(crate) fn test_against_configs(handlebars: &Handlebars, template: &str, expected: &str) {
68 | let config_rendered = handlebars.render_template(template, &config_fixture());
69 | assert!(config_rendered.is_ok());
70 | assert_eq!(&config_rendered.unwrap(), expected);
71 |
72 | let null_rendered = handlebars.render_template(template, &Value::Null);
73 | assert!(null_rendered.is_ok());
74 | assert_eq!(&null_rendered.unwrap(), "");
75 | }
76 |
77 | pub(crate) fn test_error_against_configs(
78 | handlebars: &Handlebars,
79 | template: &str,
80 | expected: &str,
81 | ) {
82 | let config_rendered = handlebars.render_template(template, &config_fixture());
83 | assert!(!config_rendered.is_ok());
84 | assert_eq!(&config_rendered.unwrap_err().desc, expected);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/storage/cache.rs:
--------------------------------------------------------------------------------
1 | use crate::app::datadogstatsd::CustomMetrics;
2 | use crate::app::datadogstatsd::DdMetrics;
3 | use anyhow::Result;
4 | use hogan::config::Environment;
5 | use hogan::config::EnvironmentDescription;
6 | use riker::actors::*;
7 | use std::sync::Arc;
8 | use std::time::{Duration, SystemTime};
9 |
10 | pub trait Cache {
11 | fn id(&self) -> &str;
12 | fn clean(&self, max_age: usize) -> Result<()>;
13 | fn read_env(&self, env: &str, sha: &str) -> Result