Skip to content

Commit 68d8ab4

Browse files
committed
feat: allow overriding templates in gen and validate
1 parent 3a5a7a2 commit 68d8ab4

File tree

4 files changed

+45
-28
lines changed

4 files changed

+45
-28
lines changed

.envrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
source_up
22

3-
which use_nix &>/dev/null && use_nix
3+
which nix-shell &>/dev/null && use_nix
44

55
layout python
66

k8t/cli.py

+25-23
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,14 @@ def print_license():
5757
@root.command(name="validate", help="Validate template files for given context.")
5858
@click.option("-m", "--method", type=click.Choice(MERGE_METHODS), default="ltr", show_default=True, help="Value file merge method.")
5959
@click.option("--value-file", "value_files", multiple=True, type=click.Path(dir_okay=False, exists=True), help="Additional value file to include.")
60-
@click.option("--value", "cli_values", type=(str, str), multiple=True, metavar="<KEY VALUE>", help="Additional value(s) to include.")
61-
@click.option("--cluster", "-c", "cname", help="Cluster context to use.")
62-
@click.option("--environment", "-e", "ename", help="Deployment environment to use.")
60+
@click.option("--value", "cli_values", type=(str, str), multiple=True, metavar="KEY VALUE", help="Additional value(s) to include.")
61+
@click.option("--cluster", "-c", "cname", metavar="NAME", help="Cluster context to use.")
62+
@click.option("--environment", "-e", "ename", metavar="NAME", help="Deployment environment to use.")
6363
@click.option("--suffix", "-s", "suffixes", default=[".yaml", ".j2", ".jinja2"], help="Filter template files by suffix. Can be used multiple times.", show_default=True)
64+
@click.option("--template-file", "-t", "template_overrides", metavar="KEY PATH", type=click.Tuple([str, str]), multiple=True, help="Restrict validation to single template file (the key is needed for references in templates).")
6465
@click.argument("directory", type=click.Path(dir_okay=True, file_okay=False, exists=True), default=os.getcwd())
6566
@requires_project_directory
66-
def cli_validate(method, value_files, cli_values, cname, ename, suffixes, directory):
67+
def cli_validate(method, value_files, cli_values, cname, ename, suffixes, template_overrides, directory):
6768
vals = deep_merge( # pylint: disable=redefined-outer-name
6869
values.load_all(directory, cname, ename, method),
6970
*(load_yaml(p) for p in value_files),
@@ -73,12 +74,12 @@ def cli_validate(method, value_files, cli_values, cname, ename, suffixes, direct
7374
)
7475
config.CONFIG = config.load_all(directory, cname, ename, method)
7576

76-
eng = build(directory, cname, ename)
77+
eng = build(directory, cname, ename, template_overrides)
7778

7879
templates = eng.list_templates() # pylint: disable=redefined-outer-name
7980

8081
if suffixes:
81-
templates = [ name for name in templates if os.path.splitext(name)[1] in suffixes ]
82+
templates = [name for name in templates if os.path.splitext(name)[1] in suffixes]
8283

8384
all_validated = True
8485

@@ -114,14 +115,15 @@ def cli_validate(method, value_files, cli_values, cname, ename, suffixes, direct
114115
@root.command(name="gen", help="Create manifest files using stored templates.")
115116
@click.option("-m", "--method", type=click.Choice(MERGE_METHODS), default="ltr", show_default=True, help="Value file merge method.")
116117
@click.option("--value-file", "value_files", multiple=True, type=click.Path(dir_okay=False, exists=True), help="Additional value file to include.")
117-
@click.option("--value", "cli_values", type=(str, str), multiple=True, metavar="<KEY VALUE>", help="Additional value(s) to include.")
118-
@click.option("--cluster", "-c", "cname", help="Cluster context to use.")
119-
@click.option("--environment", "-e", "ename", help="Deployment environment to use.")
118+
@click.option("--value", "cli_values", type=(str, str), multiple=True, metavar="KEY VALUE", help="Additional value(s) to include.")
119+
@click.option("--cluster", "-c", "cname", metavar="NAME", help="Cluster context to use.")
120+
@click.option("--environment", "-e", "ename", metavar="NAME", help="Deployment environment to use.")
120121
@click.option("--suffix", "-s", "suffixes", default=[".yaml", ".j2", ".jinja2"], help="Filter template files by suffix. Can be used multiple times.", show_default=True)
121-
@click.option("--secret-provider", help="Secret provider override.")
122+
@click.option("--secret-provider", help="Secret provider override.", type=click.Choice(['ssm', 'random', 'hash']))
123+
@click.option("--template-file", "-t", "template_overrides", metavar="KEY PATH", type=click.Tuple([str, str]), multiple=True, help="Restrict validation to single template file (the key is needed for references in templates).")
122124
@click.argument("directory", type=click.Path(dir_okay=True, file_okay=False, exists=True), default=os.getcwd())
123125
@requires_project_directory
124-
def cli_gen(method, value_files, cli_values, cname, ename, suffixes, secret_provider, directory): # pylint: disable=redefined-outer-name,too-many-arguments
126+
def cli_gen(method, value_files, cli_values, cname, ename, suffixes, secret_provider, template_overrides, directory): # pylint: disable=redefined-outer-name,too-many-arguments
125127
vals = deep_merge( # pylint: disable=redefined-outer-name
126128
values.load_all(directory, cname, ename, method),
127129
*(load_yaml(p) for p in value_files),
@@ -138,12 +140,12 @@ def cli_gen(method, value_files, cli_values, cname, ename, suffixes, secret_prov
138140

139141
config.CONFIG['secrets']['provider'] = secret_provider
140142

141-
eng = build(directory, cname, ename)
143+
eng = build(directory, cname, ename, template_overrides)
142144

143145
templates = eng.list_templates() # pylint: disable=redefined-outer-name
144146

145147
if suffixes:
146-
templates = [ name for name in templates if os.path.splitext(name)[1] in suffixes ]
148+
templates = [name for name in templates if os.path.splitext(name)[1] in suffixes]
147149

148150
validated = True
149151

@@ -188,7 +190,7 @@ def new_cluster(name, directory):
188190

189191

190192
@new.command(name="environment", help="Create a new environment context.")
191-
@click.option("--cluster", "-c", "cname")
193+
@click.option("--cluster", "-c", "cname", metavar="NAME")
192194
@click.argument("name")
193195
@click.argument("directory", type=click.Path(exists=True, file_okay=False), default=os.getcwd())
194196
@requires_project_directory
@@ -199,8 +201,8 @@ def new_environment(cname, name, directory):
199201

200202

201203
@new.command(name="template", help="Create specified kubernetes manifest template.")
202-
@click.option("--cluster", "-c", "cname", help="Cluster context to use.")
203-
@click.option("--environment", "-e", "ename", help="Deployment environment to use.")
204+
@click.option("--cluster", "-c", "cname", metavar="NAME", help="Cluster context to use.")
205+
@click.option("--environment", "-e", "ename", metavar="NAME", help="Deployment environment to use.")
204206
@click.option("--name", "-n", help="Template filename.")
205207
@click.option("--prefix", "-p", help="Prefix for filename.")
206208
@click.argument("kind", type=click.Choice(sorted(list(scaffolding.list_available_templates()))))
@@ -233,7 +235,7 @@ def get_clusters(directory):
233235

234236

235237
@get.command(name="environments", help="Get configured environments.")
236-
@click.option("--cluster", "-c", "cname", help="Cluster context to use.")
238+
@click.option("--cluster", "-c", "cname", metavar="NAME", help="Cluster context to use.")
237239
@click.argument("directory", type=click.Path(exists=True, file_okay=False), default=os.getcwd())
238240
@requires_project_directory
239241
def get_environments(cname, directory): # pylint: disable=redefined-outer-name
@@ -244,8 +246,8 @@ def get_environments(cname, directory): # pylint: disable=redefined-outer-name
244246

245247

246248
@get.command(name="templates", help="Get stored templates.")
247-
@click.option("--cluster", "-c", "cname", help="Cluster context to use.")
248-
@click.option("--environment", "-e", "ename", help="Deployment environment to use.")
249+
@click.option("--cluster", "-c", "cname", metavar="NAME", help="Cluster context to use.")
250+
@click.option("--environment", "-e", "ename", metavar="NAME", help="Deployment environment to use.")
249251
@click.argument("directory", type=click.Path(exists=True, file_okay=False), default=os.getcwd())
250252
@requires_project_directory
251253
def get_templates(directory, cname, ename): # pylint: disable=redefined-outer-name
@@ -259,8 +261,8 @@ def edit():
259261

260262

261263
@edit.command(name="config", help="Edit config files in chosen context.")
262-
@click.option("--cluster", "-c", "cname", help="Cluster context to use.")
263-
@click.option("--environment", "-e", "ename", help="Deployment environment to use.")
264+
@click.option("--cluster", "-c", "cname", metavar="NAME", help="Cluster context to use.")
265+
@click.option("--environment", "-e", "ename", metavar="NAME", help="Deployment environment to use.")
264266
@click.argument("directory", type=click.Path(exists=True, file_okay=False), default=os.getcwd())
265267
@requires_project_directory
266268
def edit_config(directory, cname, ename): # pylint: disable=redefined-outer-name
@@ -281,8 +283,8 @@ def edit_config(directory, cname, ename): # pylint: disable=redefined-outer-nam
281283

282284

283285
@edit.command(name="values", help="Edit value files in chosen context.")
284-
@click.option("--cluster", "-c", "cname", help="Cluster context to use.")
285-
@click.option("--environment", "-e", "ename", help="Deployment environment to use.")
286+
@click.option("--cluster", "-c", "cname", metavar="NAME", help="Cluster context to use.")
287+
@click.option("--environment", "-e", "ename", metavar="NAME", help="Deployment environment to use.")
286288
@click.argument("directory", type=click.Path(exists=True, file_okay=False), default=os.getcwd())
287289
@requires_project_directory
288290
def edit_values(directory, cname, ename): # pylint: disable=redefined-outer-name

k8t/engine.py

+15-4
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,35 @@
77
#
88
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
99

10+
import os
1011
import logging
1112

12-
from jinja2 import Environment, FileSystemLoader, StrictUndefined
13+
from typing import List
14+
from jinja2 import Environment, DictLoader, FileSystemLoader, StrictUndefined
1315

1416
from k8t.filters import (b64decode, b64encode, envvar, get_secret, hashf,
1517
random_password, sanitize_label, to_bool)
1618
from k8t.project import find_files
19+
from k8t.util import read_file
1720

1821
LOGGER = logging.getLogger(__name__)
1922

2023

21-
def build(path: str, cluster: str, environment: str) -> Environment:
22-
template_paths = find_template_paths(path, cluster, environment)
24+
def build(path: str, cluster: str, environment: str, template_overrides: List[str] = None) -> Environment:
25+
env = None
26+
template_paths = []
2327

2428
LOGGER.debug(
2529
"building template environment")
2630

27-
env = Environment(undefined=StrictUndefined, loader=FileSystemLoader(template_paths))
31+
if template_overrides is not None and len(template_overrides) > 0:
32+
template_paths = {key: read_file(os.path.abspath(path)) for key, path in template_overrides}
33+
34+
env = Environment(undefined=StrictUndefined, loader=DictLoader(template_paths))
35+
else:
36+
template_paths = find_template_paths(path, cluster, environment)
37+
38+
env = Environment(undefined=StrictUndefined, loader=FileSystemLoader(template_paths))
2839

2940
# Filter functions
3041
env.filters["b64decode"] = b64decode

k8t/util.py

+4
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,7 @@ def list_files(
145145
break
146146

147147
return result
148+
149+
def read_file(path: str) -> str:
150+
with open(path, 'rb') as stream:
151+
return stream.read().decode()

0 commit comments

Comments
 (0)