From de593a877ee47aa4d1879ad9e849068a6d7eecd5 Mon Sep 17 00:00:00 2001 From: Alan Ponte Date: Sat, 7 Mar 2026 16:22:14 -0800 Subject: [PATCH 1/4] add agent docs --- Agent.md => docs/Agent.md | 0 docs/upload-artifact.md | 17 +++++++++++++++++ main.py | 0 3 files changed, 17 insertions(+) rename Agent.md => docs/Agent.md (100%) create mode 100644 docs/upload-artifact.md delete mode 100644 main.py diff --git a/Agent.md b/docs/Agent.md similarity index 100% rename from Agent.md rename to docs/Agent.md diff --git a/docs/upload-artifact.md b/docs/upload-artifact.md new file mode 100644 index 0000000..f2d3721 --- /dev/null +++ b/docs/upload-artifact.md @@ -0,0 +1,17 @@ +We are building a way to deploy `babylon_features` as an artifact, which is a Zip module. + +## Task +Your task is to design and implement a workflow to build this python project with `tox`, and push the built artifact to Github. +- The workflow should be implemented in a Python script, named `artifact_upload.py`. +- There should be a CD flow in `.github` to deploy to Github artifacts. + +## Credentials +This project already connects to Github's artifact server to pull the API spec artifact. +If there are issues with credentials for pushing to Github, check with the user for instructions. + +## TOX +Tox should be the source of truth for building the zip module. + +## README and Documentation. +Update the README.md with any changes. Ensure the flow is well-documented and commented. + diff --git a/main.py b/main.py deleted file mode 100644 index e69de29..0000000 From df149ab11a4c6c1c9d015e6a30e74d55a1c81791 Mon Sep 17 00:00:00 2001 From: Alan Ponte Date: Sat, 7 Mar 2026 16:38:32 -0800 Subject: [PATCH 2/4] Add CD pipeline --- .github/workflows/artifact_cd.yml | 37 ++++++++ .gitignore | 3 + README.md | 24 ++++- artifact_upload.py | 147 ++++++++++++++++++++++++++++++ docs/upload-artifact.md | 4 +- tox.ini | 9 ++ 6 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/artifact_cd.yml create mode 100644 artifact_upload.py diff --git a/.github/workflows/artifact_cd.yml b/.github/workflows/artifact_cd.yml new file mode 100644 index 0000000..6920ba2 --- /dev/null +++ b/.github/workflows/artifact_cd.yml @@ -0,0 +1,37 @@ +# GitHub Action for Building and Uploading Babylon Features Artifact +name: Babylon Features Artifact CD + +on: + push: + tags: + - 'v*' # Trigger on version tags (e.g., v1.0.0) + workflow_dispatch: # Allow manual trigger + +permissions: + contents: write + +jobs: + build-and-upload: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox requests + + - name: Run Artifact Upload Script + env: + BABYLON_API_GITHUB_PAT_TOKEN: ${{ secrets.BABYLON_API_GITHUB_PAT_TOKEN }} + run: | + # The script will run tox -e build-artifact and upload to GitHub Releases + python artifact_upload.py --repo ${{ github.repository }} --tag ${{ github.ref_name }} diff --git a/.gitignore b/.gitignore index dd48eb3..7c6f6bb 100644 --- a/.gitignore +++ b/.gitignore @@ -211,3 +211,6 @@ __marimo__/ # Chroma local DB chromadb/ + +# Any zipped artifacts +*.zip diff --git a/README.md b/README.md index 94adc62..7a1f2f3 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,32 @@ A local distribution of the package can be created either through ``` or ```shell - tos -e dist + tox -e dist ``` Since the build is dependent on `poetry`, the commands are equivalent. +### Artifact Deployment +This project can be built as a Zip module artifact for deployment. The artifact includes the `features/` package. + +To build the artifact locally: +```shell +tox -e build-artifact +``` +This will create `babylon.zip` in the root directory. + +### CI/CD +The project uses GitHub Actions for CI/CD. + +- **babylon-features**: Runs on every push and pull request to `main`. It performs linting, formatting, and unit testing using `tox`. +- **Babylon Features Artifact CD**: Runs when a version tag (e.g., `v1.0.0`) is pushed or manually triggered. It builds the `babylon.zip` artifact using `tox` and uploads it to GitHub Releases using `artifact_upload.py`. + +To manually trigger a deployment and upload to GitHub: +```shell +python artifact_upload.py --repo / --tag +``` +Ensure `BABYLON_API_GITHUB_PAT_TOKEN` is set in your environment. + + ### Unit Tests This project uses `pytest`. You can invoke tests in a poetry environment, via ```shell diff --git a/artifact_upload.py b/artifact_upload.py new file mode 100644 index 0000000..fff6b02 --- /dev/null +++ b/artifact_upload.py @@ -0,0 +1,147 @@ +import argparse +import os +import subprocess +import sys +import requests + +def run_tox_build(): + """ + Executes tox to build the artifact. + """ + print("Building artifact with tox...") + try: + subprocess.run(["tox", "-e", "build-artifact"], check=True) + print("Tox build successful.") + except subprocess.CalledProcessError as e: + print(f"Error: Tox build failed with exit code {e.returncode}") + sys.exit(1) + except FileNotFoundError: + print("Error: 'tox' command not found. Ensure tox is installed.") + sys.exit(1) + +def get_release_by_tag(repo: str, tag: str, pat_token: str): + """ + Fetches a release by its tag. + """ + api_url = f"https://api.github.com/repos/{repo}/releases/tags/{tag}" + headers = { + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {pat_token}" + } + response = requests.get(api_url, headers=headers) + if response.status_code == 200: + return response.json() + return None + +def create_release(repo: str, tag: str, pat_token: str): + """ + Creates a new release on GitHub. + """ + print(f"Creating new release for tag: {tag}") + api_url = f"https://api.github.com/repos/{repo}/releases" + headers = { + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {pat_token}" + } + data = { + "tag_name": tag, + "name": f"Babylon Artifact {tag}", + "body": "Automatically generated Babylon artifact.", + "draft": False, + "prerelease": False + } + response = requests.post(api_url, headers=headers, json=data) + response.raise_for_status() + return response.json() + +def upload_artifact_to_release(repo: str, release_id: int, artifact_path: str, pat_token: str): + """ + Uploads a file as a release asset. + """ + artifact_name = os.path.basename(artifact_path) + print(f"Uploading {artifact_name} to release {release_id}...") + + # First, check if asset already exists and delete it if so (to allow overwrite) + api_url = f"https://api.github.com/repos/{repo}/releases/{release_id}/assets" + headers = { + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {pat_token}" + } + response = requests.get(api_url, headers=headers) + response.raise_for_status() + assets = response.json() + for asset in assets: + if asset['name'] == artifact_name: + print(f"Deleting existing asset: {artifact_name}") + delete_url = asset['url'] + requests.delete(delete_url, headers=headers).raise_for_status() + + # Upload the new asset + upload_url = f"https://uploads.github.com/repos/{repo}/releases/{release_id}/assets?name={artifact_name}" + headers["Content-Type"] = "application/zip" + + with open(artifact_path, "rb") as f: + response = requests.post(upload_url, headers=headers, data=f) + response.raise_for_status() + + print(f"Successfully uploaded {artifact_name} to GitHub Releases.") + +def main(): + """ + Main entry point for building and uploading Babylon artifacts to GitHub Releases. + The script: + 1. Parses command-line arguments for repo, tag, and GitHub PAT token. + 2. Determines the tag to use (defaults to 'latest'). + 3. Runs 'tox -e build-artifact' to generate 'babylon.zip' unless --skip-build is set. + 4. Fetches or creates a GitHub Release for the specified tag. + 5. Uploads 'babylon.zip' as a release asset (overwriting if it already exists). + """ + parser = argparse.ArgumentParser(description="Build and upload Babylon artifact to GitHub.") + parser.add_argument("--repo", required=True, help="GitHub repository (e.g., owner/repo)") + parser.add_argument("--tag", help="Release tag to upload to (defaults to 'latest' if not provided)") + parser.add_argument("--pat-token", default=os.environ.get("BABYLON_API_GITHUB_PAT_TOKEN"), help="GitHub PAT token") + parser.add_argument("--skip-build", action="store_true", help="Skip the tox build step") + + args = parser.parse_args() + + # Process tag information from GitHub Actions or manual input + # Use 'latest' if tag is not provided or if it's a branch name from GHA + tag = args.tag if args.tag and not args.tag.startswith("refs/heads/") else "latest" + if args.tag and "/" in args.tag and not args.tag.startswith("refs/tags/"): + # if it's a full ref but not a tag ref (e.g. refs/pull/...), default to latest + tag = "latest" + elif args.tag and args.tag.startswith("refs/tags/"): + # Clean up refs/tags/ prefix if present + tag = args.tag.replace("refs/tags/", "") + + if not args.pat_token: + print("Error: GitHub PAT token not provided. Set BABYLON_API_GITHUB_PAT_TOKEN or use --pat-token.") + sys.exit(1) + + artifact_path = "babylon.zip" + + # Step 1: Build the artifact using tox as the source of truth + if not args.skip_build: + run_tox_build() + + # Step 2: Ensure the artifact was actually built + if not os.path.exists(artifact_path): + print(f"Error: Artifact not found at {artifact_path}") + sys.exit(1) + + try: + # Step 3: Find or create the GitHub Release + release = get_release_by_tag(args.repo, tag, args.pat_token) + if not release: + release = create_release(args.repo, tag, args.pat_token) + + # Step 4: Upload the artifact as a release asset + upload_artifact_to_release(args.repo, release['id'], artifact_path, args.pat_token) + except requests.exceptions.RequestException as e: + print(f"GitHub API Error: {e}") + if e.response is not None: + print(f"Response: {e.response.text}") + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/docs/upload-artifact.md b/docs/upload-artifact.md index f2d3721..cc2024f 100644 --- a/docs/upload-artifact.md +++ b/docs/upload-artifact.md @@ -1,12 +1,12 @@ We are building a way to deploy `babylon_features` as an artifact, which is a Zip module. ## Task -Your task is to design and implement a workflow to build this python project with `tox`, and push the built artifact to Github. +Your task is to design and implement a Github CI workflow to build this python project with `tox`, and push the built artifact to Github. - The workflow should be implemented in a Python script, named `artifact_upload.py`. - There should be a CD flow in `.github` to deploy to Github artifacts. ## Credentials -This project already connects to Github's artifact server to pull the API spec artifact. +The Guthub PAT should exist at `$BABYLON_API_GITHUB_PAT_TOKEN`. If there are issues with credentials for pushing to Github, check with the user for instructions. ## TOX diff --git a/tox.ini b/tox.ini index 99c3d96..6374877 100644 --- a/tox.ini +++ b/tox.ini @@ -66,6 +66,15 @@ commands = mypy features allowlist_externals = mypy +[testenv:build-artifact] +description = Build babylon.zip artifact +deps = + -e . +commands = + python -c "import shutil; shutil.make_archive('babylon', 'zip', '.', 'features')" +allowlist_externals = + python + [testenv:dist] description = Build a distribution via poetry. From f4076dccda375855de434fcc9dccfa86434a3dd5 Mon Sep 17 00:00:00 2001 From: Alan Ponte Date: Sat, 7 Mar 2026 16:41:44 -0800 Subject: [PATCH 3/4] update --- README.md | 6 +++--- artifact_upload.py | 6 +++--- tox.ini | 15 +++++---------- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 7a1f2f3..962fe8e 100644 --- a/README.md +++ b/README.md @@ -41,15 +41,15 @@ This project can be built as a Zip module artifact for deployment. The artifact To build the artifact locally: ```shell -tox -e build-artifact +tox -e dist ``` -This will create `babylon.zip` in the root directory. +This will create `babylon.zip` in the root directory, along with standard poetry distribution files in `dist/`. ### CI/CD The project uses GitHub Actions for CI/CD. - **babylon-features**: Runs on every push and pull request to `main`. It performs linting, formatting, and unit testing using `tox`. -- **Babylon Features Artifact CD**: Runs when a version tag (e.g., `v1.0.0`) is pushed or manually triggered. It builds the `babylon.zip` artifact using `tox` and uploads it to GitHub Releases using `artifact_upload.py`. +- **Babylon Features Artifact CD**: Runs when a version tag (e.g., `v1.0.0`) is pushed or manually triggered. It builds the `babylon.zip` artifact using `tox -e dist` and uploads it to GitHub Releases using `artifact_upload.py`. To manually trigger a deployment and upload to GitHub: ```shell diff --git a/artifact_upload.py b/artifact_upload.py index fff6b02..539a22a 100644 --- a/artifact_upload.py +++ b/artifact_upload.py @@ -6,11 +6,11 @@ def run_tox_build(): """ - Executes tox to build the artifact. + Executes tox to build the artifact using the dist environment. """ - print("Building artifact with tox...") + print("Building artifact with tox -e dist...") try: - subprocess.run(["tox", "-e", "build-artifact"], check=True) + subprocess.run(["tox", "-e", "dist"], check=True) print("Tox build successful.") except subprocess.CalledProcessError as e: print(f"Error: Tox build failed with exit code {e.returncode}") diff --git a/tox.ini b/tox.ini index 6374877..c87265d 100644 --- a/tox.ini +++ b/tox.ini @@ -66,22 +66,17 @@ commands = mypy features allowlist_externals = mypy -[testenv:build-artifact] -description = Build babylon.zip artifact -deps = - -e . -commands = - python -c "import shutil; shutil.make_archive('babylon', 'zip', '.', 'features')" -allowlist_externals = - python - [testenv:dist] description = - Build a distribution via poetry. + Build a distribution via poetry and a zip artifact. +deps = + -e . commands = poetry build + python -c "import shutil; shutil.make_archive('babylon', 'zip', '.', 'features')" allowlist_externals = poetry + python ;[testenv:download-spec] ;description = Download OpenAPI spec artifact From 52e516aa8c7e2d3580b1efbf871c0b8ae5baf73a Mon Sep 17 00:00:00 2001 From: Alan Ponte Date: Sat, 7 Mar 2026 16:52:51 -0800 Subject: [PATCH 4/4] cleanup --- .../workflows/{artifact_cd.yml => artifact-cd.yml} | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) rename .github/workflows/{artifact_cd.yml => artifact-cd.yml} (73%) diff --git a/.github/workflows/artifact_cd.yml b/.github/workflows/artifact-cd.yml similarity index 73% rename from .github/workflows/artifact_cd.yml rename to .github/workflows/artifact-cd.yml index 6920ba2..8a909bf 100644 --- a/.github/workflows/artifact_cd.yml +++ b/.github/workflows/artifact-cd.yml @@ -3,9 +3,15 @@ name: Babylon Features Artifact CD on: push: - tags: - - 'v*' # Trigger on version tags (e.g., v1.0.0) - workflow_dispatch: # Allow manual trigger + branches: [ "main" ] +# tags: +# - 'v*' # Trigger on version tags (e.g., v1.0.0) + workflow_dispatch: + inputs: + tag: + description: 'Release tag' + required: true + default: 'latest' permissions: contents: write @@ -31,7 +37,7 @@ jobs: - name: Run Artifact Upload Script env: - BABYLON_API_GITHUB_PAT_TOKEN: ${{ secrets.BABYLON_API_GITHUB_PAT_TOKEN }} + BABYLON_API_GITHUB_PAT_TOKEN: ${{ secrets.BABYLON_PAT_CI }} run: | # The script will run tox -e build-artifact and upload to GitHub Releases python artifact_upload.py --repo ${{ github.repository }} --tag ${{ github.ref_name }}