Skip to content

Commit

Permalink
feat(bootstrap): prompt for env tokens (#114)
Browse files Browse the repository at this point in the history
* feat(bootstrap): prompt user for empty values in `.env` file
  • Loading branch information
mzaatar authored Dec 30, 2022
1 parent dfdd95e commit a6fe18f
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 13 deletions.
48 changes: 39 additions & 9 deletions src/algokit/core/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
import platform
import sys
from pathlib import Path
from shutil import copyfile, which
from shutil import which
from typing import Callable, Iterator

import click
import questionary

from algokit.core import proc

Expand Down Expand Up @@ -51,14 +52,43 @@ def bootstrap_env(project_dir: Path) -> None:

if env_path.exists():
logger.info(".env already exists; skipping bootstrap of .env")
else:
logger.debug(f"{env_path} doesn't exist yet")
if not env_template_path.exists():
logger.info("No .env or .env.template file; nothing to do here, skipping bootstrap of .env")
else:
logger.debug(f"{env_template_path} exists")
logger.info(f"Copying {env_template_path} to {env_path}")
copyfile(env_template_path, env_path)
return

logger.debug(f"{env_path} doesn't exist yet")
if not env_template_path.exists():
logger.info("No .env or .env.template file; nothing to do here, skipping bootstrap of .env")
return

logger.debug(f"{env_template_path} exists")
logger.info(f"Copying {env_template_path} to {env_path} and prompting for empty values")
# find all empty values in .env file and prompt the user for a value
with env_template_path.open(encoding="utf-8") as env_template_file, env_path.open(
mode="w", encoding="utf-8"
) as env_file:
comment_lines: list[str] = []
for line in env_template_file:
# strip newline character(s) from end of line for simpler handling
stripped_line = line.strip()
# if it is a comment line, keep it in var and continue
if stripped_line.startswith("#"):
comment_lines.append(line)
env_file.write(line)
# keep blank lines in output but don't accumulate them in comments
elif not stripped_line:
env_file.write(line)
else:
# lines not blank and not empty
var_name, *var_value = stripped_line.split("=", maxsplit=1)
# if it is an empty value, the user should be prompted for value with the comment line above
if var_value and not var_value[0]:
logger.info("".join(comment_lines))
var_name = var_name.strip()
new_value = questionary.text(f"Please provide a value for {var_name}:").unsafe_ask()
env_file.write(f"{var_name}={new_value}\n")
else:
# this is a line with value, reset comment lines.
env_file.write(line)
comment_lines = []


def bootstrap_poetry(project_dir: Path, install_prompt: Callable[[str], bool]) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ DEBUG: Checking {current_working_directory} for bootstrapping needs
DEBUG: Running `algokit bootstrap env`
DEBUG: {current_working_directory}/.env doesn't exist yet
DEBUG: {current_working_directory}/.env.template exists
Copying {current_working_directory}/.env.template to {current_working_directory}/.env
Copying {current_working_directory}/.env.template to {current_working_directory}/.env and prompting for empty values
Finished bootstrapping {current_working_directory}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ DEBUG: Checking {current_working_directory}/live_dir for bootstrapping needs
DEBUG: Running `algokit bootstrap env`
DEBUG: {current_working_directory}/live_dir/.env doesn't exist yet
DEBUG: {current_working_directory}/live_dir/.env.template exists
Copying {current_working_directory}/live_dir/.env.template to {current_working_directory}/live_dir/.env
Copying {current_working_directory}/live_dir/.env.template to {current_working_directory}/live_dir/.env and prompting for empty values
DEBUG: Running `algokit bootstrap poetry`
DEBUG: Running 'poetry --version' in '{current_working_directory}'
DEBUG: poetry: STDOUT
Expand Down
98 changes: 97 additions & 1 deletion tests/bootstrap/test_bootstrap_env.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
import click
import pytest
from _pytest.tmpdir import TempPathFactory
from utils.approvals import verify
from approvaltests.scrubbers.scrubbers import Scrubber
from prompt_toolkit.input import PipeInput
from utils.approvals import TokenScrubber, combine_scrubbers, verify
from utils.click_invoker import invoke

from tests import get_combined_verify_output


def make_output_scrubber(**extra_tokens: str) -> Scrubber:
default_tokens = {
"KqueueSelector": "Using selector: KqueueSelector",
"EpollSelector": "Using selector: EpollSelector",
"IocpProactor": "Using proactor: IocpProactor",
}
tokens = default_tokens | extra_tokens
return combine_scrubbers(
click.unstyle,
TokenScrubber(tokens=tokens),
lambda t: t.replace("KqueueSelector", "selector")
.replace("EpollSelector", "selector")
.replace("IocpProactor", "selector"),
)


def test_bootstrap_env_no_files(tmp_path_factory: TempPathFactory):
cwd = tmp_path_factory.mktemp("cwd")

Expand Down Expand Up @@ -41,3 +61,79 @@ def test_bootstrap_env_dotenv_missing_template_exists(tmp_path_factory: TempPath

assert result.exit_code == 0
verify(get_combined_verify_output(result.output, ".env", (cwd / ".env").read_text("utf-8")))


def test_bootstrap_env_dotenv_with_values(tmp_path_factory: TempPathFactory, mock_questionary_input: PipeInput):
cwd = tmp_path_factory.mktemp("cwd")
(cwd / ".env.template").write_text(
"""
TOKEN_1=123
# comment for token 2 - you should enter a valid value
# another comment
TOKEN_2_WITH_MULTI_LINES_COMMENT=test
TOKEN_3=test value with spaces
TOKEN_4_WITH_NO_EQUALS_SIGN
# another comment
TOKEN_5_SPECIAL_CHAR=*
"""
)

result = invoke(
"bootstrap env",
cwd=cwd,
)

assert result.exit_code == 0
verify(get_combined_verify_output(result.output, ".env", (cwd / ".env").read_text("utf-8")))


def test_bootstrap_env_dotenv_different_prompt_scenarios(
tmp_path_factory: TempPathFactory,
mock_questionary_input: PipeInput,
request: pytest.FixtureRequest,
mock_os_dependency: None,
):
cwd = tmp_path_factory.mktemp("cwd")
(cwd / ".env.template").write_text(
"""
TOKEN_1=123
# comment for token 2 - you should enter a valid value
# another comment
TOKEN_2_WITH_MULTI_LINES_COMMENT=
TOKEN_3=test value
TOKEN_4_WITH_SPACES =
TOKEN_5_WITHOUT_COMMENT=
TOKEN_WITH_NO_EQUALS_SIGN
# another comment
TOKEN_6_EMPTY_WITH_COMMENT=
TOKEN_7_VALUE_WILL_BE_EMPTY=
TOKEN_8 = value with spaces
TOKEN_8_SPECIAL_CHAR=*
"""
)

# provide values for tokens
mock_questionary_input.send_text("test value for TOKEN_2_WITH_MULTI_LINES_COMMENT")
mock_questionary_input.send_text("\n") # enter
mock_questionary_input.send_text("test value for TOKEN_4_WITH_SPACES")
mock_questionary_input.send_text("\n") # enter
mock_questionary_input.send_text("test value for TOKEN_5_WITHOUT_COMMENT")
mock_questionary_input.send_text("\n") # enter
mock_questionary_input.send_text("test value for TOKEN_6_EMPTY_WITH_COMMENT")
mock_questionary_input.send_text("\n") # enter
mock_questionary_input.send_text("") # Empty value for TOKEN_7_VALUE_WILL_BE_EMPTY
mock_questionary_input.send_text("\n") # enter

result = invoke(
"bootstrap env",
cwd=cwd,
)

assert result.exit_code == 0
verify(
get_combined_verify_output(result.output, ".env", (cwd / ".env").read_text("utf-8")),
scrubber=make_output_scrubber(),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
DEBUG: {current_working_directory}/.env doesn't exist yet
DEBUG: {current_working_directory}/.env.template exists
Copying {current_working_directory}/.env.template to {current_working_directory}/.env and prompting for empty values
# comment for token 2 - you should enter a valid value
# another comment

DEBUG: {selector}


# another comment


----
.env:
----

TOKEN_1=123

# comment for token 2 - you should enter a valid value
# another comment
TOKEN_2_WITH_MULTI_LINES_COMMENT=test value for TOKEN_2_WITH_MULTI_LINES_COMMENT
TOKEN_3=test value

TOKEN_4_WITH_SPACES=test value for TOKEN_4_WITH_SPACES
TOKEN_5_WITHOUT_COMMENT=test value for TOKEN_5_WITHOUT_COMMENT
TOKEN_WITH_NO_EQUALS_SIGN
# another comment
TOKEN_6_EMPTY_WITH_COMMENT=test value for TOKEN_6_EMPTY_WITH_COMMENT
TOKEN_7_VALUE_WILL_BE_EMPTY=
TOKEN_8 = value with spaces
TOKEN_8_SPECIAL_CHAR=*
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
DEBUG: {current_working_directory}/.env doesn't exist yet
DEBUG: {current_working_directory}/.env.template exists
Copying {current_working_directory}/.env.template to {current_working_directory}/.env
Copying {current_working_directory}/.env.template to {current_working_directory}/.env and prompting for empty values
----
.env:
----
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
DEBUG: {current_working_directory}/.env doesn't exist yet
DEBUG: {current_working_directory}/.env.template exists
Copying {current_working_directory}/.env.template to {current_working_directory}/.env and prompting for empty values
----
.env:
----

TOKEN_1=123
# comment for token 2 - you should enter a valid value
# another comment
TOKEN_2_WITH_MULTI_LINES_COMMENT=test
TOKEN_3=test value with spaces

TOKEN_4_WITH_NO_EQUALS_SIGN
# another comment
TOKEN_5_SPECIAL_CHAR=*

1 comment on commit a6fe18f

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
src/algokit
   __init__.py15753%6–13, 17–24, 32–34
   __main__.py220%1–3
src/algokit/cli
   completions.py103199%91
   init.py1651690%57, 204–205, 236, 239–241, 252, 290, 327, 336–338, 341–346, 361
   sandbox.py84199%155
src/algokit/core
   bootstrap.py1331688%109, 129, 201, 204, 210–224
   click_extensions.py472057%40–43, 50, 56, 67–68, 73–74, 79–80, 91, 104–114
   conf.py30487%13, 17, 25, 27
   doctor.py54394%63–65
   log_handlers.py68987%44–45, 50–51, 63, 112–116, 125
   proc.py44198%95
   sandbox.py112794%86, 151, 167, 182–184, 199
   version_prompt.py70987%27–28, 33, 52–55, 66, 76, 98
TOTAL11149691% 

Tests Skipped Failures Errors Time
156 0 💤 0 ❌ 0 🔥 10.596s ⏱️

Please sign in to comment.