Skip to content
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

feat: adding flags to enable dev mode; flags for custom path to localnet config #569

Merged
merged 15 commits into from
Sep 19, 2024
Merged
7 changes: 1 addition & 6 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ repos:
types: [python]
args: [--no-cache]
require_serial: true
additional_dependencies: []
minimum_pre_commit_version: "0"
exclude: ^src/.*core/_vendor/
- id: ruff
name: ruff
description: "Run 'ruff' for extremely fast Python linting"
Expand All @@ -19,8 +18,6 @@ repos:
"types": [python]
args: [--fix, --no-cache]
require_serial: false
additional_dependencies: []
minimum_pre_commit_version: "0"
files: "^(src|tests)/"
exclude: "^src/algokit/core/_vendor/"
- id: mypy
Expand All @@ -30,7 +27,5 @@ repos:
language: system
types_or: [python, pyi]
require_serial: true
additional_dependencies: []
minimum_pre_commit_version: "2.9.2"
files: "^(src|tests)/"
exclude: "^src/algokit/core/_vendor/"
28 changes: 24 additions & 4 deletions docs/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,19 @@
- [reset](#reset)
- [Options](#options-16)
- [--update, --no-update](#--update---no-update)
- [-P, --config-dir ](#-p---config-dir-)
- [start](#start)
- [Options](#options-17)
- [-n, --name ](#-n---name--1)
- [-P, --config-dir ](#-p---config-dir--1)
- [-d, --dev, --no-dev](#-d---dev---no-dev)
- [--force](#--force)
- [status](#status)
- [stop](#stop)
- [project](#project)
- [bootstrap](#bootstrap)
- [Options](#options-18)
- [--force](#--force)
- [--force](#--force-1)
- [Options](#options-19)
- [--interactive, --non-interactive, --ci](#--interactive---non-interactive---ci)
- [-p, --project-name ](#-p---project-name-)
Expand Down Expand Up @@ -148,7 +152,7 @@
- [analyze](#analyze)
- [Options](#options-23)
- [-r, --recursive](#-r---recursive)
- [--force](#--force-1)
- [--force](#--force-2)
- [--diff](#--diff)
- [-o, --output ](#-o---output--2)
- [-e, --exclude ](#-e---exclude-)
Expand Down Expand Up @@ -199,7 +203,7 @@
- [-f, --file ](#-f---file--3)
- [-t, --transaction ](#-t---transaction--1)
- [-o, --output ](#-o---output--4)
- [--force](#--force-2)
- [--force](#--force-3)
- [transfer](#transfer)
- [Options](#options-31)
- [-s, --sender ](#-s---sender-)
Expand Down Expand Up @@ -799,6 +803,10 @@ algokit localnet reset [OPTIONS]
### --update, --no-update
Enable or disable updating to the latest available LocalNet version, default: don't update


### -P, --config-dir <config_path>
Specify the custom localnet configuration directory.

### start

Start the AlgoKit LocalNet.
Expand All @@ -811,7 +819,19 @@ algokit localnet start [OPTIONS]


### -n, --name <name>
Specify a name for a custom LocalNet instance. AlgoKit will not manage the configuration of named LocalNet instances, allowing developers to configure it in any way they need.
Specify a name for a custom LocalNet instance. AlgoKit will not manage the configuration of named LocalNet instances, allowing developers to configure it in any way they need. Defaults to 'sandbox'.


### -P, --config-dir <config_path>
Specify the custom localnet configuration directory. Defaults to '/Users/aorumbayev/.config/algokit'.


### -d, --dev, --no-dev
Control whether to launch 'algod' in developer mode or not. Defaults to 'yes'.


### --force
Ignore the prompt to stop the LocalNet if it's already running.

### status

Expand Down
30 changes: 30 additions & 0 deletions docs/features/localnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ To create / start your AlgoKit LocalNet instance you can run `algokit localnet s
- Create a new Docker Compose deployment for AlgoKit LocalNet if it doesn't already exist
- (Re-)Start the containers

You can also specify additional options:

- `--name`: Specify a name for a custom LocalNet instance. This allows you to have multiple LocalNet configurations. Refer to [Named LocalNet Configuration Directory](#named-localnet-configuration-directory) for more details.
- `--config-dir`: Specify a custom configuration directory for the LocalNet.
- `--dev/--no-dev`: Control whether to launch 'algod' in developer mode or not. Defaults to 'yes' (developer mode enabled).

If it's the first time running it on your machine then it will download the following images from DockerHub:

- [`algorand/algod`](https://hub.docker.com/r/algorand/algod) (~500 MB)
Expand Down Expand Up @@ -82,6 +88,16 @@ When you want more control, named LocalNet instances can be used by running `alg
Once you have a named LocalNet running, the AlgoKit LocalNet commands will target this instance.
If at any point you'd like to switch back to the default LocalNet, simply run `algokit localnet start`.

### Specifying a custom LocalNet configuration directory

You can specify a custom LocalNet configuration directory by using the `--config-dir` option or by setting the `ALGOKIT_LOCALNET_CONFIG_DIR` environment variable. This allows you to have multiple LocalNet instances with different configurations in different directories, which is useful in 'CI/CD' scenarios where you can save your custom localnet in your version control and then run `algokit localnet start --config-dir /path/to/custom/config` to use it within your pipeline.

For example, to create a LocalNet instance with a custom configuration directory, you can run:

```
algokit localnet start --config-dir /path/to/custom/config
```

### Named LocalNet Configuration Directory

When running `algokit localnet start --name {name}`, AlgoKit stores configuration files in a specific directory on your system. The location of this directory depends on your operating system:
Expand All @@ -93,6 +109,19 @@ Assuming you have previously used a default LocalNet, the path `./algokit/sandbo

It is important to note that only the configuration files for a named LocalNet instance should be changed. Any changes made to the default LocalNet instance will be reverted by AlgoKit.

You can use `--name` flag along with `--config-dir` option to specify a custom path for the LocalNet configuration directory. This allows you to manage multiple LocalNet instances with different configurations in different directories on your system.

### Controlling Algod Developer Mode

By default, AlgoKit LocalNet starts algod in developer mode. This mode enables certain features that are useful for development but may not reflect the behavior of a production network. You can control this setting using the `--dev/--no-dev` flag when starting the LocalNet:

```bash
algokit localnet start --no-dev # Starts algod without developer mode
algokit localnet start --dev # Starts algod with developer mode (default)
```

If you change this setting for an existing LocalNet instance, AlgoKit will prompt you to restart the LocalNet to apply the changes.

### Stopping and Resetting the LocalNet

To stop the LocalNet you can execute `algokit localnet stop`. This will turn off the containers, but keep them ready to be started again in the same state by executing `algokit localnet start`.
Expand Down Expand Up @@ -158,5 +187,6 @@ Running an interactive session ensures that you have control over the lifecycle
- `--force`, `-f`: Force deletes stale codespaces and skips confirmation prompts. Defaults to explicitly prompting for confirmation.

For more details about managing LocalNet in GitHub Codespaces, please refer to the [AlgoKit CLI reference documentation](../cli/index.md#codespace).
U
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
U
U


> Tip: By specifying alternative port values it is possible to have several LocalNet instances running where one is using default ports via `algokit localnet start` with Docker | Podman and the other relies on port forwarding via `algokit localnet codespace`.
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ exclude = [
"node_modules",
"venv",
"docs/sphinx",
"src/algokit/core/_vendor/*",
"src/**/core/_vendor",
]
# Allow unused variables when underscore-prefixed.
lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
Expand All @@ -165,7 +165,7 @@ markers = [
addopts = "-m 'not pyinstaller_binary_tests'" # by default, exclude pyinstaller_binary_tests
[tool.mypy]
files = ["src", "tests"]
exclude = ["dist", "src/algokit/core/_vendor/*"]
exclude = ["dist", "*/_vendor/*"]
python_version = "3.10"
warn_unused_ignores = true
warn_redundant_casts = true
Expand Down
61 changes: 55 additions & 6 deletions src/algokit/cli/localnet.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import logging
import os
from pathlib import Path

import click
import questionary
Expand All @@ -7,6 +9,7 @@
from algokit.cli.explore import explore_command
from algokit.cli.goal import goal_command
from algokit.core import proc
from algokit.core.conf import get_app_config_dir
from algokit.core.config_commands.container_engine import get_container_engine, save_container_engine
from algokit.core.sandbox import (
COMPOSE_VERSION_COMMAND,
Expand Down Expand Up @@ -124,9 +127,35 @@ def config_command(*, engine: str | None, force: bool) -> None:
default=None,
help="Specify a name for a custom LocalNet instance. "
"AlgoKit will not manage the configuration of named LocalNet instances, "
"allowing developers to configure it in any way they need.",
f"allowing developers to configure it in any way they need. Defaults to '{SANDBOX_BASE_NAME}'.",
)
def start_localnet(name: str | None) -> None:
@click.option(
aorumbayev marked this conversation as resolved.
Show resolved Hide resolved
"--config-dir",
"-P",
"config_path",
type=click.Path(exists=True, readable=True, file_okay=False, resolve_path=True, path_type=Path),
default=lambda: os.environ.get("ALGOKIT_LOCALNET_CONFIG_DIR", None),
required=False,
help=f"Specify the custom localnet configuration directory. Defaults to '{get_app_config_dir()}'.",
)
@click.option(
"--dev/--no-dev",
"-d",
"algod_dev_mode",
is_flag=True,
required=False,
default=True,
type=click.BOOL,
help=("Control whether to launch 'algod' in developer mode or not. Defaults to 'yes'."),
)
@click.option(
"force",
"--force",
is_flag=True,
default=False,
help="Ignore the prompt to stop the LocalNet if it's already running.",
)
def start_localnet(*, name: str | None, config_path: Path | None, algod_dev_mode: bool, force: bool) -> None:
sandbox = ComposeSandbox.from_environment()
full_name = f"{SANDBOX_BASE_NAME}_{name}" if name is not None else SANDBOX_BASE_NAME
if sandbox is not None and full_name != sandbox.name:
Expand All @@ -135,7 +164,7 @@ def start_localnet(name: str | None) -> None:
sandbox.stop()
else:
raise click.ClickException("LocalNet is already running. Please stop it first")
sandbox = ComposeSandbox() if name is None else ComposeSandbox(name)
sandbox = ComposeSandbox(SANDBOX_BASE_NAME, config_path) if name is None else ComposeSandbox(name, config_path)
compose_file_status = sandbox.compose_file_status()
sandbox.check_docker_compose_for_new_image_versions()
if compose_file_status is ComposeFileStatus.MISSING:
Expand All @@ -156,7 +185,18 @@ def start_localnet(name: str | None) -> None:
"A named LocalNet is running, update checks are disabled. If you wish to synchronize with the latest "
"version, run `algokit localnet reset --update`"
)
sandbox.up()
if sandbox.is_algod_dev_mode() != algod_dev_mode:
sandbox.set_algod_dev_mode(dev_mode=algod_dev_mode)
logger.info(f"Refreshed 'DevMode' flag to '{algod_dev_mode}'")
if not force and click.confirm(
f"Would you like to restart 'LocalNet' to apply 'DevMode' flag set to '{algod_dev_mode}'? "
"Otherwise, the next `algokit localnet reset` will restart with the new flag",
default=True,
):
sandbox.down()
sandbox.up()
else:
sandbox.up()


@localnet_group.command("stop", short_help="Stop the AlgoKit LocalNet.")
Expand All @@ -176,10 +216,19 @@ def stop_localnet() -> None:
default=False,
help="Enable or disable updating to the latest available LocalNet version, default: don't update",
)
def reset_localnet(*, update: bool) -> None:
@click.option(
"--config-dir",
"-P",
"config_path",
type=click.Path(exists=True, readable=True, file_okay=False, resolve_path=True, path_type=Path),
default=lambda: os.environ.get("ALGOKIT_LOCALNET_CONFIG_DIR", None),
required=False,
help="Specify the custom localnet configuration directory.",
)
def reset_localnet(*, update: bool, config_path: Path | None) -> None:
sandbox = ComposeSandbox.from_environment()
if sandbox is None:
sandbox = ComposeSandbox()
sandbox = ComposeSandbox(config_path=config_path)
compose_file_status = sandbox.compose_file_status()
if compose_file_status is ComposeFileStatus.MISSING:
logger.debug("Existing LocalNet not found; creating from scratch...")
Expand Down
27 changes: 24 additions & 3 deletions src/algokit/core/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ def get_min_compose_version() -> str:


class ComposeSandbox:
aorumbayev marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, name: str = SANDBOX_BASE_NAME) -> None:
def __init__(self, name: str = SANDBOX_BASE_NAME, config_path: Path | None = None) -> None:
self.name = SANDBOX_BASE_NAME if name == SANDBOX_BASE_NAME else f"{SANDBOX_BASE_NAME}_{name}"
self.directory = get_app_config_dir() / self.name
self.directory = (config_path or get_app_config_dir()) / self.name
if not self.directory.exists():
logger.debug(f"The {self.name} directory does not exist yet; creating it")
self.directory.mkdir()
Expand Down Expand Up @@ -128,6 +128,17 @@ def _create_instance_from_data(cls, data: list[dict[str, Any]]) -> ComposeSandbo
return cls(name)
return None

def set_algod_dev_mode(self, *, dev_mode: bool) -> None:
content = self.algod_network_template_file_path.read_text()
new_value = "true" if dev_mode else "false"
new_content = re.sub(r'"DevMode":\s*(true|false)', f'"DevMode": {new_value}', content)
self.algod_network_template_file_path.write_text(new_content)

def is_algod_dev_mode(self) -> bool:
content = self.algod_network_template_file_path.read_text()
search = re.search(r'"DevMode":\s*(true|false)', content)
return search is not None and search.group(1) == "true"

def compose_file_status(self) -> ComposeFileStatus:
try:
compose_content = self.compose_file_path.read_text()
Expand All @@ -142,10 +153,20 @@ def compose_file_status(self) -> ComposeFileStatus:
return ComposeFileStatus.OUT_OF_DATE
return ComposeFileStatus.MISSING
else:
algod_network_json_content = json.loads(
algod_network_template_content.replace("NUM_ROUNDS", '"NUM_ROUNDS"')
)
latest_algod_network_json_content = json.loads(
self._latest_algod_network_template.replace("NUM_ROUNDS", '"NUM_ROUNDS"')
)

del algod_network_json_content["Genesis"]["DevMode"]
del latest_algod_network_json_content["Genesis"]["DevMode"]

if (
compose_content == self._latest_yaml
and config_content == self._latest_config_json
and algod_network_template_content == self._latest_algod_network_template
and algod_network_json_content == latest_algod_network_json_content
and proxy_config_content == self._latest_proxy_config
):
return ComposeFileStatus.UP_TO_DATE
Expand Down
1 change: 1 addition & 0 deletions tests/localnet/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def _health_success(httpx_mock: HTTPXMock) -> None:
@pytest.fixture()
def _localnet_up_to_date(proc_mock: ProcMock, httpx_mock: HTTPXMock) -> None:
arg = "{{range .RepoDigests}}{{println .}}{{end}}"

proc_mock.set_output(
["docker", "image", "inspect", ALGORAND_IMAGE, "--format", arg],
["tag@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n"],
Expand Down
Loading
Loading