-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #90 from algorandfoundation/initial-algokit-doctor
feat(doctor): check system and services
- Loading branch information
Showing
17 changed files
with
802 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,6 +75,8 @@ Here's how to test it out and maybe even start hacking, assuming you have access | |
4. Run `pipx install ./dist/algokit-<TAB>-<TAB>` (ie the .whl file) | ||
5. You can now run `algokit` and should see a help message! 🎉 | ||
|
||
> Recommended: Run `algokit doctor` to check the system is ready to enjoy development on Algorand! | ||
### Update | ||
|
||
To update a previous algokit installation you can simply run `pipx reinstall algokit` and it'll grab the latest from wherever it was installed from. Note: If you installed a specific version e.g. `pipx install git+https://github.com/algorandfoundation/[email protected]` then this command won't have any effect since that repository tag will point to the same version. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import logging | ||
import platform | ||
|
||
import click | ||
import pyclip # type: ignore | ||
from algokit.core import doctor as doctor_functions | ||
from algokit.core.doctor import ProcessResult | ||
|
||
logger = logging.getLogger(__name__) | ||
DOCTOR_END_MESSAGE = ( | ||
"If you are experiencing a problem with algokit, feel free to submit an issue " | ||
"via https://github.com/algorandfoundation/algokit-cli/issues/new; please include this output, " | ||
"if you want to populate this message in your clipboard, run `algokit doctor -c`" | ||
) | ||
|
||
|
||
@click.command( | ||
"doctor", | ||
short_help="Run the Algorand doctor CLI.", | ||
context_settings={ | ||
"ignore_unknown_options": True, | ||
}, | ||
) | ||
@click.option( | ||
"--copy-to-clipboard", | ||
"-c", | ||
help="Copy the contents of the doctor message (in Markdown format) in your clipboard.", | ||
is_flag=True, | ||
default=False, | ||
) | ||
def doctor_command(*, copy_to_clipboard: bool) -> None: | ||
return_code = 0 | ||
os_type = platform.system().lower() | ||
service_outputs: dict[str, ProcessResult] = {} | ||
|
||
service_outputs["Time"] = doctor_functions.get_date() | ||
service_outputs["AlgoKit"] = doctor_functions.get_algokit_info() | ||
if os_type == "windows": | ||
service_outputs["Chocolatey"] = doctor_functions.get_choco_info() | ||
if os_type == "darwin": | ||
service_outputs["Brew"] = doctor_functions.get_brew_info() | ||
service_outputs["OS"] = doctor_functions.get_os(os_type) | ||
service_outputs["Docker"] = doctor_functions.get_docker_info() | ||
service_outputs["Docker Compose"] = doctor_functions.get_docker_compose_info() | ||
service_outputs["Git"] = doctor_functions.get_git_info(os_type) | ||
service_outputs["AlgoKit Python"] = doctor_functions.get_algokit_python_info() | ||
service_outputs["Global Python"] = doctor_functions.get_global_python_info() | ||
service_outputs["Pipx"] = doctor_functions.get_pipx_info() | ||
service_outputs["Poetry"] = doctor_functions.get_poetry_info() | ||
service_outputs["Node.js"] = doctor_functions.get_node_info() | ||
service_outputs["Npm"] = doctor_functions.get_npm_info() | ||
|
||
critical_services = ["Docker", "Docker Compose", "Git"] | ||
# Print the status details | ||
for key, value in service_outputs.items(): | ||
color = "green" | ||
if value.exit_code != 0: | ||
color = "red" if key in critical_services else "yellow" | ||
return_code = 1 | ||
logger.info(click.style(f"{key}: ", bold=True) + click.style(f"{value.info}", fg=color)) | ||
|
||
# print end message anyway | ||
logger.info(DOCTOR_END_MESSAGE) | ||
|
||
if copy_to_clipboard: | ||
pyclip.copy("\n".join(f"{key}: {value.info}" for key, value in service_outputs.items())) | ||
|
||
if return_code != 0: | ||
raise click.exceptions.Exit(code=1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
import dataclasses | ||
import logging | ||
import platform | ||
import shutil | ||
from datetime import datetime, timezone | ||
from sys import version_info as sys_version_info | ||
|
||
from algokit.core import proc | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
DOCKER_COMPOSE_MINIMUM_VERSION = "2.5" | ||
|
||
DOCKER_COMPOSE_MINIMUM_VERSION_MESSAGE = ( | ||
f"\nDocker Compose {DOCKER_COMPOSE_MINIMUM_VERSION} required to `run algokit sandbox command`; " | ||
"install via https://docs.docker.com/compose/install/" | ||
) | ||
|
||
|
||
@dataclasses.dataclass | ||
class ProcessResult: | ||
info: str | ||
exit_code: int | ||
|
||
|
||
def get_date() -> ProcessResult: | ||
return ProcessResult(format(datetime.now(timezone.utc).isoformat()), 0) | ||
|
||
|
||
def get_algokit_info() -> ProcessResult: | ||
try: | ||
pipx_list_process_results = proc.run(["pipx", "list", "--short"]) | ||
algokit_pipx_line = [ | ||
line for line in pipx_list_process_results.output.splitlines() if line.startswith("algokit") | ||
] | ||
algokit_version = algokit_pipx_line[0].split(" ")[1] | ||
|
||
algokit_location = "" | ||
pipx_env_process_results = proc.run(["pipx", "environment"]) | ||
algokit_pip_line = [ | ||
line for line in pipx_env_process_results.output.splitlines() if line.startswith("PIPX_LOCAL_VENVS") | ||
] | ||
pipx_venv_location = algokit_pip_line[0].split("=")[1] | ||
algokit_location = f"{pipx_venv_location}/algokit" | ||
return ProcessResult(f"{algokit_version} {algokit_location}", 0) | ||
except Exception: | ||
return ProcessResult("None found", 1) | ||
|
||
|
||
def get_choco_info() -> ProcessResult: | ||
try: | ||
process_results = proc.run(["choco"]) | ||
return ProcessResult(process_results.output.splitlines()[0].split(" v")[1], process_results.exit_code) | ||
except Exception: | ||
return ProcessResult("None found", 1) | ||
|
||
|
||
def get_brew_info() -> ProcessResult: | ||
try: | ||
process_results = proc.run(["brew", "-v"]) | ||
return ProcessResult(process_results.output.splitlines()[0].split(" ")[1], process_results.exit_code) | ||
except Exception: | ||
return ProcessResult("None found", 1) | ||
|
||
|
||
def get_os(os_type: str) -> ProcessResult: | ||
os_version = "" | ||
os_name = "" | ||
if os_type == "windows": | ||
os_name = "Windows" | ||
os_version = platform.win32_ver()[0] | ||
elif os_type == "darwin": | ||
os_name = "Mac OS X" | ||
os_version = platform.mac_ver()[0] | ||
else: | ||
os_name = "Unix/Linux" | ||
os_version = platform.version() | ||
return ProcessResult(f"{os_name} {os_version}", 0) | ||
|
||
|
||
def get_docker_info() -> ProcessResult: | ||
try: | ||
process_results = proc.run(["docker", "-v"]) | ||
return ProcessResult( | ||
process_results.output.splitlines()[0].split(" ")[2].split(",")[0], process_results.exit_code | ||
) | ||
except Exception: | ||
return ProcessResult( | ||
( | ||
"None found.\nDocker required to `run algokit sandbox` command;" | ||
" install via https://docs.docker.com/get-docker/" | ||
), | ||
1, | ||
) | ||
|
||
|
||
def get_docker_compose_info() -> ProcessResult: | ||
try: | ||
process_results = proc.run(["docker-compose", "-v"]) | ||
docker_compose_version = process_results.output.splitlines()[0].split(" v")[2] | ||
minimum_version_met = is_minimum_version(docker_compose_version, DOCKER_COMPOSE_MINIMUM_VERSION) | ||
return ProcessResult( | ||
( | ||
docker_compose_version | ||
if minimum_version_met | ||
else f"{docker_compose_version}{DOCKER_COMPOSE_MINIMUM_VERSION_MESSAGE}" | ||
), | ||
process_results.exit_code if minimum_version_met else 1, | ||
) | ||
except Exception: | ||
return ProcessResult(f"None found. {DOCKER_COMPOSE_MINIMUM_VERSION_MESSAGE}", 1) | ||
|
||
|
||
def get_git_info(system: str) -> ProcessResult: | ||
try: | ||
process_results = proc.run(["git", "-v"]) | ||
return ProcessResult(process_results.output.splitlines()[0].split(" ")[2], process_results.exit_code) | ||
except Exception: | ||
if system == "windows": | ||
return ProcessResult( | ||
( | ||
"None found.\nGit required to `run algokit init`; install via `choco install git` " | ||
"if using Chocolatey or via https://github.com/git-guides/install-git#install-git-on-windows" | ||
), | ||
1, | ||
) | ||
else: | ||
return ProcessResult( | ||
"None found.\nGit required to run `algokit init`; " | ||
"install via https://github.com/git-guides/install-git", | ||
1, | ||
) | ||
|
||
|
||
def get_algokit_python_info() -> ProcessResult: | ||
try: | ||
return ProcessResult(f"{sys_version_info.major}.{sys_version_info.minor}.{sys_version_info.micro}", 0) | ||
except Exception: | ||
return ProcessResult("None found.", 1) | ||
|
||
|
||
def get_global_python_info() -> ProcessResult: | ||
try: | ||
global_python3_version = proc.run(["python3", "--version"]).output.splitlines()[0].split(" ")[1] | ||
global_python3_location = shutil.which("python3") | ||
return ProcessResult(f"{global_python3_version} {global_python3_location}", 0) | ||
except Exception: | ||
return ProcessResult("None found.", 1) | ||
|
||
|
||
def get_pipx_info() -> ProcessResult: | ||
try: | ||
process_results = proc.run(["pipx", "--version"]) | ||
return ProcessResult(process_results.output.splitlines()[0], process_results.exit_code) | ||
except Exception: | ||
return ProcessResult( | ||
"None found.\nPipx is required to install Poetry; install via https://pypa.github.io/pipx/", 1 | ||
) | ||
|
||
|
||
def get_poetry_info() -> ProcessResult: | ||
try: | ||
process_results = proc.run(["poetry", "--version"]) | ||
poetry_version = process_results.output.splitlines()[-1].split("version ")[1].split(")")[0] | ||
return ProcessResult(poetry_version, process_results.exit_code) | ||
except Exception: | ||
return ProcessResult( | ||
( | ||
"None found.\nPoetry is required for some Python-based templates; install via `algokit bootstrap` " | ||
"within project directory, or via https://python-poetry.org/docs/#installation" | ||
), | ||
1, | ||
) | ||
|
||
|
||
def get_node_info() -> ProcessResult: | ||
try: | ||
process_results = proc.run(["node", "-v"]) | ||
return ProcessResult(process_results.output.splitlines()[0].split("v")[1], process_results.exit_code) | ||
except Exception: | ||
return ProcessResult( | ||
( | ||
"None found.\nNode.js is required for some Node.js-based templates; install via `algokit bootstrap` " | ||
"within project directory, or via https://nodejs.dev/en/learn/how-to-install-nodejs/" | ||
), | ||
1, | ||
) | ||
|
||
|
||
def get_npm_info() -> ProcessResult: | ||
try: | ||
process_results = proc.run(["npm", "-v"]) | ||
return ProcessResult(process_results.output.splitlines()[0], process_results.exit_code) | ||
except Exception: | ||
return ProcessResult("None found.", 1) | ||
|
||
|
||
def is_minimum_version(system_version: str, minimum_version: str) -> bool: | ||
system_version_as_tuple = tuple(map(int, (system_version.split(".")))) | ||
minimum_version_as_tuple = tuple(map(int, (minimum_version.split(".")))) | ||
return system_version_as_tuple >= minimum_version_as_tuple |
Oops, something went wrong.
b74f594
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Coverage Report