diff --git a/.github/actions/autodeploy/action.yml b/.github/actions/autodeploy/action.yml new file mode 100644 index 0000000..e31f5ac --- /dev/null +++ b/.github/actions/autodeploy/action.yml @@ -0,0 +1,40 @@ +name: Autodeploy Setup +description: Copy the `autodeploy` scripts into the caller repository, install prerequisites and run `setup.sh`. +inputs: + branch: + description: "Branch or ref to check out in the caller repository" + required: false + default: "main" + # install_prereqs: + # description: "Whether to install prerequisites on the runner" + # required: false + # default: "true" + +runs: + using: "composite" + steps: + - name: Checkout caller repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: true + ref: ${{ inputs.branch }} + + - name: Copy autodeploy into workspace + run: | + cp -R "$GITHUB_ACTION_PATH/autodeploy" "$GITHUB_WORKSPACE/" + shell: bash + + # - name: Install prerequisites + # if: ${{ inputs.install_prereqs == 'true' }} + # run: | + # sudo apt-get update + # sudo apt-get install -y curl tar + # shell: bash + + - name: Run autodeploy setup script + working-directory: ${{ github.workspace }}/autodeploy + run: | + chmod +x setup.sh + ./setup.sh + shell: bash diff --git a/README.md b/README.md index e69de29..a17d77d 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,28 @@ +Usage +----- + +Call this composite action from any repository to run the `autodeploy/setup.sh` script included in this repo. + +Example workflow snippet (in the caller repo): + +```yaml +name: call-autodeploy + +on: + workflow_dispatch: + +jobs: + run-autodeploy: + runs-on: ubuntu-latest + steps: + - name: Run autodeploy action from repo + uses: your-username/snek/.github/actions/autodeploy@main + with: + branch: main + install_prereqs: 'true' +``` + +Notes: +- The action will `checkout` the caller repository (so it can `git add`/`git commit` and `git push`). +- If you rely on `GITHUB_TOKEN` permissions for pushing, ensure `permissions.contents` in the caller workflow allows `write`. +- The action copies the `autodeploy` directory from this repo into the caller workspace before running the script. diff --git a/cli/README.md b/cli/README.md deleted file mode 100644 index 36d888a..0000000 --- a/cli/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# 🐍 snek - -## Usage - -```bash -$ ./snek -h -``` -``` -usage: snek [-h] [--root ROOT] [--exe EXE] {envs,root,exe,info,fork,bless} ... - -🐍 - -optional arguments: - -h, --help show this help message and exit - --root ROOT conda root folder / prefix (read from the current env, if not given) - --exe EXE conda or mamba executable (read from the current env, if not given) - -commands: - {envs,root,exe,info,fork,bless} - envs print existing envs - root print root - exe print exe - info print info - fork fork a conda env - bless bless a conda env -``` - diff --git a/cli/snek b/cli/snek deleted file mode 100755 index b3e8438..0000000 --- a/cli/snek +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/env python - -import argparse -import json -import shutil -import subprocess -from collections import defaultdict -from datetime import datetime -from pathlib import Path - - -TS_FMT = "%Y%m%d-%H%M%S" - - -class Conda: - - def __init__(self, root=None, exe=None): - self.exe = exe or shutil.which("mamba") or shutil.which("conda") - if not self.exe: - raise SystemExit("neither conda nor mamba found") - - root = root or self.get_root() - self.root = Path(root) - - - def fork(self, name): - envs = self.get_envs() - if name not in envs: - envs = ", ".join(envs) - msg = f'"{name}" is not a known environment.\nChoose from: {envs}' - raise SystemExit(msg) - - basename = name - if "@" in name: - basename, original_timestamp = name.rsplit("@", 1) - original_timestamp = datetime.strptime(original_timestamp, TS_FMT) - print(f'extracted basename "{basename}" from {name} (original timestamp: {original_timestamp})') - - timestamp = datetime.now().strftime(TS_FMT) - new = f"{basename}@{timestamp}" - self.clone(name, new) - - - def bless(self, name): - if "@" not in name: - msg = f'"{name}" is not a forked environment' - raise SystemExit(msg) - - basename, timestamp = name.rsplit("@", 1) - - try: - timestamp = datetime.strptime(timestamp, TS_FMT) - except ValueError as e: - raise SystemExit(e) - - self.clone(name, basename) - - - def print_envs(self): - envs = self.get_envs() - for i in envs: - print("-", i) - - - def get_envs(self): - cmd = [self.exe, "env", "list", "--json"] - info = run_and_parse(cmd) - envs = info["envs"] - envs = sorted(envs) - - if self.root: - envs_root = self.root / "envs" - envs = [Path(i) for i in envs] - envs = [i.relative_to(envs_root) for i in envs if i.is_relative_to(envs_root)] - envs = [str(i) for i in envs] - - return envs - - - def print_root(self): - print(self.root) - - - def get_root(self): - info = self.get_info() - return info["conda_prefix"] - - - def print_exe(self): - print(self.exe) - - - def print_info(self): - info = self.get_info() - length = maxstrlen(info.keys()) - for k, v in sorted(info.items()): - k = str(k) + ":" - v = str(v) - if len(v) >= 100: - v = "\n" + v + "\n" - print(k.ljust(length), v) - - - def get_info(self): - cmd = [self.exe, "info", "--json"] - info = run_and_parse(cmd) - return info - - - def clone(self, original, new): - # conda create --name theclone --clone theoriginal # uses hardlinks - # conda create --name thecopy --copy theoriginal # copies files - print("clone:", original, "=>", new) - cmd = [self.exe, "create", "--name", new, "--clone", original] - subprocess.run(cmd) - - - def get_list(self, env): - cmd = [self.exe, "list", "--json", "--name", env] - pkgs = run_and_parse(cmd) - res = defaultdict(list) - for p in pkgs: - for k in ("name", "version", "build_string", "build_number", "channel"): - res[k].append(p[k]) - return dict(res) - - - - - -def run_and_parse(cmd): - completed = subprocess.run(cmd, capture_output=True) - - stderr = completed.stderr - if stderr: - msg = stderr.decode().strip() - raise SystemExit(msg) - - stdout = completed.stdout - if stdout: - return json.loads(stdout) - - -def maxstrlen(seq): - return max(len(str(i)) for i in seq) - - - -def handle_clargs(): - parser = argparse.ArgumentParser(description="🐍") - - parser.add_argument("--root", help="conda root folder / prefix (read from the current env, if not given)") - parser.add_argument("--exe", help="conda or mamba executable (read from the current env, if not given)") - - subparsers = parser.add_subparsers(title="commands", dest="command", required=True) - - parser_envs = subparsers.add_parser("envs", help="print existing envs") - parser_root = subparsers.add_parser("root", help="print root") - parser_exe = subparsers.add_parser("exe", help="print exe") - parser_info = subparsers.add_parser("info", help="print info") - - parser_fork = subparsers.add_parser("fork", help="fork a conda env") - parser_bless = subparsers.add_parser("bless", help="bless a conda env") - - msg = "name of the conda env" - parser_fork.add_argument("name", help=msg) - parser_bless.add_argument("name", help=msg) - - clargs = parser.parse_args() - return clargs - - - - - -if __name__ == "__main__": - clargs = handle_clargs() - conda = Conda(root=clargs.root, exe=clargs.exe) - - dispatch = { - "envs": conda.print_envs, - "root": conda.print_root, - "exe": conda.print_exe, - "info": conda.print_info, - "fork": lambda: conda.fork(clargs.name), - "bless": lambda: conda.bless(clargs.name) - } - - dispatch[clargs.command]() - - -