Skip to content
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
10 changes: 9 additions & 1 deletion .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,18 @@ jobs:
with:
python-version: '3.8'

- name: Cache pip dependencies
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install .[test]
pip install black==24.8.0 isort==5.13.2

- name: Run Black
run: black --check .
Expand Down
40 changes: 31 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ on:
branches:
- main


permissions:
contents: read

Expand All @@ -14,20 +13,43 @@ jobs:
runs-on: ubuntu-${{ matrix.ubuntu-version }}
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
ubuntu-version: ["20.04", "22.04"]
python-version: ["3.9", "3.10", "3.11", "3.12"]
ubuntu-version: ["22.04"]
fail-fast: false
steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: pyproject.toml

- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: latest
virtualenvs-create: true
virtualenvs-in-project: true

- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v3
with:
path: .venv
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
restore-keys: |
venv-${{ runner.os }}-${{ matrix.python-version }}-

- name: Install dependencies
run: |
pip install '.[test]'
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --with dev

- name: Save cached venv
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
uses: actions/cache@v3
with:
path: .venv
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}

- name: Run tests
run: |
pytest
run: poetry run pytest
26 changes: 25 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
repos:
- repo: https://github.com/psf/black
rev: 23.1.0 # Use the latest version
rev: 24.8.0 # Use the latest version
hooks:
- id: black
language_version: python3
types: [python]

- repo: https://github.com/pycqa/isort
rev: 5.13.2
hooks:
- id: isort
language_version: python3
types: [python]

# - repo: https://github.com/pycqa/flake8
# rev: 7.0.0
# hooks:
# - id: flake8
# language_version: python3
# types: [python]

# - repo: https://github.com/pre-commit/mirrors-mypy
# rev: v1.8.0
# hooks:
# - id: mypy
# language_version: python3
# types: [python]
# additional_dependencies: [types-all]
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ pip install arm-cli

Once installed, setup the CLI initially by running `arm-cli system setup`. You may need to rerun if you update the CLI via pip. This will do things like configure system settings to enable tab complete.

**Note**: If you installed the CLI with `pip install --user`, you may need to manually run the local bin version the first time:
```bash
~/.local/bin/arm-cli system setup
```

## Usage
### Initial Setup
For help, run:
Expand All @@ -30,15 +35,15 @@ python -m arm_cli --help
```
## Development

To contribute to this tool, first checkout the code. Then create a new virtual environment:
To contribute to this tool, first checkout the code. Then create a new virtual environment. From the root of the repo:
```bash
cd arm-cli
python -m venv venv
source venv/bin/activate
```
Now install the dependencies and test dependencies:
```bash
pip install -e '.[test]'
pip install -e '.[dev]'
```
To run the tests:
```bash
Expand Down
16 changes: 15 additions & 1 deletion arm_cli/self/self.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,19 @@ def self():
help="Install from a local source path",
show_default=True,
)
def update(source):
@click.option("-f", "--force", is_flag=True, help="Skip confirmation prompts")
def update(source, force):
"""Update arm-cli from PyPI or source"""
if source:
print(f"Installing arm-cli from source at {source}...")

if not force:
if not click.confirm(
"Do you want to install arm-cli from source? This will clear pip cache."
):
print("Update cancelled.")
return

# Clear Python import cache
print("Clearing Python caches...")
subprocess.run(["rm", "-rf", os.path.expanduser("~/.cache/pip")])
Expand All @@ -34,5 +42,11 @@ def update(source):
print(f"arm-cli installed from source at {source} successfully!")
else:
print("Updating arm-cli from PyPI...")

if not force:
if not click.confirm("Do you want to update arm-cli from PyPI?"):
print("Update cancelled.")
return

subprocess.run([sys.executable, "-m", "pip", "install", "--upgrade", "arm-cli"], check=True)
print("arm-cli updated successfully!")
131 changes: 129 additions & 2 deletions arm_cli/system/setup_utils.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,148 @@
import os
import stat
import subprocess
import sys

import click

from arm_cli.system.shell_scripts import detect_shell, get_current_shell_addins


def setup_xhost():
def check_xhost_setup():
"""Check if xhost is already configured for Docker"""
try:
result = subprocess.run(["xhost"], capture_output=True, text=True, check=True)
return "LOCAL:docker" in result.stdout
except subprocess.CalledProcessError:
return False


def setup_xhost(force=False):
"""Setup xhost for GUI applications"""
try:
# Check if xhost is already configured
if check_xhost_setup():
print("X11 access for Docker containers is already configured.")
return

# Ensure xhost allows local Docker connections
print("Setting up X11 access for Docker containers...")
if not force:
if not click.confirm("Do you want to configure X11 access for Docker containers?"):
print("X11 setup cancelled.")
return

subprocess.run(["xhost", "+local:docker"], check=True)
print("xhost configured successfully.")
except subprocess.CalledProcessError as e:
print(f"Error configuring xhost: {e}")


def check_sudo_privileges():
"""Check if the user has sudo privileges"""
try:
subprocess.run(["sudo", "-n", "true"], check=True, capture_output=True)
return True
except subprocess.CalledProcessError:
return False


def check_data_directories_setup():
"""Check if data directories are already properly set up"""
data_dirs = ["/DATA/influxdb2", "/DATA/images", "/DATA/node_exporter"]
current_uid = os.getuid()
current_gid = os.getgid()

for directory in data_dirs:
# Check if directory exists
if not os.path.exists(directory):
return False

# Check ownership
try:
stat_info = os.stat(directory)
if stat_info.st_uid != current_uid or stat_info.st_gid != current_gid:
return False

# Check permissions (should be 775)
mode = stat_info.st_mode
if not (mode & stat.S_IRWXU and mode & stat.S_IRWXG and not mode & stat.S_IWOTH):
return False

except (OSError, PermissionError):
return False

return True


def setup_data_directories(force=False):
"""Setup data directories for the ARM system"""
try:
# Check if directories are already properly set up
if check_data_directories_setup():
print("Data directories are already properly set up.")
return True

print("Setting up data directories...")

# Check if user has sudo privileges
if not check_sudo_privileges():
print("This operation requires sudo privileges.")
print("Please run: sudo arm-cli system setup")
return False

# Ask user for confirmation
print("This will create the following directories:")
data_dirs = ["/DATA/influxdb2", "/DATA/images", "/DATA/node_exporter"]
for directory in data_dirs:
print(f" - {directory}")
print("And set appropriate ownership and permissions.")

if not force:
if not click.confirm("Do you want to proceed?"):
print("Setup cancelled.")
return False

# Get current user UID and GID
uid = os.getuid()
gid = os.getgid()

print("Creating directories and setting permissions...")

# Create all directories in one sudo command
mkdir_cmd = ["sudo", "mkdir", "-p"] + data_dirs
subprocess.run(mkdir_cmd, check=True)
print("Created directories.")

# Set ownership for all directories in one sudo command
chown_cmd = ["sudo", "chown", "-R", f"{uid}:{gid}"] + data_dirs
subprocess.run(chown_cmd, check=True)
print("Set ownership.")

# Set permissions for all directories in one sudo command
chmod_cmd = ["sudo", "chmod", "-R", "775"] + data_dirs
subprocess.run(chmod_cmd, check=True)
print("Set permissions.")

print("Data directories setup completed successfully.")
return True

except subprocess.CalledProcessError as e:
print(f"Error setting up data directories: {e}")
print("Please ensure you have sudo privileges.")
return False
except Exception as e:
print(f"Unexpected error during data directory setup: {e}")
return False


def is_line_in_file(line, filepath) -> bool:
"""Checks if a line is already in a file"""
with open(filepath, "r") as f:
return any(line.strip() in l.strip() for l in f)


def setup_shell():
def setup_shell(force=False):
"""Setup shell addins for autocomplete"""
shell = detect_shell()

Expand All @@ -31,7 +151,14 @@ def setup_shell():
line = f"source {get_current_shell_addins()}"
if not is_line_in_file(line, bashrc_path):
print(f'Adding \n"{line}"\nto {bashrc_path}')
if not force:
if not click.confirm("Do you want to add shell autocomplete to ~/.bashrc?"):
print("Shell setup cancelled.")
return

with open(bashrc_path, "a") as f:
f.write(f"\n{line}\n")
else:
print("Shell addins are already configured in ~/.bashrc")
else:
print(f"Unsupported shell: {shell}", file=sys.stderr)
5 changes: 5 additions & 0 deletions arm_cli/system/shell_scripts/shell_addins.fish
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ _ARM_CLI_COMPLETE=fish_source arm-cli | source

# Export for use when launching Docker to match host file ownership
set -x CURRENT_UID (id -u):(id -g)

# Allow Docker containers to access X11 for GUI apps
if type -q xhost
xhost +local:docker > /dev/null 2>&1
end
Loading