Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
sondrelg committed Aug 13, 2021
0 parents commit e0ee104
Show file tree
Hide file tree
Showing 13 changed files with 1,385 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .github/get_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""
Simple script used to tag our releases with major and minor git tags.
This lets users use the action with @v1 or @v1.1 references, and not have
to use the complete tag (with patch version specified).
"""

import sys

from packaging import version

if __name__ == '__main__':
ref = sys.argv[1] # ref will usually look like refs/tags/v1.0.1
major = sys.argv[2] == 'major'
version = version.parse(ref.split('refs/tags/v')[1])

if major:
print(f'v{version.major}')
else:
print(f'v{version.major}.{version.minor}')
20 changes: 20 additions & 0 deletions .github/workflows/tag.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# TODO: When upgrading to v1 and above, implement this:
# https://github.com/snok/install-poetry/blob/main/.github/workflows/tag_release.yml
name: Tag releases with minor versions

on:
release:
types: [published]

jobs:
tag-v1:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Update tag
run: |
minor_tag="$(python .github/get_version.py "${GITHUB_REF}" minor)"
git tag $minor_tag
git push origin HEAD:refs/heads/master --tags --force
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
80 changes: 80 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Test

on:
pull_request:
push:
branches:
- main

jobs:
linting:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.10.0-rc.1

- name: Load pre-commit cache
uses: actions/cache@v2
with:
path: |
~/.cache/pip
~/.cache/pre-commit
key: ${{ runner.os }}-pip-2
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Install pre-commit
run: python -m pip install pre-commit

- name: Run pre-commit
run: pre-commit run --all-files

test:
needs: linting
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.10.0-rc.1

- name: Load cached Poetry installation
uses: actions/cache@v2
with:
path: ~/.local
key: poetry-cache-0

- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: 1.2.0a2
virtualenvs-in-project: true

- name: Load cached venv
uses: actions/cache@v2
id: cache-venv
with:
path: .venv
key: ${{ hashFiles('**/poetry.lock') }}-0

- name: Install dependencies
run: poetry install --no-interaction --no-root
if: steps.cache-venv.outputs.cache-hit != 'true'

- name: Run tests
run: source $VENV && pytest main_tests.py --cov-report=xml

- uses: codecov/codecov-action@v2
with:
file: ./coverage.xml
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.env
.venv
.idea/
.mypy_cache/
__pycache__/
.coverage
47 changes: 47 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
repos:
- repo: https://github.com/ambv/black
rev: 21.7b0
hooks:
- id: black
args: ['--quiet']
- repo: https://github.com/pycqa/isort
rev: 5.9.3
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: check-ast
- id: check-added-large-files
- id: check-merge-conflict
- id: check-case-conflict
- id: check-docstring-first
- id: check-json
- id: check-yaml
- id: double-quote-string-fixer
- id: end-of-file-fixer
- id: trailing-whitespace
- id: mixed-line-ending
- id: trailing-whitespace
- repo: https://github.com/asottile/pyupgrade
rev: v2.23.3
hooks:
- id: pyupgrade
args: ['--py36-plus', '--py37-plus', '--keep-runtime-typing']
- repo: https://gitlab.com/pycqa/flake8
rev: 3.9.2
hooks:
- id: flake8
additional_dependencies: [
'flake8-bugbear',
'flake8-comprehensions',
'flake8-deprecated',
'flake8-use-fstring',
'flake8-docstrings',
'flake8-type-checking',
]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v0.910'
hooks:
- id: mypy
additional_dependencies: [types-dateparser]
9 changes: 9 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM python:3.10.0rc1-alpine

RUN apk add build-base

RUN pip install httpx dateparser

COPY main.py /main.py

ENTRYPOINT ["python", "main.py"]
160 changes: 160 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
[![release](https://img.shields.io/github/release/sondrelg/container-retention-policy.svg)](https://github.com/sondrelg/container-retention-policy/releases/latest)
[![coverage](https://codecov.io/gh/snok/drf-openapi-tester/branch/master/graph/badge.svg)](https://codecov.io/gh/sondrelg/container-retention-policy)

# 📘 GHCR Container Retention Policy

A Github Action for deleting old image versions from the Github container registry.

Storage isn't free and registries can often get bloated with unused images. Having a retention policy to prevent clutter
makes sense in most cases.

Supports both organizational and personal accounts.

# Content

- [Usage](#usage)
- [Examples](#examples)
- [Parameters](#parameters)
- [Contributing](#contributing)

# Usage

To use the action, simply add it to your Github workflow, like this:

```yaml
- uses: sondrelg/[email protected]
with:
image-names: dev, web, test
cut-off: two hours ago UTC+2
timestamp-to-use: updated_at
account-type: org
org-name: google
token: ${{ secrets.PAT }}
```
You could run this as
a [scheduled event](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#schedule), or as a part
of an existing workflow, but for the sake of inspiration, it might also make sense for you to trigger it with a:
- [workflow_dispatch](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#workflow_dispatch): trigger it manually in the Github repo UI when needed
- [workflow_run](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#workflow_run): have it run as clean-up after another key workflow completes
- or triggering it with a
webhook ([repository_dispatch](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#repository_dispatch))
# Examples
For an organization, a full example might look something like this:
```yaml
name: Delete old container images

on:
schedule:
- cron: '0 0 * * *' # every day at midnight

jobs:
delete-old-container-images:
- name: Delete 'dev' containers older than a week
uses: sondrelg/[email protected]
with:
image-names: app1/dev, app2/dev
cut-off: A week ago UTC
account-type: org
org-name: my-org
token: ${{ secrets.PAT }}

- name: Delete 'test' containers older than a month
uses: sondrelg/[email protected]
with:
image-names: app1/test, app2/test
cut-off: One month ago UTC
account-type: org
org-name: my-org
token: ${{ secrets.PAT }}
```
While for a personal account, something like this might do:
```yaml
name: Delete old container images

on:
schedule:
- cron: '0 0 0 * *' # the first day of the month

jobs:
delete-old-container-images:
- name: Delete old images
uses: sondrelg/[email protected]
with:
image-names: dev
cut-off: One month ago UTC
account-type: personal
token: ${{ secrets.PAT }}
```
# Parameters
## image-names
* **Required**: `Yes`
* **Example**: `image-names: image1,image2,image3`

The names of the container images you want to delete old versions for. Takes one or several container image names as a
comma separated list.

## cut-off

* **Required**: `Yes`
* **Example**: `cut-off: 1 week ago UTC`

The timezone-aware datetime you want to delete container versions that are older than.

We use [dateparser](https://dateparser.readthedocs.io/en/latest/) to parse the cut-off specified. This means you should
be able to specify your cut-off in relative human readable terms like `Two hours ago UTC`, or by using a normal
timestamp.

The parsed datetime **must** contain a timezone.

## timestamp-to-use

* **Required**: `Yes`
* **Example**: `timestamp-to-use: created_at`
* **Default**: `updated_at`
* **Valid choices**: `updated_at` or `created_at`

Which timestamp to use when comparing the cut-off to the container version.

Must be `created_at` or `updated_at`. The timestamp to use determines how we filter container versions.

## account-type

* **Required**: `Yes`
* **Example**: `account-type: personal`
* **Valid choices**: `org` or `personal`

The account type of the account running the action. The account type determines which API endpoints to use in the Github
API.

## org-name

* **Required**: `Only if account type is org`
* **Example**: `org-name: google`

The name of your organization.

## token

* **Required**: `Yes`
* **Example**: `token: ${{ secrets.PAT }}`

For the token, you need to pass a [personal access token](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token)
with access to the container registry. Specifically, you need to grant
it the following scopes:

- `read:packages`, and
- `delete:packages`

# Contributing

Please do 👏
37 changes: 37 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: 'Container Retention Policy'
description: 'Lets you create a retention policy for GHCR hosted container images'
branding:
icon: "book"
color: "blue"
inputs:
account-type:
description: "The type of account. Can be either 'org' or 'personal'."
required: true
org-name:
description: "The name of the organization. Only required if the account type is 'personal'."
default: ''
required: false
image-names:
description: 'Image name to delete. Supports passing several names as a comma-separated list.'
required: true
timestamp-to-use:
description: 'Whether to use updated_at or created_at timestamps. Defaults to updated_at.'
required: true
default: 'updated_at'
cut-off:
description: "The cut-off for which to delete images older than. For example '2 days ago UTC'. Timezone is required."
required: true
token:
description: 'Personal access token with read and delete scopes.'
required: true

runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.account-type }}
- ${{ inputs.org-name }}
- ${{ inputs.image-names }}
- ${{ inputs.timestamp-to-use }}
- ${{ inputs.cut-off }}
- ${{ inputs.token }}
Loading

0 comments on commit e0ee104

Please sign in to comment.