Skip to content

Commit

Permalink
feat(organization): adds organization command in the CLI
Browse files Browse the repository at this point in the history
This commit introduces the organization command in the CLI that allows
you to list the organizations that you are a part of and let's you
switch between organiztions as well.

Usage: python -m riocli organization [OPTIONS] COMMAND [ARGS]...

  Organizations in rapyuta.io

Options:
  --help  Show this message and exit.

Commands:
  list    List all the organizations that you are a part of
  select  Sets the current organization to the one provided in the...

BREAKING CHANGE: This commit modifies the login command options.
Commands with the following structure will break

rio auth login --email <> --password <> --project <> --no-interactive
  • Loading branch information
pallabpain committed May 9, 2023
1 parent 5761c25 commit 5a5f599
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 16 deletions.
62 changes: 48 additions & 14 deletions riocli/auth/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
import click
from click_help_colors import HelpColorsCommand

from riocli.auth.util import select_project, get_token, select_organization
from riocli.auth.util import get_token, select_organization, select_project
from riocli.utils.context import get_root_context
from riocli.utils import print_separator

LOGIN_SUCCESS = click.style('Logged in successfully!', fg='green')


@click.command(
Expand All @@ -32,16 +33,23 @@
help='Context will be set to the organization after authentication')
@click.option('--project', type=str, default=None,
help='Context will be set to the project after authentication')
@click.option('--interactive/--no-interactive', is_flag=True, type=bool, default=True,
@click.option('--interactive/--no-interactive',
'--interactive/--silent', is_flag=True,
type=bool, default=True,
help='Make login interactive')
@click.pass_context
def login(ctx: click.Context, email: str, password: str,
organization: str, project: str, interactive: bool):
def login(
ctx: click.Context,
email: str,
password: str,
organization: str,
project: str,
interactive: bool
) -> None:
"""
Log into the Rapyuta.io account using the CLI. This is required to use most of the
functionalities of the CLI.
Log into the Rapyuta.io account using the CLI. This is required
to use most of the functionalities of the CLI.
"""

if interactive:
email = email or click.prompt('Email')
password = password or click.prompt('Password', hide_input=True)
Expand All @@ -63,14 +71,40 @@ def login(ctx: click.Context, email: str, password: str,
if not ctx.obj.exists or not interactive:
ctx.obj.save()
else:
click.echo("[Warning] rio already has a config file present")
click.confirm('Do you want to override the config', abort=True)
click.secho("[Warning] rio already has a config file present",
fg='yellow')
click.confirm('Do you want to override the config?', abort=True)

if not interactive:
# When just the email and password are provided
if not project and not organization:
click.echo(LOGIN_SUCCESS)
return

if not interactive and not project:
click.echo('Logged in successfully!')
return
# When project is provided without an organization.
# It is quite possible to have a project with the
# same name in two organizations. Hence, organization
# needs to be explicitly provided in this case.
if project and not organization:
click.secho(
'Please specify an organization. See `rio auth login --help`',
fg='yellow')
raise SystemExit(1)

# When just the organization is provided, we save the
# organization name and id and the login is marked as
# successful.
if organization and not project:
select_organization(ctx.obj, organization=organization)
click.secho("Your organization is set to '{}'".format(
ctx.obj.data['organization_name']), fg='green')
ctx.obj.save()
click.echo(LOGIN_SUCCESS)
return

organization = select_organization(ctx.obj, organization=organization)
select_project(ctx.obj, project=project, organization=organization)

ctx.obj.save()
click.echo('Logged in successfully!')

click.echo(LOGIN_SUCCESS)
8 changes: 6 additions & 2 deletions riocli/auth/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@
from rapyuta_io.utils import UnauthorizedError

from riocli.config import Configuration
from riocli.project.util import find_project_guid
from riocli.project.util import find_project_guid, find_organization_guid
from riocli.utils.selector import show_selection


def select_organization(config: Configuration, organization: str = None) -> str:
client = config.new_client(with_project=False)

org_guid = None

if organization:
org_guid = organization if organization.startswith('org-') else None
org_guid = organization if organization.startswith(
'org-') else find_organization_guid(client, name=organization)

# fetch user organizations and sort them on their name
organizations = client.get_user_organizations()
Expand Down Expand Up @@ -63,6 +65,8 @@ def select_project(config: Configuration, project: str = None, organization: str

projects = client.list_projects(organization_guid=organization)
if len(projects) == 0:
config.data['project_id'] = ""
config.data['project_name'] = ""
click.secho("There are no projects in this organization", fg='black', bg='white')
return

Expand Down
2 changes: 2 additions & 0 deletions riocli/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from riocli.managedservice import managedservice
from riocli.marketplace import marketplace
from riocli.network import network
from riocli.organization import organization
from riocli.package import package
from riocli.parameter import parameter
from riocli.project import project
Expand Down Expand Up @@ -95,3 +96,4 @@ def version():
cli.add_command(deprecated_repl)
cli.add_command(managedservice)
cli.add_command(template)
cli.add_command(organization)
35 changes: 35 additions & 0 deletions riocli/organization/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright 2023 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.
import click
from click_help_colors import HelpColorsGroup

from riocli.organization.list import list_organizations
from riocli.organization.select import select_organization


@click.group(
invoke_without_command=False,
cls=HelpColorsGroup,
help_headers_color='yellow',
help_options_color='green',
)
def organization() -> None:
"""
Organizations in rapyuta.io
"""
pass


organization.add_command(list_organizations)
organization.add_command(select_organization)
57 changes: 57 additions & 0 deletions riocli/organization/list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Copyright 2023 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.

import click

from riocli.config import new_client
from riocli.utils import tabulate_data


@click.command('list')
@click.pass_context
def list_organizations(ctx: click.Context) -> None:
"""
List all the organizations that you are a part of
Example:
rio organization list
"""
try:
client = new_client(with_project=False)
organizations = client.get_user_organizations()
current = ctx.obj.data['organization_id']
print_organizations(organizations, current)
except Exception as e:
click.secho(str(e), fg='red')
raise SystemExit(1) from e


def print_organizations(organizations, current):
organizations = sorted(organizations, key=lambda o: o.name)

headers = ["Name", "GUID", "Creator", "Short GUID"]

data = []
for org in organizations:
fg = None
if org.guid == current:
fg = 'green'
data.append([
click.style(v, fg=fg)
for v in (org.name, org.guid,
org.creator, org.short_guid)
])

tabulate_data(data, headers)
46 changes: 46 additions & 0 deletions riocli/organization/select.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright 2023 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.
import click

from riocli.auth.util import select_project
from riocli.utils.context import get_root_context
from riocli.project.util import name_to_organization_guid


@click.command('select')
@click.argument('organization-name', type=str)
@click.pass_context
@name_to_organization_guid
def select_organization(ctx: click.Context, organization_name: str, organization_guid: str) -> None:
"""
Sets the current organization to the one provided
in the argument and prompts you to select a new project
in the changed organization
Example:
rio organization select other-org
"""
ctx = get_root_context(ctx)

if ctx.obj.data['organization_id'] == organization_guid:
click.secho("You are already in the '{}' organization".format(organization_name), fg='green')
return

ctx.obj.data['organization_id'] = organization_guid
ctx.obj.data['organization_name'] = organization_name

select_project(ctx.obj, organization=organization_guid)

ctx.obj.save()

0 comments on commit 5a5f599

Please sign in to comment.