This commit is contained in:
2026-01-29 16:59:15 +01:00
parent 7ae397aaa6
commit 4e05afd30f
4 changed files with 68 additions and 219 deletions
+40
View File
@@ -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
+28
View File
@@ -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.
-27
View File
@@ -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
```
-192
View File
@@ -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]()