Skip to content

Commit

Permalink
Merge pull request #341 from rapyuta-robotics/devel
Browse files Browse the repository at this point in the history
🎉 release: v8.0.0
  • Loading branch information
pallabpain authored Aug 2, 2024
2 parents cfefc76 + 7943a4a commit d8c02fd
Show file tree
Hide file tree
Showing 23 changed files with 232 additions and 183 deletions.
6 changes: 3 additions & 3 deletions riocli/__main__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Rapyuta Robotics
# Copyright 2024 Rapyuta Robotics
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -12,7 +12,7 @@
# 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 riocli.bootstrap import cli
from riocli.bootstrap import safe_cli

if __name__ == "__main__":
cli()
safe_cli()
9 changes: 6 additions & 3 deletions riocli/apply/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2022 Rapyuta Robotics
# Copyright 2024 Rapyuta Robotics
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -17,11 +17,12 @@
import click
from click_help_colors import HelpColorsCommand

from riocli.apply.explain import explain
from riocli.apply.explain import explain, list_examples
from riocli.apply.parse import Applier
from riocli.apply.template import template
from riocli.apply.util import process_files_values_secrets
from riocli.constants import Colors
from riocli.utils import print_centered_text


@click.command(
Expand Down Expand Up @@ -74,7 +75,7 @@ def apply(
click.secho('No files specified', fg=Colors.RED)
raise SystemExit(1)

click.secho("----- Files Processed ----", fg=Colors.YELLOW)
print_centered_text('Files Processed')
for file in glob_files:
click.secho(file, fg=Colors.YELLOW)

Expand All @@ -93,6 +94,7 @@ def apply(
if not silent and not dryrun:
click.confirm("Do you want to proceed?", default=True, abort=True)

print_centered_text('Applying Manifests')
rc.apply(dryrun=dryrun, workers=workers, retry_count=retry_count, retry_interval=retry_interval)


Expand Down Expand Up @@ -137,4 +139,5 @@ def delete(
if not silent and not dryrun:
click.confirm("Do you want to proceed?", default=True, abort=True)

print_centered_text('Deleting Resources')
rc.delete(dryrun=dryrun)
21 changes: 20 additions & 1 deletion riocli/apply/explain.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2022 Rapyuta Robotics
# Copyright 2024 Rapyuta Robotics
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -14,9 +14,28 @@
from pathlib import Path

import click
import yaml
from click_help_colors import HelpColorsCommand

from riocli.constants import Colors, Symbols
from riocli.utils import tabulate_data


@click.command(
'list-examples',
cls=HelpColorsCommand,
help_headers_color=Colors.YELLOW,
help_options_color=Colors.GREEN,
help='List all examples supported in rio explain command'
)
def list_examples() -> None:
path = Path(__file__).parent.joinpath('manifests')

examples = []
for each in path.glob('*.yaml'):
examples.append([each.name.split('.yaml')[0]])

tabulate_data(examples, ['Examples'])


@click.command(
Expand Down
2 changes: 1 addition & 1 deletion riocli/apply/manifests/device.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ spec:
rosDistro: "melodic" # Options: ["kinetic", "melodic" (default), "noetic"]
virtual:
enabled: True # Required
product: "sootballs" # Required Options: ["sootballs", "flaptter", "oks"]
product: "sootballs" # Required Options: ["sootballs", "flaptter", "oks", "platform"]
arch: "amd64" # Options: ["amd64" (default), "arm64" ]
os: "ubuntu" # Options: ["ubuntu" (default), "debian" ]
codename: "focal" # Options: ["bionic", "focal" (default), "jammy", "bullseye"]
Expand Down
110 changes: 26 additions & 84 deletions riocli/apply/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
# 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.
import copy
import json
import queue
import threading
Expand All @@ -21,32 +20,18 @@
import click
import jinja2
import yaml
from tabulate import tabulate

from riocli.apply.resolver import ResolverCache
from riocli.config import Configuration
from riocli.constants import Colors, Symbols
from riocli.utils import dump_all_yaml, print_separator, run_bash
from riocli.utils import dump_all_yaml, run_bash
from riocli.utils.graph import Graphviz
from riocli.utils.spinner import with_spinner


class Applier(object):
DEFAULT_MAX_WORKERS = 6

EXPECTED_TIME = {
"organization": 3,
"project": 3,
"secret": 3,
"package": 3,
"staticroute": 3,
"disk": 180,
"deployment": 240,
"network": 120,
"device": 5,
"user": 3,
}

def __init__(self, files: typing.List, values, secrets):
self.environment = None
self.input_file_paths = files
Expand Down Expand Up @@ -179,28 +164,6 @@ def parse_dependencies(
self._add_graph_node(key)
number_of_objects = number_of_objects + 1

resource_list = []
total_time = 0

for node in copy.deepcopy(self.graph).static_order():
action = 'UPDATE'
if not self.resolved_objects[node]['src'] == 'remote':
action = 'CREATE'
elif delete:
action = 'DELETE'
kind = node.split(":")[0]
expected_time = round(
self.EXPECTED_TIME.get(kind.lower(), 5) / 60, 2)
total_time += expected_time
resource_list.append([node, action, expected_time])

if not template:
self._display_context(
total_time=total_time,
total_objects=number_of_objects,
resource_list=resource_list
)

if check_missing:
missing_resources = []
for key, item in self.resolved_objects.items():
Expand Down Expand Up @@ -252,25 +215,37 @@ def _register_object(self, data):
return

def _load_file_content(self, file_name, is_value=False, is_secret=False):
if not is_secret:
with open(file_name) as opened:
data = opened.read()
else:
data = run_bash('sops -d {}'.format(file_name))
"""Load the file content and return the parsed data.
# TODO: If no Kind in yaml/json, then skip
When the file is a template, render it using values or secrets.
"""
try:
if is_secret:
data = run_bash(f'sops -d {file_name}')
else:
with open(file_name) as f:
data = f.read()
except Exception as e:
raise Exception(f'Error loading file {file_name}: {str(e)}')

# When the file is a template, render it using
# values or secrets.
if not (is_value or is_secret):
if self.environment or file_name.endswith('.j2'):
template = self.environment.from_string(data)
try:
template = self.environment.from_string(data)
except Exception as e:
raise Exception(f'Error loading template {file_name}: {str(e)}')

template_args = self.values

if self.secrets:
template_args['secrets'] = self.secrets

try:
data = template.render(**template_args)
except Exception as ex:
click.secho('{} yaml parsing error. Msg: {}'.format(
file_name, str(ex)))
raise ex
raise Exception(f'Failed to parse {file_name}: {str(ex)}')

file_name = file_name.rstrip('.j2')

Expand All @@ -281,19 +256,13 @@ def _load_file_content(self, file_name, is_value=False, is_secret=False):
loaded = json.loads(data)
loaded_data.append(loaded)
except json.JSONDecodeError as ex:
ex_message = '{} yaml parsing error. Msg: {}'.format(
file_name, str(ex))
raise Exception(ex_message)

raise Exception(f'Failed to parse {file_name}: {str(ex)}')
elif file_name.endswith('yaml') or file_name.endswith('yml'):
try:
loaded = yaml.safe_load_all(data)
loaded_data = list(loaded)

except yaml.YAMLError as e:
ex_message = '{} yaml parsing error. Msg: {}'.format(
file_name, str(e))
raise Exception(ex_message)
raise Exception(f'Failed to parse {file_name}: {str(e)}')

if not loaded_data:
click.secho('{} file is empty'.format(file_name))
Expand Down Expand Up @@ -351,8 +320,7 @@ def _resolve_dependency(self, dependent_key, dependency):
dependent_key, obj_guid, dependency, obj)

if (name_or_guid == obj_name) and (
'version' in dependency and obj[
'packageVersion'] == dependency.get('version')):
'version' in dependency and obj['packageVersion'] == dependency.get('version')):
self._add_remote_object_to_resolve_tree(
dependent_key, obj_guid, dependency, obj)

Expand Down Expand Up @@ -403,32 +371,6 @@ def show_dependency_graph(self):
self.diagram.visualize()

# Utils
def _display_context(
self,
total_time: int,
total_objects: int,
resource_list: typing.List
) -> None:
headers = [
click.style('Resource Context', bold=True, fg=Colors.YELLOW)]
context = [
['Expected Time (mins)', round(total_time, 2)],
['Files', len(self.files)],
['Resources', total_objects],
]
click.echo(tabulate(context, headers=headers,
tablefmt='simple', numalign='center'))

# Display Resource Inventory
headers = []
for header in ['Resource', 'Action', 'Expected Time (mins)']:
headers.append(click.style(header, fg=Colors.YELLOW, bold=True))

print_separator()
click.echo(tabulate(resource_list, headers=headers,
tablefmt='simple', numalign='center'))
print_separator()

@staticmethod
def _get_attr(obj, accept_keys):
metadata = None
Expand Down
2 changes: 1 addition & 1 deletion riocli/apply/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,5 @@ def process_files_values_secrets(files, values, secrets):
if abs_secrets in glob_files:
glob_files.remove(abs_secrets)

glob_files = list(set(glob_files))
glob_files = sorted(list(set(glob_files)))
return glob_files, abs_values, abs_secret
1 change: 0 additions & 1 deletion riocli/auth/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ def login(
raise SystemExit(1)

ctx.obj.data['email_id'] = email
ctx.obj.data['password'] = password
ctx.obj.data['auth_token'] = get_token(email, password)

# Save if the file does not already exist
Expand Down
20 changes: 16 additions & 4 deletions riocli/auth/refresh_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,27 @@
help_options_color=Colors.GREEN,
)
@click.pass_context
def refresh_token(ctx: click.Context):
@click.option(
'--password',
type=str,
prompt=True,
hide_input=True,
help='Password for the rapyuta.io account',
)
def refresh_token(ctx: click.Context, password: str):
"""
Refreshes the authentication token after it expires
"""
email = ctx.obj.data.get('email_id', None)
password = ctx.obj.data.get('password', None)

if not ctx.obj.exists or not email or not password:
raise LoggedOut
try:
if not ctx.obj.exists or not email or not password:
raise LoggedOut
except LoggedOut as e:
click.secho(str(e), fg=Colors.RED)
raise SystemExit(1) from e

click.secho(f'Refreshing token for {email}...', fg=Colors.YELLOW)

ctx.obj.data['auth_token'] = get_token(email, password)

Expand Down
13 changes: 4 additions & 9 deletions riocli/auth/staging.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
# limitations under the License.
import click

from riocli.auth.login import select_project, select_organization
from riocli.auth.util import get_token
from riocli.config import Configuration
from riocli.constants import Colors, Symbols
from riocli.utils.context import get_root_context
Expand Down Expand Up @@ -62,16 +60,13 @@ def environment(ctx: click.Context, interactive: bool, name: str):
)
return

# Since credentials are retained, try fetching a token with the same.
email = ctx.obj.data.get('email_id', None)
password = ctx.obj.data.get('password', None)
ctx.obj.data['auth_token'] = get_token(email, password)

organization = select_organization(ctx.obj)
select_project(ctx.obj, organization=organization)
ctx.obj.data['email_id'] = None
ctx.obj.data['auth_token'] = None

ctx.obj.save()

click.secho(f'Your environment is set to {name}. Please login again using `rio auth login`', fg=Colors.GREEN)


def _configure_environment(config: Configuration, name: str) -> None:
is_valid_env = name in _NAMED_ENVIRONMENTS or name.startswith('pr')
Expand Down
4 changes: 2 additions & 2 deletions riocli/auth/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ def token(email: str, password: str, level: int = 0):
if not email:
email = config.data.get("email_id", None)

if not password:
password = config.data.get("password", None)
password = password or click.prompt('Password', hide_input=True)

if not config.exists or not email or not password:
raise LoggedOut

new_token = get_token(email, password)

click.echo(new_token)
Loading

0 comments on commit d8c02fd

Please sign in to comment.