diff --git a/.github/workflows/create-tag.yaml b/.github/workflows/create-tag.yaml new file mode 100644 index 0000000..796386c --- /dev/null +++ b/.github/workflows/create-tag.yaml @@ -0,0 +1,78 @@ +# This workflow creates and pushes a release tag using the push-release-tag.sh script. +# It can be triggered manually and will prompt for confirmation before creating the tag. + +name: Create Release Tag + +on: + workflow_dispatch: + inputs: + dry_run: + description: "Run in dry-run mode (show what would be done without actually creating/pushing the tag)" + required: false + type: boolean + default: true + confirm_release: + description: "Type 'YES' to confirm you want to create and push the release tag" + required: true + type: string + +jobs: + check-branch: + runs-on: ubuntu-latest + environment: production + steps: + - name: Check if running on release branch + run: | + if [ "${{ github.ref }}" != "refs/heads/release" ]; then + echo "Error: This workflow can only be run from the 'release' branch." + echo "Current branch: ${{ github.ref }}" + echo "Please switch to the 'release' branch and try again." + exit 1 + fi + echo "Running on release branch - proceeding with workflow." + + create-release-tag: + runs-on: ubuntu-latest + needs: check-branch + environment: production + if: github.ref == 'refs/heads/release' + + permissions: + contents: write # Required to create and push tags + + steps: + - name: Validate confirmation + if: github.event.inputs.confirm_release != 'YES' && github.event.inputs.dry_run != 'true' + run: | + echo "Error: You must type 'YES' in the confirm_release input to proceed with creating a release tag." + echo "Received: '${{ github.event.inputs.confirm_release }}'" + exit 1 + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history and tags + + - name: Make scripts executable + run: | + chmod +x scripts/push_release_tag.sh + chmod +x scripts/get_version.sh + + - name: Configure Git + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Run push-release-tag script (dry-run) + if: github.event.inputs.dry_run == 'true' + run: | + echo "Running in dry-run mode..." + make push-release-tag DRY_RUN=--dry-run + + - name: Run push-release-tag script + if: github.event.inputs.dry_run != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Creating and pushing release tag..." + # Override the interactive confirmation since we already confirmed via workflow input + echo "YES" | make push-release-tag diff --git a/.github/workflows/publish-to-aws.yaml b/.github/workflows/publish-to-aws.yaml index f7d9014..2380745 100644 --- a/.github/workflows/publish-to-aws.yaml +++ b/.github/workflows/publish-to-aws.yaml @@ -3,16 +3,38 @@ name: Deploy Python Package to AWS on: push: tags: - - "*" + # e.g. v1.0.0, v1.0.1, v1.0.0-rc1, etc. + - "v[0-9]+.[0-9]+.[0-9]+*" workflow_dispatch: env: - PACKAGE_NAME: atlas + PACKAGE_NAME: layerlens jobs: + validate: + runs-on: ubuntu-latest + environment: production + outputs: + release_tag: ${{ steps.set_release_tag.outputs.release_tag }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history for checking branch + - name: Set release tag + id: set_release_tag + # ensure the tag is valid (matches code, is on release branch, etc) + run: | + RELEASE_TAG=${GITHUB_REF#refs/tags/} + echo "Using tag: $RELEASE_TAG" + chmod +x ./scripts/validate_release_tag.sh + ./scripts/validate_release_tag.sh "$RELEASE_TAG" + echo "RELEASE_TAG=$RELEASE_TAG" >> $GITHUB_ENV + echo "release_tag=$RELEASE_TAG" >> $GITHUB_OUTPUT + deploy: + needs: validate runs-on: ubuntu-latest - environment: development + environment: production steps: - name: Checkout code diff --git a/pyproject.toml b/pyproject.toml index fef0e24..ab059a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "atlas" +name = "layerlens" version = "1.0.2" description = "The official Python library for the LayerLens Atlas API" license = "Apache-2.0" diff --git a/scripts/push_release_tag.sh b/scripts/push_release_tag.sh new file mode 100644 index 0000000..e6529ce --- /dev/null +++ b/scripts/push_release_tag.sh @@ -0,0 +1,72 @@ +#!/bin/bash +set -euo pipefail + +ROOT_DIR=$(git rev-parse --show-toplevel) + +# Parse command line arguments +DRY_RUN=false + +while [[ $# -gt 0 ]]; do + case "$1" in + --dry-run) + DRY_RUN=true + shift + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--dry-run]" + exit 1 + ;; + esac +done + +git fetch --tags --prune + +REPO_URL="https://github.com/LayerLens/atlas-python" +# e.g. v1.0.0, v1.0.1, etc. +TAG_PREFIX="v" +COMMIT=$(git rev-parse --short HEAD) +VERSION=$(bash "$ROOT_DIR/scripts/get_version.sh") +TAG="${TAG_PREFIX}${VERSION}" + +if git rev-parse "$TAG" >/dev/null 2>&1; then + echo "Error: Tag $TAG already exists" + exit 1 +fi + +# Find the most recent version tag +LAST_RELEASE=$(git tag -l "${TAG_PREFIX}*" --sort=-v:refname | head -n 1) + +echo "================================================" +echo " Atlas Python SDK Release" +echo "================================================" +echo "version: ${TAG}" +echo "commit: ${COMMIT}" +echo "code: ${REPO_URL}/commit/${COMMIT}" +echo "changeset: ${REPO_URL}/compare/${LAST_RELEASE}...${COMMIT}" + +if [ "$DRY_RUN" = true ]; then + exit 0 +fi + +echo "" +echo "" +echo "Are you ready to release version ${VERSION}? Type 'YES' to continue:" +read -r CONFIRMATION + +if [ "$CONFIRMATION" != "YES" ]; then + echo "Release cancelled." + exit 1 +fi + +# Create and push the tag +echo "" +echo "Creating and pushing tag ${TAG}" +echo "" + +git tag "$TAG" "$COMMIT" +git push origin "$TAG" + +echo "" +echo "Tag ${TAG} has been created and pushed to origin." +echo "" \ No newline at end of file diff --git a/scripts/validate_release_tag.sh b/scripts/validate_release_tag.sh new file mode 100644 index 0000000..31776b8 --- /dev/null +++ b/scripts/validate_release_tag.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Validate release requirements +# - Checks if the tag matches semantic versioning convention (v{major}.{minor}.{patch} with optional suffix) +# - Checks if the tag matches the version in the package +# - Ensures we're releasing from the release branch + +set -e + +# Get the tag from the first command line argument +if [ $# -eq 0 ]; then + echo "ERROR: Release tag argument not provided" + echo "Usage: $0 " + exit 1 +fi + +ROOT_DIR=$(git rev-parse --show-toplevel) + +# Fetch the latest tags to ensure we're up to date +git fetch --tags --prune --force + +TAG=$1 + +# Check if tag follows semantic versioning pattern (v{major}.{minor}.{patch} with optional suffix) +if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+.*$ ]]; then + echo "ERROR: Tag must follow semantic versioning pattern (v{major}.{minor}.{patch} with optional suffix)" + echo "Examples: v1.0.0, v2.1.3, v1.0.0-beta, v1.0.0-rc.1" + exit 1 +fi + +# Extract version without the 'v' prefix +VERSION=${TAG#v} + +PACKAGE_VERSION=$(bash "$ROOT_DIR/scripts/get_version.sh") + +# Check if the tag version matches the package version +if [ "$VERSION" != "$PACKAGE_VERSION" ]; then + echo "ERROR: Tag version ($VERSION) does not match package version ($PACKAGE_VERSION)" + exit 1 +fi + +CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) +if [ "$CURRENT_BRANCH" != "release" ]; then + # If we're in detached HEAD state (which is likely in GitHub Actions with a tag), + # we need to check if the tag is on the release branch + if ! git rev-parse "$TAG" &>/dev/null; then + echo "ERROR: Tag $TAG does not exist in the repository" + exit 1 + fi + + TAG_COMMIT=$(git rev-parse "$TAG") + + # Ensure we have release branch history + git fetch origin release --depth=1000 + + # Check if tag is on release branch + if ! git merge-base --is-ancestor "$TAG_COMMIT" origin/release; then + echo "ERROR: Tag $TAG is not on the release branch" + exit 1 + fi +fi + +# All checks passed +exit 0 \ No newline at end of file diff --git a/src/CONTRIBUTING.md b/src/CONTRIBUTING.md index e97c10b..fb73d10 100644 --- a/src/CONTRIBUTING.md +++ b/src/CONTRIBUTING.md @@ -106,3 +106,141 @@ To format and fix all ruff issues automatically: ```sh $ ./scripts/format ``` + +## Release Process + +This section outlines the complete process for releasing a new version of the Atlas Python SDK. + + +### Step-by-Step Release Process + +#### 1. Prepare the Release Branch + +```sh +# Switch to the main branch and pull latest changes +$ git checkout main +$ git pull origin main + +# Create or switch to the release branch +$ git checkout release +$ git pull origin release + +# rebase | cherry-pick latest changes from main into release +$ git rebase main +``` + +#### 2. Bump the Version + +Update the version number in the following files: + +- **`src/atlas/_version.py`**: Update the `__version__` string +- **`pyproject.toml`**: Update the `version` field + +**Version Format**: Use semantic versioning (MAJOR.MINOR.PATCH) +- **MAJOR**: Breaking changes +- **MINOR**: New features (backward compatible) +- **PATCH**: Bug fixes (backward compatible) + +**Examples**: +- `1.0.2` → `1.0.3` (patch release for bug fixes) +- `1.0.2` → `1.1.0` (minor release for new features) +- `1.0.2` → `2.0.0` (major release for breaking changes) + +```sh +# Example: Update to version 1.0.3 +# Edit src/atlas/_version.py +__version__ = "1.0.3" + +# Edit pyproject.toml +version = "1.0.3" +``` + +#### 3. Test and Validate + +```sh +# Run all tests to ensure everything works +$ ./scripts/test + +# Run linting to ensure code quality +$ ./scripts/lint + +# Build the package to verify it builds correctly +$ rye build +``` + +#### 4. Commit and Push Changes + +```sh +# Add and commit the version bump +$ git add src/atlas/_version.py pyproject.toml +$ git commit -m "chore: bump version to 1.0.3" + +# Push the release branch +$ git push origin release +``` + +#### 5. Create the Release Tag + +Use the GitHub Actions workflow to create and push the release tag: + +1. **Go to GitHub Actions**: Navigate to the "Actions" tab in the GitHub repository +2. **Find "Create Release Tag"**: Look for the workflow in the left sidebar +3. **Run workflow**: Click "Run workflow" with these settings: + - **Use workflow from**: `release` (branch) + - **Run in dry-run mode**: Check this box first to preview + - **Confirm release**: Leave empty for dry-run + +4. **Review dry-run output**: Check the workflow output to ensure everything looks correct + +5. **Create actual release**: Run the workflow again with: + - **Run in dry-run mode**: Uncheck this box + - **Confirm release**: Type `YES` exactly + +Alternatively, if any issue occurs, you can create the tag manually: + +```sh +# Create and push the tag manually (if preferred) +$ git tag v1.0.3 +$ git push origin v1.0.3 +``` + +#### 6. Automatic Deployment + +Once the tag is pushed, the "Deploy Python Package to AWS" workflow will automatically: + +1. **Validate the release**: Run the validation script to ensure the tag follows semantic versioning +2. **Run tests**: Execute the full test suite +3. **Build the package**: Create distribution files +4. **Deploy to AWS**: Publish the package to the configured AWS repository + +#### 7. Verify the Release + +1. **Check GitHub Actions**: Ensure the deployment workflow completed successfully +2. **Verify package availability**: Check that the new version is available in your package repository +3. **Test installation**: Try installing the new version in a clean environment + +### Release Checklist + +Use this checklist to ensure you we don't miss any steps: + +- [ ] Switched to and updated the `release` branch +- [ ] Merged latest changes from `main` +- [ ] Updated version in `src/atlas/_version.py` +- [ ] Updated version in `pyproject.toml` +- [ ] Ran tests (`./scripts/test`) +- [ ] Ran linting (`./scripts/lint`) +- [ ] Built package successfully (`rye build`) +- [ ] Committed and pushed version bump +- [ ] Created release tag (via GitHub Actions or manually) +- [ ] Verified deployment workflow completed successfully +- [ ] Tested new version installation + +### Troubleshooting + +**Tag already exists**: If you need to recreate a tag, delete it first: +```sh +$ git tag -d v1.0.3 # Delete locally +$ git push origin :v1.0.3 # Delete on remote +``` + +**Rollback**: If you need to rollback a release, create a new patch version rather than trying to delete the problematic release.