Skip to content

Commit

Permalink
add latest wrangler
Browse files Browse the repository at this point in the history
  • Loading branch information
emmyoop committed Apr 2, 2024
1 parent 772158d commit f6189f4
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .github/actions/latest-wrangler/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM python:3-slim AS builder
ADD . /app
WORKDIR /app

# We are installing a dependency here directly into our app source dir
RUN pip install --target=/app requests packaging

# A distroless container image with Python and some basics like SSL certificates
# https://github.com/GoogleContainerTools/distroless
FROM gcr.io/distroless/python3-debian10
COPY --from=builder /app /app
WORKDIR /app
ENV PYTHONPATH /app
CMD ["/app/main.py"]
50 changes: 50 additions & 0 deletions .github/actions/latest-wrangler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Github package 'latest' tag wrangler for containers
## Usage

Plug in the necessary inputs to determine if the container being built should be tagged 'latest; at the package level, for example `dbt-redshift:latest`.

## Inputs
| Input | Description |
| - | - |
| `package` | Name of the GH package to check against |
| `new_version` | Semver of new container |
| `gh_token` | GH token with package read scope|
| `halt_on_missing` | Return non-zero exit code if requested package does not exist. (defaults to false)|


## Outputs
| Output | Description |
| - | - |
| `latest` | Wether or not the new container should be tagged 'latest'|
| `minor_latest` | Wether or not the new container should be tagged major.minor.latest |

## Example workflow
```yaml
name: Ship it!
on:
workflow_dispatch:
inputs:
package:
description: The package to publish
required: true
version_number:
description: The version number
required: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Wrangle latest tag
id: is_latest
uses: ./.github/actions/latest-wrangler
with:
package: ${{ github.event.inputs.package }}
new_version: ${{ github.event.inputs.new_version }}
gh_token: ${{ secrets.GITHUB_TOKEN }}
- name: Print the results
run: |
echo "Is it latest? Survey says: ${{ steps.is_latest.outputs.latest }} !"
echo "Is it minor.latest? Survey says: ${{ steps.is_latest.outputs.minor_latest }} !"
```
21 changes: 21 additions & 0 deletions .github/actions/latest-wrangler/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: "GitHub package `latest` tag wrangler for containers"
description: "Determines if the published image should include `latest` tags"

inputs:
package_name:
description: "Package being published (i.e. `dbt-core`, `dbt-redshift`, etc.)"
required: true
new_version:
description: "SemVer of the package being published (i.e. 1.7.2, 1.8.0a1, etc.)"
required: true
github_token:
description: "Auth token for GitHub (must have view packages scope)"
required: true

outputs:
tags:
description: "A list of tags to associate with this version"

runs:
using: "docker"
image: "Dockerfile"
26 changes: 26 additions & 0 deletions .github/actions/latest-wrangler/examples/example_workflow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Ship it!
on:
workflow_dispatch:
inputs:
package:
description: The package to publish
required: true
version_number:
description: The version number
required: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Wrangle latest tag
id: is_latest
uses: ./.github/actions/latest-wrangler
with:
package: ${{ github.event.inputs.package }}
new_version: ${{ github.event.inputs.new_version }}
gh_token: ${{ secrets.GITHUB_TOKEN }}
- name: Print the results
run: |
echo "Is it latest? Survey says: ${{ steps.is_latest.outputs.latest }} !"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"inputs": {
"version_number": "1.0.1",
"package": "dbt-redshift"
}
}
71 changes: 71 additions & 0 deletions .github/actions/latest-wrangler/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import os
from packaging.version import Version, parse
import requests
import sys
from typing import List


def main():
package_name: str = os.environ["INPUT_PACKAGE_NAME"]
new_version: Version = parse(os.environ["INPUT_NEW_VERSION"])
github_token: str = os.environ["INPUT_GITHUB_TOKEN"]

response = _package_metadata(package_name, github_token)
published_versions = _published_versions(response)
new_version_tags = _new_version_tags(new_version, published_versions)
_register_tags(new_version_tags, package_name)


def _package_metadata(package_name: str, github_token: str) -> requests.Response:
url = f"https://api.github.com/orgs/dbt-labs/packages/container/{package_name}/versions"
return requests.get(url, auth=("", github_token))


def _published_versions(response: requests.Response) -> List[Version]:
package_metadata = response.json()
return [
parse(tag)
for version in package_metadata
for tag in version["metadata"]["container"]["tags"]
if "latest" not in tag
]


def _new_version_tags(new_version: Version, published_versions: List[Version]) -> List[str]:
# the package version is always a tag
tags = [str(new_version)]

# pre-releases don't get tagged with `latest`
if new_version.is_prerelease:
return tags

if new_version > max(published_versions):
tags.append("latest")

published_patches = [
version
for version in published_versions
if version.major == new_version.major and version.minor == new_version.minor
]
if new_version > max(published_patches):
tags.append(f"{new_version.major}.{new_version.minor}.latest")

return tags


def _register_tags(tags: List[str], package_name: str) -> None:
fully_qualified_tags = ",".join([f"ghcr.io/dbt-labs/{package_name}:{tag}" for tag in tags])
github_output = os.environ.get("GITHUB_OUTPUT")
with open(github_output, "at", encoding="utf-8") as gh_output:
gh_output.write(f"fully_qualified_tags={fully_qualified_tags}")


def _validate_response(response: requests.Response) -> None:
message = response["message"]
if response.status_code != 200:
print(f"Call to GitHub API failed: {response.status_code} - {message}")
sys.exit(1)


if __name__ == "__main__":
main()

0 comments on commit f6189f4

Please sign in to comment.