Skip to content

Commit

Permalink
feat: Add support for a customisable named localnet (#373)
Browse files Browse the repository at this point in the history
* feat: add name option to localnet start, allowing for a customisable LocalNet configuration
* docs: updating docs
  • Loading branch information
negar-abbasi authored Dec 28, 2023
1 parent 4438fa4 commit 41c4946
Show file tree
Hide file tree
Showing 60 changed files with 653 additions and 184 deletions.
5 changes: 2 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
// General - see also /.editorconfig
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
// Don't want to use isort because it conflicts with Ruff - see run on save below
"source.organizeImports": true,
"source.fixAll": true
"source.organizeImports": "explicit",
"source.fixAll": "explicit"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.exclude": {
Expand Down
36 changes: 22 additions & 14 deletions docs/cli/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,19 @@
- [Options](#options-16)
- [--update, --no-update](#--update---no-update)
- [start](#start)
- [Options](#options-17)
- [-n, --name ](#-n---name--1)
- [status](#status)
- [stop](#stop)
- [task](#task)
- [ipfs](#ipfs)
- [Options](#options-17)
- [Options](#options-18)
- [-f, --file ](#-f---file--1)
- [-n, --name ](#-n---name--1)
- [-n, --name ](#-n---name--2)
- [mint](#mint)
- [Options](#options-18)
- [Options](#options-19)
- [--creator ](#--creator-)
- [-n, --name ](#-n---name--2)
- [-n, --name ](#-n---name--3)
- [-u, --unit ](#-u---unit-)
- [-t, --total ](#-t---total-)
- [-d, --decimals ](#-d---decimals-)
Expand All @@ -117,45 +119,45 @@
- [--nft, --ft](#--nft---ft)
- [-n, --network ](#-n---network-)
- [nfd-lookup](#nfd-lookup)
- [Options](#options-19)
- [Options](#options-20)
- [-o, --output ](#-o---output--2)
- [Arguments](#arguments-5)
- [VALUE](#value)
- [opt-in](#opt-in)
- [Options](#options-20)
- [Options](#options-21)
- [-a, --account ](#-a---account-)
- [-n, --network ](#-n---network--1)
- [Arguments](#arguments-6)
- [ASSET_IDS](#asset_ids)
- [opt-out](#opt-out)
- [Options](#options-21)
- [Options](#options-22)
- [-a, --account ](#-a---account--1)
- [--all](#--all)
- [-n, --network ](#-n---network--2)
- [Arguments](#arguments-7)
- [ASSET_IDS](#asset_ids-1)
- [send](#send)
- [Options](#options-22)
- [Options](#options-23)
- [-f, --file ](#-f---file--2)
- [-t, --transaction ](#-t---transaction-)
- [-n, --network ](#-n---network--3)
- [sign](#sign)
- [Options](#options-23)
- [Options](#options-24)
- [-a, --account ](#-a---account--2)
- [-f, --file ](#-f---file--3)
- [-t, --transaction ](#-t---transaction--1)
- [-o, --output ](#-o---output--3)
- [--force](#--force-1)
- [transfer](#transfer)
- [Options](#options-24)
- [Options](#options-25)
- [-s, --sender ](#-s---sender-)
- [-r, --receiver ](#-r---receiver--1)
- [--asset, --id ](#--asset---id-)
- [-a, --amount ](#-a---amount--1)
- [--whole-units](#--whole-units-2)
- [-n, --network ](#-n---network--4)
- [vanity-address](#vanity-address)
- [Options](#options-25)
- [Options](#options-26)
- [-m, --match ](#-m---match-)
- [-o, --output ](#-o---output--4)
- [-a, --alias ](#-a---alias-)
Expand All @@ -164,19 +166,19 @@
- [Arguments](#arguments-8)
- [KEYWORD](#keyword)
- [wallet](#wallet)
- [Options](#options-26)
- [Options](#options-27)
- [-a, --address ](#-a---address-)
- [-m, --mnemonic](#-m---mnemonic)
- [-f, --force](#-f---force-1)
- [Arguments](#arguments-9)
- [ALIAS_NAME](#alias_name)
- [Arguments](#arguments-10)
- [ALIAS](#alias)
- [Options](#options-27)
- [Options](#options-28)
- [-f, --force](#-f---force-2)
- [Arguments](#arguments-11)
- [ALIAS](#alias-1)
- [Options](#options-28)
- [Options](#options-29)
- [-f, --force](#-f---force-3)

# algokit
Expand Down Expand Up @@ -703,6 +705,12 @@ Start the AlgoKit LocalNet.
algokit localnet start [OPTIONS]
```

### 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.

### status

Check the status of the AlgoKit LocalNet.
Expand Down
23 changes: 16 additions & 7 deletions docs/features/localnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ We publish DockerHub images for `arm64` and `amd64`, which means that AlgoKit Lo

## Functionality

### Creating / starting the LocalNet
### Creating / Starting the LocalNet

To create / start your AlgoKit LocalNet instance you can run `algokit localnet start`. This will:

Expand All @@ -46,15 +46,24 @@ Once they have downloaded, it won't try and re-download images unless you perfor
Once the LocalNet has started, the following endpoints will be available:

- [algod](https://developer.algorand.org/docs/rest-apis/algod/v2/):
- address: http://localhost:4001
- address: <http://localhost:4001>
- token: `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`
- [kmd](https://developer.algorand.org/docs/rest-apis/kmd/):
- address: http://localhost:4002
- address: <http://localhost:4002>
- token: `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`
- [indexer](https://developer.algorand.org/docs/rest-apis/indexer/):
- address: http://localhost:8980
- address: <http://localhost:8980>
- tealdbg port:
- address: http://localhost:9392
- address: <http://localhost:9392>

### Creating / Starting a Named LocalNet

AlgoKit manages the default LocalNet environment and automatically keeps the configuration updated with any upstream changes. As a result, configuration changes are reset automatically by AlgoKit, so that developers always have access to a known good LocalNet configuration. This works well for the majority of scenarios, however sometimes developers need the control to make specific configuration changes for specific scenarios.

When you want more control, named LocalNet instances can be used by running `algokit localnet start --name {name}`. This command will set up and run a named LocalNet environment (based off the default), however AlgoKit will not update the environment or configuration automatically. From here developers are able to modify their named environment in any way they like, for example setting `DevMode: false` in `algod_network_template.json`.

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`.

### Stopping and Resetting the LocalNet

Expand Down Expand Up @@ -95,7 +104,7 @@ Needing to do this manual step every time you spin up a new development environm

AlgoKit Utils provides methods to help you do this:

* TypeScript - [`ensureFunded`](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/docs/capabilities/transfer.md#ensurefunded) and [`getDispenserAccount`](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/docs/capabilities/transfer.md#dispenser)
* Python - [`ensure_funded`](https://algorandfoundation.github.io/algokit-utils-py/html/apidocs/algokit_utils/algokit_utils.html#algokit_utils.ensure_funded) and [`get_dispenser_account`](https://algorandfoundation.github.io/algokit-utils-py/html/apidocs/algokit_utils/algokit_utils.html#algokit_utils.get_dispenser_account)
- TypeScript - [`ensureFunded`](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/docs/capabilities/transfer.md#ensurefunded) and [`getDispenserAccount`](https://github.com/algorandfoundation/algokit-utils-ts/blob/main/docs/capabilities/transfer.md#dispenser)
- Python - [`ensure_funded`](https://algorandfoundation.github.io/algokit-utils-py/html/apidocs/algokit_utils/algokit_utils.html#algokit_utils.ensure_funded) and [`get_dispenser_account`](https://algorandfoundation.github.io/algokit-utils-py/html/apidocs/algokit_utils/algokit_utils.html#algokit_utils.get_dispenser_account)

For more details about the `AlgoKit localnet` command, please refer to the [AlgoKit CLI reference documentation](../cli/index.md#localnet).
21 changes: 14 additions & 7 deletions src/algokit/cli/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
post_process,
preprocess_command_args,
)
from algokit.core.sandbox import ComposeFileStatus, ComposeSandbox
from algokit.core.sandbox import DEFAULT_NAME, ComposeFileStatus, ComposeSandbox

logger = logging.getLogger(__name__)

Expand All @@ -34,8 +34,6 @@ def goal_command(*, console: bool, goal_args: list[str]) -> None:
Look at https://developer.algorand.org/docs/clis/goal/goal/ for more information.
"""
volume_mount_path_local = get_volume_mount_path_local()
volume_mount_path_docker = get_volume_mount_path_docker()
goal_args = list(goal_args)
try:
proc.run(["docker", "version"], bad_return_code_error_message="Docker engine isn't running; please start it.")
Expand All @@ -47,18 +45,27 @@ def goal_command(*, console: bool, goal_args: list[str]) -> None:
"See https://docs.docker.com/get-docker/ for more information."
) from ex

sandbox = ComposeSandbox()
sandbox = ComposeSandbox.from_environment()
if sandbox is None:
sandbox = ComposeSandbox()

if sandbox.name != DEFAULT_NAME:
logger.info("A named LocalNet is running, goal command will be executed against the named LocalNet")

volume_mount_path_local = get_volume_mount_path_local(directory_name=sandbox.name)
volume_mount_path_docker = get_volume_mount_path_docker()

compose_file_status = sandbox.compose_file_status()
if compose_file_status is not ComposeFileStatus.UP_TO_DATE:
if compose_file_status is not ComposeFileStatus.UP_TO_DATE and sandbox.name == DEFAULT_NAME:
raise click.ClickException("LocalNet definition is out of date; please run `algokit localnet reset` first!")

if console:
if goal_args:
logger.warning("--console opens an interactive shell, remaining arguments are being ignored")
logger.info("Opening Bash console on the algod node; execute `exit` to return to original console")
result = proc.run_interactive("docker exec -it -w /root algokit_algod bash".split())
result = proc.run_interactive(f"docker exec -it -w /root algokit_{sandbox.name}_algod bash".split())
else:
cmd = "docker exec --interactive --workdir /root algokit_algod goal".split()
cmd = f"docker exec --interactive --workdir /root algokit_{sandbox.name}_algod goal".split()
input_files, output_files, goal_args = preprocess_command_args(
goal_args, volume_mount_path_local, volume_mount_path_docker
)
Expand Down
76 changes: 60 additions & 16 deletions src/algokit/cli/localnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from algokit.cli.goal import goal_command
from algokit.core import proc
from algokit.core.sandbox import (
DEFAULT_NAME,
DOCKER_COMPOSE_MINIMUM_VERSION,
DOCKER_COMPOSE_VERSION_COMMAND,
ComposeFileStatus,
Expand Down Expand Up @@ -57,31 +58,57 @@ def localnet_group() -> None:


@localnet_group.command("start", short_help="Start the AlgoKit LocalNet.")
def start_localnet() -> None:
sandbox = ComposeSandbox()
@click.option(
"name",
"--name",
"-n",
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.",
)
def start_localnet(name: str | None) -> None:
sandbox = ComposeSandbox.from_environment()
full_name = f"{DEFAULT_NAME}_{name}" if name is not None else DEFAULT_NAME
if sandbox is not None and full_name != sandbox.name:
logger.debug("LocalNet is already running.")
if click.confirm("This will stop any running AlgoKit LocalNet instance. Are you sure?", default=True):
sandbox.stop()
else:
raise click.ClickException("LocalNet is already running. Please stop it first")
sandbox = ComposeSandbox() if name is None else ComposeSandbox(name)
compose_file_status = sandbox.compose_file_status()
sandbox.check_docker_compose_for_new_image_versions()

if compose_file_status is ComposeFileStatus.MISSING:
logger.debug("LocalNet compose file does not exist yet; writing it out for the first time")
sandbox.write_compose_file()
if name is not None:
logger.info(
f"The named LocalNet configuration has been created in {sandbox.directory}. \n"
f"You can edit the configuration by changing those files. "
f"Running `algokit localnet reset` will ensure the configuration is applied"
)
elif compose_file_status is ComposeFileStatus.UP_TO_DATE:
logger.debug("LocalNet compose file does not require updating")
else:
logger.warning("LocalNet definition is out of date; please run algokit localnet reset")
elif compose_file_status is ComposeFileStatus.OUT_OF_DATE and name is None:
logger.warning("LocalNet definition is out of date; please run `algokit localnet reset`")
if name is not None:
logger.info(
"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()


@localnet_group.command("stop", short_help="Stop the AlgoKit LocalNet.")
def stop_localnet() -> None:
sandbox = ComposeSandbox()
compose_file_status = sandbox.compose_file_status()
if compose_file_status is ComposeFileStatus.MISSING:
logger.debug(
"LocalNet compose file does not exist yet; run `algokit localnet start` to start the AlgoKit LocalNet"
)
sandbox = ComposeSandbox.from_environment()
if sandbox is not None:
compose_file_status = sandbox.compose_file_status()
if compose_file_status is not ComposeFileStatus.MISSING:
sandbox.stop()
else:
sandbox.stop()
logger.debug("LocalNet is not running; run `algokit localnet start` to start the AlgoKit LocalNet")


@localnet_group.command("reset", short_help="Reset the AlgoKit LocalNet.")
Expand All @@ -91,12 +118,14 @@ def stop_localnet() -> None:
help="Enable or disable updating to the latest available LocalNet version, default: don't update",
)
def reset_localnet(*, update: bool) -> None:
sandbox = ComposeSandbox()
sandbox = ComposeSandbox.from_environment()
if sandbox is None:
sandbox = ComposeSandbox()
compose_file_status = sandbox.compose_file_status()
if compose_file_status is ComposeFileStatus.MISSING:
logger.debug("Existing LocalNet not found; creating from scratch...")
sandbox.write_compose_file()
else:
elif sandbox.name == DEFAULT_NAME:
sandbox.down()
if compose_file_status is not ComposeFileStatus.UP_TO_DATE:
logger.info("LocalNet definition is out of date; updating it to latest")
Expand All @@ -105,7 +134,20 @@ def reset_localnet(*, update: bool) -> None:
sandbox.pull()
else:
sandbox.check_docker_compose_for_new_image_versions()

elif update:
if click.confirm(
f"A named LocalNet is running, are you sure you want to reset the LocalNet configuration "
f"in {sandbox.directory}?\nThis will stop the running LocalNet and overwrite any changes "
"you've made to the configuration",
default=True,
):
sandbox.down()
sandbox.write_compose_file()
sandbox.pull()
else:
raise click.ClickException("LocalNet configuration has not been reset")
else:
sandbox.down()
sandbox.up()


Expand All @@ -114,7 +156,9 @@ def reset_localnet(*, update: bool) -> None:

@localnet_group.command("status", short_help="Check the status of the AlgoKit LocalNet.")
def localnet_status() -> None:
sandbox = ComposeSandbox()
sandbox = ComposeSandbox.from_environment()
if sandbox is None:
sandbox = ComposeSandbox()
ps = sandbox.ps()
ps_by_name = {stats["Service"]: stats for stats in ps}
# if any of the required containers does not exist (ie it's not just stopped but hasn't even been created),
Expand Down
4 changes: 2 additions & 2 deletions src/algokit/core/goal.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ def get_volume_mount_path_docker() -> Path:
return Path("/root/goal_mount/")


def get_volume_mount_path_local() -> Path:
return get_app_config_dir().joinpath("sandbox", "goal_mount")
def get_volume_mount_path_local(directory_name: str) -> Path:
return get_app_config_dir().joinpath(directory_name, "goal_mount")


filename_pattern = re.compile(r"^[\w-]+\.\w+$")
Expand Down
Loading

1 comment on commit 41c4946

@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.py108298%83, 98
   deploy.py72790%44, 46, 92–94, 158, 182
   dispenser.py118199%75
   doctor.py48394%142–144
   explore.py501276%34–39, 41–46
   generate.py67396%74–75, 140
   goal.py43198%64
   init.py1901692%277–278, 328, 331–333, 344, 388, 414, 454, 463–465, 468–473, 486
   localnet.py1191587%74–78, 111, 123, 138–148, 161, 206, 227–228
src/algokit/cli/common
   utils.py26292%120, 123
src/algokit/cli/tasks
   assets.py821384%65–66, 72, 74–75, 105, 119, 125–126, 132, 134, 136–137
   ipfs.py51884%52, 80, 92, 94–95, 105–107
   mint.py66494%48, 70, 91, 250
   send_transaction.py651085%52–53, 57, 89, 158, 170–174
   sign_transaction.py59886%21, 28–30, 71–72, 109, 123
   transfer.py37392%25, 89, 116
   utils.py994555%26–34, 40–43, 75–76, 100–101, 125–133, 152–162, 209, 258–259, 279–290, 297–299
   vanity_address.py561082%41, 45–48, 112, 114, 121–123
   wallet.py79495%21, 66, 136, 162
src/algokit/core
   bootstrap.py1612485%103–104, 126, 149, 214, 217, 223–237, 246–251
   conf.py54885%10, 24, 28, 36, 38, 71–73
   deploy.py691184%61–64, 73–75, 79, 84, 91–93
   dispenser.py2022687%91, 123–124, 141–149, 191–192, 198–200, 218–219, 259–260, 318, 332–334, 345–346, 356, 369, 384
   doctor.py65789%67–69, 92–94, 134
   generate.py41295%69, 87
   goal.py60395%30–31, 41
   log_handlers.py68790%50–51, 63, 112–116, 125
   proc.py45198%98
   sandbox.py2181892%62, 73–75, 96, 142–149, 160, 456, 472, 497, 505
   typed_client_generation.py80594%55–57, 70, 75
   utils.py56296%36–37
   version_prompt.py72889%26–27, 39, 58–61, 79, 108
src/algokit/core/tasks
   ipfs.py63789%58–64, 140, 144, 146, 152
   nfd.py491373%25, 31, 34–41, 70–72, 99–101
   vanity_address.py903462%49–50, 54, 59–75, 92–108, 128–131
   wallet.py71593%37, 129, 155–157
src/algokit/core/tasks/mint
   mint.py781087%123–133, 187
   models.py901188%50, 52, 57, 71–74, 85–88
TOTAL327837888% 

Tests Skipped Failures Errors Time
386 0 💤 0 ❌ 0 🔥 17.356s ⏱️

Please sign in to comment.