diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml new file mode 100644 index 0000000..ff46ad7 --- /dev/null +++ b/.github/workflows/pr-checks.yml @@ -0,0 +1,69 @@ +name: PR Checks + +on: + pull_request: + branches: + - main + +jobs: + quality: + name: Lint & Security + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install UV + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + + - name: Install dev dependencies + run: uv sync --frozen --only-group dev + + - name: Run ruff + run: uv run ruff check circleci_env_cli.py + + - name: Run bandit + run: uv run bandit -c pyproject.toml circleci_env_cli.py + + build: + name: Build (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13"] + + steps: + - uses: actions/checkout@v4 + + - name: Install UV + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: uv sync --frozen --no-dev + + - name: Build distribution + run: uv build + + - name: Verify CLI entry point + run: uv run circleci-env-cli --version + + docker: + name: Docker build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Build image + run: docker build -t circleci-env-cli . --file Dockerfile + + - name: Verify CLI entry point + run: docker run --rm circleci-env-cli --version diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index deee8e9..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: Build and Publish - -on: - push: - tags: - - v* - -env: - IMAGE_NAME: circleci-env-cli - -jobs: - push_to_pypi: - runs-on: ubuntu-latest - if: github.event_name == 'push' - - steps: - - uses: actions/checkout@v3 - - name: Build - run: | - pip3 install --quiet twine - python3 setup.py sdist bdist_wheel - - name: Publish - run: twine upload --skip-existing dist/* - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - - push_to_docker: - runs-on: ubuntu-latest - if: github.event_name == 'push' - - steps: - - uses: actions/checkout@v3 - - - name: Build image - run: docker build . --file Dockerfile --tag $IMAGE_NAME - - - name: Log into registry - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - - - name: Push image to gcr.io - run: | - IMAGE_ID=ghcr.io/${{ github.actor }}/$IMAGE_NAME - - # Strip "v" prefix from tag name - VERSION=${GITHUB_REF_NAME/v/} - - echo IMAGE_ID=$IMAGE_ID - echo VERSION=$VERSION - - docker tag $IMAGE_NAME $IMAGE_ID:$VERSION - docker tag $IMAGE_NAME $IMAGE_ID:latest - docker push $IMAGE_ID:$VERSION - docker push $IMAGE_ID:latest - - - name: Push image to hub.docker.com - run: | - echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u ${{ github.actor }} --password-stdin - - IMAGE_ID=${{ github.actor }}/$IMAGE_NAME - - # Strip "v" prefix from tag name - VERSION=${GITHUB_REF_NAME/v/} - - echo IMAGE_ID=$IMAGE_ID - echo VERSION=$VERSION - - docker tag $IMAGE_NAME $IMAGE_ID:$VERSION - docker tag $IMAGE_NAME $IMAGE_ID:latest - docker push $IMAGE_ID:$VERSION - docker push $IMAGE_ID:latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2b21b56..db0f922 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,37 +1,148 @@ # https://docs.github.com/en/actions -name: "Release" +name: Release -on: # yamllint disable-line rule:truthy +on: push: tags: - "v*" +env: + IMAGE_NAME: circleci-env-cli + VERSION: ${{ github.ref_name }} + +permissions: + contents: write + jobs: - release: - name: "Release" + prepare: + name: Bump version + runs-on: ubuntu-latest - runs-on: "ubuntu-latest" + steps: + - uses: actions/checkout@v4 + + - name: Install UV + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + + - name: Bump version in pyproject.toml + run: uv version "${VERSION#v}" + + - name: Update lockfile + run: uv lock + + - name: Upload bumped manifests + uses: actions/upload-artifact@v4 + with: + name: manifests + path: | + pyproject.toml + uv.lock + + publish_pypi: + name: Publish to PyPI + runs-on: ubuntu-latest + needs: prepare steps: - - name: "Create release" - uses: "actions/github-script@v6" + - uses: actions/checkout@v4 + + - name: Download bumped manifests + uses: actions/download-artifact@v4 + with: + name: manifests + + - name: Install UV + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + + - name: Build + run: uv build + + - name: Publish + run: uv publish + env: + UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }} + + publish_docker: + name: Publish to Docker registries + runs-on: ubuntu-latest + needs: prepare + + steps: + - uses: actions/checkout@v4 + + - name: Download bumped manifests + uses: actions/download-artifact@v4 + with: + name: manifests + + - name: Build image + run: docker build . --file Dockerfile --tag $IMAGE_NAME + + - name: Push to ghcr.io + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + IMAGE_ID=ghcr.io/${{ github.actor }}/$IMAGE_NAME + docker tag $IMAGE_NAME $IMAGE_ID:${VERSION#v} + docker tag $IMAGE_NAME $IMAGE_ID:latest + docker push $IMAGE_ID:${VERSION#v} + docker push $IMAGE_ID:latest + + - name: Push to hub.docker.com + run: | + echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u ${{ github.actor }} --password-stdin + IMAGE_ID=${{ github.actor }}/$IMAGE_NAME + docker tag $IMAGE_NAME $IMAGE_ID:${VERSION#v} + docker tag $IMAGE_NAME $IMAGE_ID:latest + docker push $IMAGE_ID:${VERSION#v} + docker push $IMAGE_ID:latest + + commit_and_release: + name: Commit version bump & Create release + runs-on: ubuntu-latest + needs: [publish_pypi, publish_docker] + + steps: + - uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Download bumped manifests + uses: actions/download-artifact@v4 + with: + name: manifests + + - name: Commit and push version bump + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add pyproject.toml uv.lock + git commit -m "chore: bump version to ${VERSION#v} [skip ci]" + git push origin main + + - name: Create GitHub release + uses: actions/github-script@v7 with: github-token: "${{ secrets.GITHUB_TOKEN }}" script: | try { - const response = await github.rest.repos.createRelease({ + await github.rest.repos.createRelease({ draft: false, generate_release_notes: true, - name: process.env.GITHUB_REF_NAME, + name: process.env.VERSION, owner: context.repo.owner, prerelease: false, repo: context.repo.repo, - tag_name: process.env.GITHUB_REF_NAME, + tag_name: process.env.VERSION, }); - - core.exportVariable('RELEASE_ID', response.data.id); - core.exportVariable('RELEASE_UPLOAD_URL', response.data.upload_url); } catch (error) { core.setFailed(error.message); } diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml new file mode 100644 index 0000000..49ede07 --- /dev/null +++ b/.github/workflows/update-changelog.yml @@ -0,0 +1,37 @@ +name: Update Changelog + +on: + release: + types: + - published + +env: + GIT_TAG: ${{ github.event.release.tag_name }} + +jobs: + update: + name: Update CHANGELOG.md + runs-on: ubuntu-latest + + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.target_commitish }} + + - name: Update Changelog + uses: stefanzweifel/changelog-updater-action@v1 + with: + latest-version: ${{ env.GIT_TAG }} + release-notes: ${{ github.event.release.body }} + + - name: Commit updated CHANGELOG + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add CHANGELOG.md + git commit -m "docs: update CHANGELOG for $GIT_TAG [skip ci]" + git push diff --git a/Dockerfile b/Dockerfile index 37c69a8..a3c2e69 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,13 @@ -FROM python:3.10-alpine +FROM python:3.13-alpine -COPY requirements.txt . -RUN pip install -r requirements.txt +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ -COPY circleci_env_cli.py . +WORKDIR /app -ENTRYPOINT ["python", "circleci_env_cli.py"] +COPY pyproject.toml uv.lock ./ +RUN uv sync --frozen --no-dev --no-install-project + +COPY circleci_env_cli.py ./ +RUN uv sync --frozen --no-dev + +ENTRYPOINT ["uv", "run", "--no-sync", "circleci-env-cli"] diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 096dd50..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -include LICENSE -include requirements.txt diff --git a/README.md b/README.md index d7c734e..f7cc500 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,35 @@ # circleci-env-cli -[![Build Status](https://github.com/phsmith/circleci-env-cli/actions/workflows/publish.yml/badge.svg)](https://github.com/phsmith/circleci-env-cli/actions/workflows/publish.yml) + +[![Build Status](https://github.com/phsmith/circleci-env-cli/actions/workflows/release.yml/badge.svg)](https://github.com/phsmith/circleci-env-cli/actions/workflows/release.yml) [![PyPI version](https://img.shields.io/pypi/v/circleci-env-cli?color=yellow)](https://python.org/pypi/circleci-env-cli) [![Docker Image Version (latest semver)](https://img.shields.io/docker/v/phsmith/circleci-env-cli?label=docker%20version&color=blue)](https://hub.docker.com/r/phsmith/circleci-env-cli) [![Docker Pulls](https://img.shields.io/docker/pulls/phsmith/circleci-env-cli?color=lightblue)](https://hub.docker.com/r/phsmith/circleci-env-cli) -CLI tool for manage CircleCI contexts and environment vars +CLI tool for managing CircleCI contexts and environment variables. ## Installation -### Install locally -``` -pip install -r requirements.txt -./circleci_env_cli.py [OPTIONS] + +### Install via UV (recommended) + +```sh +uv tool install circleci-env-cli ``` ### Install via Pip -``` + +```sh pip install circleci-env-cli ``` -### Install via Docker +### Run without installing + +```sh +uvx circleci-env-cli [OPTIONS] ``` + +### Install via Docker + +```sh docker pull phsmith/circleci-env-cli:[VERSION] # Run example @@ -31,18 +41,19 @@ docker run --rm -it \ ## Usage -A [CircleCI personal API token](https://circleci.com/docs/managing-api-tokens/#creating-a-personal-api-token) must be create before use this tool. +A [CircleCI personal API token](https://circleci.com/docs/managing-api-tokens/#creating-a-personal-api-token) must be created before using this tool. > All options can be specified as environment variables in the format: `CIRCLE_