From ceb12c5c12b20618769a44435b946dd73695f661 Mon Sep 17 00:00:00 2001 From: Pallab Pain Date: Sat, 10 Dec 2022 22:51:53 +0530 Subject: [PATCH] feat(template): add helm3 template like support This commit adds a feature in the CLI where you can generate `helm template` like output for your manifests Usage: python -m riocli template [OPTIONS] [FILES]... Print manifests with filled values Options: -v, --values TEXT path to values yaml file. key/values specified in the values file can be used as variables in template yamls -s, --secrets TEXT secret files are sops encoded value files. rio-cli expects sops to be authorized for decoding files on this computer --help Show this message and exit. Reviewed By: Ankit Gadiya --- riocli/apply/__init__.py | 6 +++-- riocli/apply/parse.py | 22 ++++++++++++------- riocli/apply/template.py | 47 ++++++++++++++++++++++++++++++++++++++++ riocli/bootstrap.py | 7 +++--- riocli/utils/__init__.py | 7 ++++++ 5 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 riocli/apply/template.py diff --git a/riocli/apply/__init__.py b/riocli/apply/__init__.py index 69fc1b4b..3549959a 100644 --- a/riocli/apply/__init__.py +++ b/riocli/apply/__init__.py @@ -12,13 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Iterable + import click from click_help_colors import HelpColorsCommand -from typing import Iterable +from riocli.apply.explain import explain +from riocli.apply.template import template from riocli.apply.parse import Applier from riocli.apply.util import process_files_values_secrets -from riocli.apply.explain import explain @click.command( diff --git a/riocli/apply/parse.py b/riocli/apply/parse.py index e09da9db..1f4da266 100644 --- a/riocli/apply/parse.py +++ b/riocli/apply/parse.py @@ -27,6 +27,7 @@ from riocli.apply.resolver import ResolverCache from riocli.config import Configuration +from riocli.utils import dump_all_yaml from riocli.utils import run_bash from riocli.utils.mermaid import mermaid_link, mermaid_safe @@ -138,7 +139,11 @@ def delete(self, *args, **kwargs): if obj in self.resolved_objects and 'manifest' in self.resolved_objects[obj]: self._delete_manifest(obj, *args, **kwargs) - def parse_dependencies(self, check_missing=True, delete=False): + def print_resolved_manifests(self): + manifests = [o for _, o in self.objects.items()] + dump_all_yaml(manifests) + + def parse_dependencies(self, check_missing=True, delete=False, template=False): number_of_objects = 0 for f, data in self.files.items(): for model in data: @@ -157,12 +162,13 @@ def parse_dependencies(self, check_missing=True, delete=False): action = 'DELETE' kind = node.split(":")[0] expected_time = round( - self.EXPECTED_TIME.get(kind.lower(), 5)/60, 2) + self.EXPECTED_TIME.get(kind.lower(), 5) / 60, 2) total_time = total_time + expected_time resource_list.append([node, action, expected_time]) - self._display_context( - total_time=total_time, total_objects=number_of_objects, resource_list=resource_list) + if not template: + self._display_context( + total_time=total_time, total_objects=number_of_objects, resource_list=resource_list) if check_missing: missing_resources = [] @@ -172,7 +178,7 @@ def parse_dependencies(self, check_missing=True, delete=False): if missing_resources: click.secho("missing resources found in yaml. " + - "Plese ensure the following are either available in your yaml" + + "Please ensure the following are either available in your yaml" + "or created on the server. {}".format(set(missing_resources)), fg="red") raise SystemExit(1) @@ -269,7 +275,7 @@ def _add_graph_node(self, key): def _add_graph_edge(self, dependent_key, key): self.graph.add(dependent_key, key) self.diagram.append('\t{}[{}] --> {}[{}] '.format(mermaid_safe(key), - key, mermaid_safe(dependent_key), dependent_key)) + key, mermaid_safe(dependent_key), dependent_key)) # Dependency Resolution def _parse_dependency(self, dependent_key, model): @@ -372,7 +378,7 @@ def _display_context(self, total_time: int, total_objects: int, resource_list: t ['Resources', total_objects], ] click.echo(tabulate(context, headers=headers, - tablefmt='simple', numalign='center')) + tablefmt='simple', numalign='center')) # Display Resource Inventory headers = [] @@ -382,7 +388,7 @@ def _display_context(self, total_time: int, total_objects: int, resource_list: t col, _ = get_terminal_size() click.secho(" " * col, bg='blue') click.echo(tabulate(resource_list, headers=headers, - tablefmt='simple', numalign='center')) + tablefmt='simple', numalign='center')) click.secho(" " * col, bg='blue') @staticmethod diff --git a/riocli/apply/template.py b/riocli/apply/template.py new file mode 100644 index 00000000..b9bbde2f --- /dev/null +++ b/riocli/apply/template.py @@ -0,0 +1,47 @@ +# Copyright 2022 Rapyuta Robotics +# +# 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. + +from typing import Iterable + +import click +from click_help_colors import HelpColorsCommand + +from riocli.apply.parse import Applier +from riocli.apply.util import process_files_values_secrets + + +@click.command( + 'template', + cls=HelpColorsCommand, + help_headers_color='yellow', + help_options_color='green', +) +@click.option('--values', '-v', + help="path to values yaml file. key/values specified in the values file can be used as variables in template yamls") +@click.option('--secrets', '-s', + help="secret files are sops encoded value files. rio-cli expects sops to be authorized for decoding files on this computer") +@click.argument('files', nargs=-1) +def template(values: str, secrets: str, files: Iterable[str]) -> None: + """ + Print manifests with filled values + """ + glob_files, abs_values, abs_secrets = process_files_values_secrets( + files, values, secrets) + + if len(glob_files) == 0: + click.secho('no files specified', fg='red') + raise SystemExit(1) + + rc = Applier(glob_files, abs_values, abs_secrets) + rc.print_resolved_manifests() diff --git a/riocli/bootstrap.py b/riocli/bootstrap.py index 1b533114..e9efdbaa 100644 --- a/riocli/bootstrap.py +++ b/riocli/bootstrap.py @@ -22,15 +22,16 @@ from click_plugins import with_plugins from pkg_resources import iter_entry_points -from riocli.chart import chart -from riocli.apply import apply, explain, delete +from riocli.apply import apply, explain, delete, template from riocli.auth import auth from riocli.build import build +from riocli.chart import chart from riocli.completion import completion from riocli.config import Configuration from riocli.deployment import deployment from riocli.device import device from riocli.disk import disk +from riocli.managedservice import managedservice from riocli.marketplace import marketplace from riocli.network import network from riocli.package import package @@ -40,7 +41,6 @@ from riocli.secret import secret from riocli.shell import shell, deprecated_repl from riocli.static_route import static_route -from riocli.managedservice import managedservice @with_plugins(iter_entry_points('riocli.plugins')) @@ -94,3 +94,4 @@ def version(): cli.add_command(shell) cli.add_command(deprecated_repl) cli.add_command(managedservice) +cli.add_command(template) diff --git a/riocli/utils/__init__.py b/riocli/utils/__init__.py index d20dd2eb..705e67a4 100644 --- a/riocli/utils/__init__.py +++ b/riocli/utils/__init__.py @@ -34,6 +34,13 @@ def inspect_with_format(obj: typing.Any, format_type: str): raise Exception('Invalid format') +def dump_all_yaml(objs: typing.List): + """ + Dump multiple documents as YAML separated by triple dash (---) + """ + click.echo(yaml.dump_all(objs, allow_unicode=True, explicit_start=True)) + + def run_bash(cmd, bg=False): cmd_parts = shlex.split(cmd)