8000 Add profile as an option to every click command by jw2 · Pull Request #12500 · localstack/localstack · GitHub
[go: up one dir, main page]

Skip to content

Add profile as an option to every click command #12500

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 15, 2025
Merged
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
5 changes: 3 additions & 2 deletions localstack-core/localstack/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ def main():
os.environ["LOCALSTACK_CLI"] = "1"

# config profiles are the first thing that need to be loaded (especially before localstack.config!)
from .profiles import set_profile_from_sys_argv
from .profiles import set_and_remove_profile_from_sys_argv

set_profile_from_sys_argv()
# WARNING: This function modifies sys.argv to remove the profile argument.
set_and_remove_profile_from_sys_argv()

# initialize CLI plugins
from .localstack import create_with_plugins
Expand Down
52 changes: 39 additions & 13 deletions localstack-core/localstack/cli/profiles.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,66 @@
import argparse
import os
import sys
from typing import Optional

# important: this needs to be free of localstack imports


def set_profile_from_sys_argv():
def set_and_remove_profile_from_sys_argv():
"""
Reads the --profile flag from sys.argv and then sets the 'CONFIG_PROFILE' os variable accordingly. This is later
picked up by ``localstack.config``.
Performs the following steps:

1. Use argparse to parse the command line arguments for the --profile flag.
All occurrences are removed from the sys.argv list, and the value from
the last occurrence is used. This allows the user to specify a profile
at any point on the command line.

2. If a --profile flag is not found, check for the -p flag. The first
occurrence of the -p flag is used and it is not removed from sys.argv.
The reasoning for this is that at least one of the CLI subcommands has
a -p flag, and we want to keep it in sys.argv for that command to
pick up. An existing bug means that if a -p flag is used with a
subcommand, it could erroneously be used as the profile value as well.
This behaviour is undesired, but we must maintain back-compatibility of
allowing the profile to be specified using -p.

3. If a profile is found, the 'CONFIG_PROFILE' os variable is set
accordingly. This is later picked up by ``localstack.config``.

WARNING: Any --profile options are REMOVED from sys.argv, so that they are
not passed to the localstack CLI. This allows the profile option
to be set at any point on the command line.
"""
profile = parse_profile_argument(sys.argv)
parser = argparse.ArgumentParser()
parser.add_argument("--profile")
namespace, sys.argv = parser.parse_known_args(sys.argv)
profile = namespace.profile

if not profile:
# if no profile is given, check for the -p argument
profile = parse_p_argument(sys.argv)

if profile:
os.environ["CONFIG_PROFILE"] = profile.strip()


def parse_profile_argument(args) -> Optional[str]:
def parse_p_argument(args) -> Optional[str]:
"""
Lightweight arg parsing to find ``--profile <config>``, or ``--profile=<config>`` and return the value of
Lightweight arg parsing to find the first occurrence of ``-p <config>``, or ``-p=<config>`` and return the value of
``<config>`` from the given arguments.

:param args: list of CLI arguments
:returns: the value of ``--profile``.
:returns: the value of ``-p``.
"""
for i, current_arg in enumerate(args):
if current_arg.startswith("--profile="):
# if using the "<arg>=<value>" notation, we remove the "--profile=" prefix to get the value
return current_arg[10:]
elif current_arg.startswith("-p="):
if current_arg.startswith("-p="):
# if using the "<arg>=<value>" notation, we remove the "-p=" prefix to get the value
return current_arg[3:]
if current_arg in ["--profile", "-p"]:
if current_arg == "-p":
# otherwise use the next arg in the args list as value
try:
return args[i + 1]
except KeyError:
except IndexError:
return None

return None
148 changes: 139 additions & 9 deletions tests/unit/cli/test_profiles.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,148 @@
import os
import sys

from localstack.cli.profiles import set_profile_from_sys_argv
from localstack.cli.profiles import set_and_remove_profile_from_sys_argv


def test_profiles_equals_notation(monkeypatch):
monkeypatch.setattr(sys, "argv", ["--profile=non-existing-test-profile"])
def profile_test(monkeypatch, input_args, expected_profile, expected_argv):
monkeypatch.setattr(sys, "argv", input_args)
monkeypatch.setenv("CONFIG_PROFILE", "")
set_profile_from_sys_argv()
assert os.environ["CONFIG_PROFILE"] == "non-existing-test-profile"
set_and_remove_profile_from_sys_argv()
assert os.environ["CONFIG_PROFILE"] == expected_profile
assert sys.argv == expected_argv


def test_profiles_equals_notation(monkeypatch):
profile_test(
monkeypatch,
input_args=["--profile=non-existing-test-profile"],
expected_profile="non-existing-test-profile",
expected_argv=[],
)


def test_profiles_separate_args_notation(monkeypatch):
monkeypatch.setattr(sys, "argv", ["--profile", "non-existing-test-profile"])
monkeypatch.setenv("CONFIG_PROFILE", "")
set_profile_from_sys_argv()
assert os.environ["CONFIG_PROFILE"] == "non-existing-test-profile"
profile_test(
monkeypatch,
input_args=["--profile", "non-existing-test-profile"],
expected_profile="non-existing-test-profile",
expected_argv=[],
)


def test_p_equals_notation(monkeypatch):
profile_test(
monkeypatch,
input_args=["-p=non-existing-test-profile"],
expected_profile="non-existing-test-profile",
expected_argv=["-p=non-existing-test-profile"],
)


def test_p_separate_args_notation(monkeypatch):
profile_test(
monkeypatch,
input_args=["-p", "non-existing-test-profile"],
expected_profile="non-existing-test-profile",
expected_argv=["-p", "non-existing-test-profile"],
)


def test_profiles_args_before_and_after(monkeypatch):
profile_test(
monkeypatch,
input_args=["cli", "-D", "--profile=non-existing-test-profile", "start"],
expected_profile="non-existing-test-profile",
expected_argv=["cli", "-D", "start"],
)


def test_profiles_args_before_and_after_separate(monkeypatch):
profile_test(
monkeypatch,
input_args=["cli", "-D", "--profile", "non-existing-test-profile", "start"],
expected_profile="non-existing-test-profile",
expected_argv=["cli", "-D", "start"],
)


def test_p_args_before_and_after_separate(monkeypatch):
profile_test(
monkeypatch,
input_args=["cli", "-D", "-p", "non-existing-test-profile", "start"],
expected_profile="non-existing-test-profile",
expected_argv=["cli", "-D", "-p", "non-existing-test-profile", "start"],
)


def test_profiles_args_multiple(monkeypatch):
profile_test(
monkeypatch,
input_args=[
"cli",
"--profile",
"non-existing-test-profile",
"start",
"--profile",
"another-profile",
],
expected_profile="another-profile",
expected_argv=["cli", "start"],
)


def test_p_args_multiple(monkeypatch):
profile_test(
monkeypatch,
input_args=[
"cli",
"-p",
"non-existing-test-profile",
"start",
"-p",
"another-profile",
],
expected_profile="non-existing-test-profile",
expected_argv=[
"cli",
"-p",
"non-existing-test-profile",
"start",
"-p",
"another-profile",
],
)


def test_p_and_profile_args(monkeypatch):
profile_test(
monkeypatch,
input_args=[
"cli",
"-p",
"non-existing-test-profile",
"start",
"--profile",
"the_profile",
"-p",
"another-profile",
],
expected_profile="the_profile",
expected_argv=[
"cli",
"-p",
"non-existing-test-profile",
"start",
"-p",
"another-profile",
],
)


def test_trailing_p_argument(monkeypatch):
profile_test(
monkeypatch,
input_args=["cli", "start", "-p"],
expected_profile="",
expected_argv=["cli", "start", "-p"],
)
Loading
0