Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 52 additions & 31 deletions stacklet/client/platform/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,26 @@
import jwt
import requests

from stacklet.client.platform.cognito import CognitoUserManager
from stacklet.client.platform.commands import commands
from stacklet.client.platform.config import StackletConfig
from stacklet.client.platform.context import StackletContext
from stacklet.client.platform.formatter import Formatter
from stacklet.client.platform.utils import click_group_entry, default_options
from .cognito import CognitoUserManager
from .commands import commands
from .config import DEFAULT_CONFIG_FILE, StackletConfig
from .context import StackletContext
from .utils import default_options, setup_logging


@click.group()
@default_options()
@click.pass_context
def cli(*args, **kwargs):
def cli(
ctx,
config,
output,
cognito_user_pool_id,
cognito_client_id,
cognito_region,
api,
v,
):
"""
Stacklet CLI

Expand Down Expand Up @@ -77,7 +85,25 @@ def cli(*args, **kwargs):
--after $after_token \\
list
"""
click_group_entry(*args, **kwargs)
setup_logging(v)

config_items = [cognito_user_pool_id, cognito_client_id, cognito_region, api]
if any(config_items) and not all(config_items):
raise Exception(
"All options must be set for config items: --cognito-user-pool-id, "
+ "--cognito-client-id, --cognito-region, and --api"
)

raw_config = None
if all(config_items):
raw_config = {
"cognito_user_pool_id": cognito_user_pool_id,
"cognito_client_id": cognito_client_id,
"region": cognito_region,
"api": api,
}

ctx.obj = StackletContext(raw_config=raw_config, config_file=config, output_format=output)


@cli.command(short_help="Configure stacklet-admin cli")
Expand All @@ -89,9 +115,7 @@ def cli(*args, **kwargs):
@click.option("--auth-url", prompt="(SSO) Auth Url", default="")
@click.option("--cubejs", prompt="Stacklet cube.js endpoint", default="")
@click.option("--location", prompt="Config File Location", default="~/.stacklet/config.json") # noqa
@click.pass_context
def configure(
ctx,
api,
region,
cognito_client_id,
Expand Down Expand Up @@ -140,7 +164,7 @@ def configure(
)
@click.option(
"--location",
default="~/.stacklet/config.json",
default=str(DEFAULT_CONFIG_FILE),
type=click.Path(path_type=pathlib.Path, dir_okay=False),
show_default=True,
)
Expand Down Expand Up @@ -263,19 +287,17 @@ def auto_configure(url, prefix, idp, location):


@cli.command()
@click.pass_context
def show(ctx):
@click.pass_obj
def show(obj):
"""
Show your config
"""
context = StackletContext(ctx.obj["config"], ctx.obj["raw_config"])
fmt = Formatter.registry.get(ctx.obj["output"])()
if os.path.exists(os.path.expanduser(StackletContext.DEFAULT_ID)):
with open(os.path.expanduser(StackletContext.DEFAULT_ID), "r") as f:
id_details = jwt.decode(f.read(), options={"verify_signature": False})
fmt = obj.formatter()
if id_token := obj.credentials.id_token():
id_details = jwt.decode(id_token, options={"verify_signature": False})
click.echo(fmt(id_details))
click.echo()
click.echo(fmt(context.config.to_json()))
click.echo(fmt(obj.config.to_json()))


@cli.command(short_help="Login to Stacklet")
Expand All @@ -295,38 +317,37 @@ def login(ctx, username, password):

If password is not passed in, your password will be prompted
"""
context = StackletContext(ctx.obj["config"], ctx.obj["raw_config"])
context = ctx.obj
config = context.config
can_sso_login = context.can_sso_login()
# sso login
if context.can_sso_login() and not any([username, password]):
if can_sso_login and not any([username, password]):
from stacklet.client.platform.vendored.auth import BrowserAuthenticator

BrowserAuthenticator(
authority_url=context.config.auth_url,
client_id=context.config.cognito_client_id,
idp_id=context.config.idp_id,
authority_url=config.auth_url,
client_id=config.cognito_client_id,
idp_id=config.idp_id,
)()
return
elif not context.can_sso_login() and not any([username, password]):
elif not can_sso_login and not any([username, password]):
click.echo(
"To login with SSO ensure that your configuration includes "
+ "auth_url, and cognito_client_id values."
)
raise Exception()
ctx.exit(1)

# handle login with username/password
if not username:
username = click.prompt("Username")
if not password:
password = click.prompt("Password", hide_input=True)
manager = CognitoUserManager.from_context(context)
res = manager.login(
id_token, access_token = manager.login(
user=username,
password=password,
)
if not os.path.exists(os.path.dirname(os.path.expanduser(StackletContext.DEFAULT_CREDENTIALS))):
os.makedirs(os.path.dirname(os.path.expanduser(StackletContext.DEFAULT_CREDENTIALS)))
with open(os.path.expanduser(StackletContext.DEFAULT_CREDENTIALS), "w+") as f: # noqa
f.write(res)
context.credentials.write(id_token, access_token)


for c in commands:
Expand Down
21 changes: 9 additions & 12 deletions stacklet/client/platform/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,27 @@

# platform client using cli

from pathlib import Path

import jmespath

# import the commands so the registry is populated when the client is instantiated
import stacklet.client.platform.commands # noqa
from stacklet.client.platform.context import StackletContext
from stacklet.client.platform.executor import StackletGraphqlExecutor
from stacklet.client.platform.utils import _PAGINATION_OPTIONS, get_token
from . import commands # noqa
from .config import DEFAULT_CONFIG_FILE
from .context import StackletContext
from .executor import StackletGraphqlExecutor
from .utils import _PAGINATION_OPTIONS


def platform_client(pager=False, expr=False):
# for more pythonic experience, pass expr=True to de-graphqlize the result
# for automatic pagination handling, pass pager=True
if (
not Path(StackletContext.DEFAULT_CONFIG).expanduser().exists()
or not Path(StackletContext.DEFAULT_CREDENTIALS).expanduser().exists()
):
context = StackletContext(raw_config={})
if not DEFAULT_CONFIG_FILE.exists() or not context.credentials.api_token():
raise ValueError("Please configure and authenticate on stacklet-admin cli")

d = {}
ctx = StackletContext(raw_config={})
token = get_token()
executor = StackletGraphqlExecutor(ctx, token)

executor = StackletGraphqlExecutor(context)

for k, snippet in StackletGraphqlExecutor.registry.items():
# assert k == snippet.name, f"{k} mismatch {snippet.name}"
Expand Down
5 changes: 3 additions & 2 deletions stacklet/client/platform/cognito.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,15 @@ def create_user(self, user, password, email, phone_number, permanent=True):
self.log.debug(res)
return True

def login(self, user, password):
def login(self, user, password) -> tuple[str, str]:
res = self.client.initiate_auth(
ClientId=self.user_pool_client_id,
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": user, "PASSWORD": password},
)
self.log.debug("Authentication Success")
return res["AuthenticationResult"]["AccessToken"]
auth = res["AuthenticationResult"]
return auth["IdToken"], auth["AccessToken"]

def ensure_group(self, user, group) -> bool:
try:
Expand Down
50 changes: 24 additions & 26 deletions stacklet/client/platform/commands/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from stacklet.client.platform.executor import StackletGraphqlExecutor, _run_graphql, snippet_options
from stacklet.client.platform.graphql import StackletGraphqlSnippet
from stacklet.client.platform.utils import click_group_entry, default_options
from stacklet.client.platform.utils import default_options


@StackletGraphqlExecutor.registry.register("list-accounts")
Expand Down Expand Up @@ -273,7 +273,6 @@ class ValidateAccountSnippet(StackletGraphqlSnippet):

@click.group(short_help="Run account queries/mutations")
@default_options()
@click.pass_context
def account(*args, **kwargs):
"""
Query against and Run mutations against Account objects in Stacklet.
Expand All @@ -287,86 +286,85 @@ def account(*args, **kwargs):
$ stacklet account --output json list

"""
click_group_entry(*args, **kwargs)


@account.command()
@snippet_options("list-accounts")
@click.pass_context
def list(ctx, **kwargs):
@click.pass_obj
def list(obj, **kwargs):
"""
List cloud accounts in Stacklet
"""
click.echo(_run_graphql(ctx=ctx, name="list-accounts", variables=kwargs))
click.echo(_run_graphql(obj, name="list-accounts", variables=kwargs))


@account.command()
@snippet_options("add-account")
@click.pass_context
def add(ctx, **kwargs):
@click.pass_obj
def add(obj, **kwargs):
"""
Add an account to Stacklet
"""
click.echo(_run_graphql(ctx=ctx, name="add-account", variables=kwargs))
click.echo(_run_graphql(obj, name="add-account", variables=kwargs))


@account.command()
@snippet_options("remove-account")
@click.pass_context
def remove(ctx, **kwargs):
@click.pass_obj
def remove(obj, **kwargs):
"""
Remove an account from Stacklet
"""
click.echo(_run_graphql(ctx=ctx, name="remove-account", variables=kwargs))
click.echo(_run_graphql(obj, name="remove-account", variables=kwargs))


@account.command()
@snippet_options("update-account")
@click.pass_context
def update(ctx, **kwargs):
@click.pass_obj
def update(obj, **kwargs):
"""
Update an account in platform
"""
click.echo(_run_graphql(ctx=ctx, name="update-account", variables=kwargs))
click.echo(_run_graphql(obj, name="update-account", variables=kwargs))


@account.command()
@snippet_options("show-account")
@click.pass_context
def show(ctx, **kwargs):
@click.pass_obj
def show(obj, **kwargs):
"""
Show an account in Stacklet
"""
click.echo(_run_graphql(ctx=ctx, name="show-account", variables=kwargs))
click.echo(_run_graphql(obj, name="show-account", variables=kwargs))


@account.command()
@snippet_options("validate-account")
@click.pass_context
def validate(ctx, **kwargs):
@click.pass_obj
def validate(obj, **kwargs):
"""
Validate an account in Stacklet
"""
click.echo(_run_graphql(ctx=ctx, name="validate-account", variables=kwargs))
click.echo(_run_graphql(obj, name="validate-account", variables=kwargs))


@account.command()
@snippet_options("list-accounts")
@click.pass_context
def validate_all(ctx, **kwargs):
@click.pass_obj
def validate_all(obj, **kwargs):
"""
Validate all accounts in Stacklet
"""
result = _run_graphql(ctx=ctx, name="list-accounts", variables=kwargs, raw=True)
result = _run_graphql(obj, name="list-accounts", variables=kwargs, raw=True)

# get all the accounts
count = result["data"]["accounts"]["pageInfo"]["total"]
kwargs["last"] = count

result = _run_graphql(ctx=ctx, name="list-accounts", variables=kwargs, raw=True)
result = _run_graphql(obj, name="list-accounts", variables=kwargs, raw=True)
account_provider_pairs = [
{"provider": r["node"]["provider"], "key": r["node"]["key"]}
for r in result["data"]["accounts"]["edges"]
]
for pair in account_provider_pairs:
click.echo(_run_graphql(ctx=ctx, name="validate-account", variables=pair))
click.echo(_run_graphql(obj, name="validate-account", variables=pair))
Loading