ktmpl/
lib.rs

1//! the [Templates + Parameterization proposal](https://github.com/kubernetes/kubernetes/blob/master/docs/proposals/templates.md).
2//!
3//! The crate ships with a command line utility and a library for programmatically processing
4//! manifest templates. See the [README](https://github.com/jimmycuadra/ktmpl) for documentation for
5//! the command line utility, and a general overview of the Kubernetes template system.
6//!
7//! Here is a simple example of using the library:
8//!
9//! ```
10//! extern crate ktmpl;
11//!
12//! use ktmpl::{ParameterValue, ParameterValues, Template};
13//!
14//! fn main() {
15//!     let template_contents = r#"
16//! ---
17//! kind: "Template"
18//! apiVersion: "v1"
19//! metadata:
20//!   name: "example"
21//! objects:
22//!   - kind: "Service"
23//!     apiVersion: "v1"
24//!     metadata:
25//!       name: "$(DATABASE_SERVICE_NAME)"
26//!     spec:
27//!       ports:
28//!         - name: "db"
29//!           protocol: "TCP"
30//!           targetPort: 3000
31//!       selector:
32//!         name: "$(DATABASE_SERVICE_NAME)"
33//! parameters:
34//!   - name: "DATABASE_SERVICE_NAME"
35//!     description: "Database service name"
36//!     required: true
37//!     parameterType: "string"
38//! "#;
39//!
40//!     let mut parameter_values = ParameterValues::new();
41//!
42//!     parameter_values.insert(
43//!         "DATABASE_SERVICE_NAME".to_string(),
44//!         ParameterValue::Plain("mongo".to_string()),
45//!     );
46//!
47//!     let template = Template::new(
48//!         template_contents.to_string(),
49//!         parameter_values,
50//!         None,
51//!     ).unwrap();
52//!     let processed_template = template.process().unwrap();
53//!
54//!     assert_eq!(
55//!         processed_template.lines().map(|l| l.trim_right()).collect::<Vec<&str>>().join("\n"),
56//!         r#"---
57//! kind: Service
58//! apiVersion: v1
59//! metadata:
60//!   name: mongo
61//! spec:
62//!   ports:
63//!     - name: db
64//!       protocol: TCP
65//!       targetPort: 3000
66//!   selector:
67//!     name: mongo"#
68//!     );
69//! }
70//! ```
71
72#![deny(missing_debug_implementations)]
73#![deny(missing_docs)]
74
75pub use crate::{
76    parameter::{
77        parameter_values_from_file,
78        parameter_values_from_str,
79        parameter_values_from_yaml,
80        ParameterValue,
81        ParameterValues,
82    },
83    secret::{Secret, Secrets},
84    template::Template,
85};
86
87mod parameter;
88mod processor;
89mod secret;
90mod template;
91
92#[cfg(test)]
93mod tests {
94    use std::fs::File;
95    use std::io::Read;
96
97    use super::{
98        parameter_values_from_file,
99        ParameterValue,
100        ParameterValues,
101        Secret,
102        Secrets,
103        Template,
104    };
105
106    #[test]
107    fn process_keys() {
108        let template_contents = r#"
109---
110objects:
111    - $(FOO): bar
112parameters:
113  - name: FOO
114    value: baz
115"#;
116        let template = Template::new(
117            template_contents.to_string(),
118            ParameterValues::new(),
119            None,
120        )
121        .unwrap();
122
123        let processed_template = template.process().unwrap();
124
125        assert_eq!(
126            processed_template
127                .lines()
128                .map(|l| l.trim_end())
129                .collect::<Vec<&str>>()
130                .join("\n"),
131            r#"---
132baz: bar"#
133        );
134    }
135
136    #[test]
137    fn encode_secrets() {
138        let template_contents = r#"
139---
140kind: "Template"
141apiVersion: "v1"
142metadata:
143  name: "example"
144objects:
145  - kind: "Secret"
146    apiVersion: "v1"
147    metadata:
148      name: "webapp"
149    data:
150      config.yml: |
151        username: "carl"
152        password: "$(PASSWORD)"
153    type: "Opaque"
154parameters:
155  - name: "PASSWORD"
156    description: "The password for the web app"
157    required: true
158    parameterType: "string"
159"#;
160
161        let mut parameter_values = ParameterValues::new();
162
163        parameter_values.insert(
164            "PASSWORD".to_string(),
165            ParameterValue::Plain("narble".to_string()),
166        );
167
168        let mut secrets = Secrets::new();
169
170        secrets.insert(Secret {
171            name: "webapp".to_string(),
172            namespace: "default".to_string(),
173        });
174
175        let template = Template::new(
176            template_contents.to_string(),
177            parameter_values,
178            Some(secrets),
179        )
180        .unwrap();
181
182        let processed_template = template.process().unwrap();
183
184        assert_eq!(
185            processed_template
186                .lines()
187                .map(|l| l.trim_end())
188                .collect::<Vec<&str>>()
189                .join("\n"),
190            r#"---
191kind: Secret
192apiVersion: v1
193metadata:
194  name: webapp
195data:
196  config.yml: dXNlcm5hbWU6ICJjYXJsIgpwYXNzd29yZDogIm5hcmJsZSIK
197type: Opaque"#
198        );
199    }
200
201    #[test]
202    fn missing_secret() {
203        let template_contents = r#"
204---
205kind: "Template"
206apiVersion: "v1"
207metadata:
208  name: "example"
209objects:
210  - kind: "Namespace"
211    apiVersion: "v1"
212    metadata:
213      name: "$(NAMESPACE)"
214parameters:
215  - name: "NAMESPACE"
216    description: "The namespace to create"
217    required: true
218    parameterType: "string"
219"#;
220
221        let mut parameter_values = ParameterValues::new();
222
223        parameter_values.insert(
224            "NAMESPACE".to_string(),
225            ParameterValue::Plain("foo".to_string()),
226        );
227
228        let mut secrets = Secrets::new();
229
230        secrets.insert(Secret {
231            name: "ghost".to_string(),
232            namespace: "default".to_string(),
233        });
234
235        let template = Template::new(
236            template_contents.to_string(),
237            parameter_values,
238            Some(secrets),
239        )
240        .unwrap();
241
242        assert!(template.process().is_err());
243    }
244
245    #[test]
246    fn parameter_file() {
247        let mut template_file = File::open("example.yml").unwrap();
248        let mut template_contents = String::new();
249
250        template_file
251            .read_to_string(&mut template_contents)
252            .unwrap();
253
254        let parameter_values = parameter_values_from_file("params.yml").unwrap();
255
256        let template =
257            Template::new(template_contents.to_string(), parameter_values, None).unwrap();
258
259        let processed_template = template.process().unwrap();
260
261        assert_eq!(
262            processed_template
263                .lines()
264                .map(|l| l.trim_end())
265                .collect::<Vec<&str>>()
266                .join("\n"),
267            r#"---
268kind: Service
269apiVersion: v1
270metadata:
271  name: mongodb
272spec:
273  ports:
274    - name: mongo
275      protocol: TCP
276      targetPort: 27017
277  selector:
278    name: mongodb
279---
280kind: ReplicationController
281apiVersion: v1
282metadata:
283  name: mongodb
284spec:
285  replicas: 2
286  selector:
287    name: mongodb
288  template:
289    metadata:
290      creationTimestamp: ~
291      labels:
292        name: mongodb
293    spec:
294      containers:
295        - name: mongodb
296          image: docker.io/centos/mongodb-26-centos7
297          ports:
298            - containerPort: 27017
299              protocol: TCP
300          env:
301            - name: MONGODB_USER
302              value: carl
303            - name: MONGODB_PASSWORD
304              value: c2VjcmV0
305            - name: MONGODB_DATABASE
306              value: sampledb"#
307        );
308    }
309}