Compare commits

..

No commits in common. "main" and "v0.1.2" have entirely different histories.
main ... v0.1.2

53 changed files with 2542 additions and 2185 deletions

View File

@ -1,10 +0,0 @@
CLI
Codecov
Kaussow
PyPI
YAML
xoxys
SELinux
dateparser
Autostop
entrypoint

407
.drone.jsonnet Normal file
View File

@ -0,0 +1,407 @@
local PythonVersion(pyversion='3.5') = {
name: 'python' + std.strReplace(pyversion, '.', '') + '-pytest',
image: 'python:' + pyversion,
environment: {
PY_COLORS: 1,
SETUPTOOLS_SCM_PRETEND_VERSION: '${DRONE_TAG##v}',
},
commands: [
'pip install pipenv -qq',
'pipenv --bare install --dev --keep-outdated',
'pipenv run pytest dockertidy --cov=dockertidy --no-cov-on-fail',
'pip install -qq .',
'docker-tidy --help',
'docker-tidy --version',
],
depends_on: [
'clone',
],
};
local PipelineLint = {
kind: 'pipeline',
name: 'lint',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'flake8',
image: 'python:3.8',
environment: {
PY_COLORS: 1,
},
commands: [
'pip install pipenv -qq',
'pipenv --bare install --dev --keep-outdated',
'pipenv run flake8 ./dockertidy',
],
},
],
trigger: {
ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineDeps = {
kind: 'pipeline',
name: 'dependencies',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'pipenv',
image: 'python:3.8',
environment: {
PY_COLORS: 1,
},
commands: [
'pip install pipenv -qq',
'pipenv --bare install --keep-outdated',
# temp disabled
# 'pipenv check -i 37752',
'pipenv --bare install --dev --keep-outdated',
'pipenv run pipenv-setup check',
],
},
],
depends_on: [
'lint',
],
trigger: {
ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineTest = {
kind: 'pipeline',
name: 'test',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
PythonVersion(pyversion='3.5'),
PythonVersion(pyversion='3.6'),
PythonVersion(pyversion='3.7'),
PythonVersion(pyversion='3.8'),
{
name: 'codecov',
image: 'python:3.8',
environment: {
PY_COLORS: 1,
CODECOV_TOKEN: { from_secret: 'codecov_token' },
},
commands: [
'pip install codecov -qq',
'codecov --required -X gcov',
],
depends_on: [
'python35-pytest',
'python36-pytest',
'python37-pytest',
'python38-pytest',
],
},
],
depends_on: [
'dependencies',
],
trigger: {
ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineSecurity = {
kind: 'pipeline',
name: 'security',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'bandit',
image: 'python:3.8',
environment: {
PY_COLORS: 1,
},
commands: [
'pip install pipenv -qq',
'pipenv --bare install --dev --keep-outdated',
'pipenv run bandit -r ./dockertidy -x ./dockertidy/test',
],
},
],
depends_on: [
'test',
],
trigger: {
ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineBuildPackage = {
kind: 'pipeline',
name: 'build-package',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'build',
image: 'python:3.8',
environment: {
SETUPTOOLS_SCM_PRETEND_VERSION: '${DRONE_TAG##v}',
},
commands: [
'python setup.py sdist bdist_wheel',
],
},
{
name: 'checksum',
image: 'alpine',
commands: [
'cd dist/ && sha256sum * > ../sha256sum.txt',
],
},
{
name: 'publish-github',
image: 'plugins/github-release',
settings: {
overwrite: true,
api_key: { from_secret: 'github_token' },
files: ['dist/*', 'sha256sum.txt'],
title: '${DRONE_TAG}',
note: 'CHANGELOG.md',
},
when: {
ref: ['refs/tags/**'],
},
},
{
name: 'publish-pypi',
image: 'plugins/pypi',
settings: {
username: { from_secret: 'pypi_username' },
password: { from_secret: 'pypi_password' },
repository: 'https://upload.pypi.org/legacy/',
skip_build: true,
},
when: {
ref: ['refs/tags/**'],
},
},
],
depends_on: [
'security',
],
trigger: {
ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineBuildContainer(arch='amd64') = {
kind: 'pipeline',
name: 'build-container-' + arch,
platform: {
os: 'linux',
arch: arch,
},
steps: [
{
name: 'build',
image: 'python:3.8',
commands: [
'python setup.py bdist_wheel',
],
environment: {
SETUPTOOLS_SCM_PRETEND_VERSION: '${DRONE_TAG##v}',
},
},
{
name: 'dryrun',
image: 'plugins/docker:18-linux-' + arch,
settings: {
dry_run: true,
dockerfile: 'Dockerfile',
repo: 'xoxys/${DRONE_REPO_NAME}',
username: { from_secret: 'docker_username' },
password: { from_secret: 'docker_password' },
},
when: {
ref: ['refs/pull/**'],
},
},
{
name: 'publish',
image: 'plugins/docker:18-linux-' + arch,
settings: {
auto_tag: true,
auto_tag_suffix: arch,
dockerfile: 'Dockerfile',
repo: 'xoxys/${DRONE_REPO_NAME}',
username: { from_secret: 'docker_username' },
password: { from_secret: 'docker_password' },
},
when: {
ref: ['refs/heads/master', 'refs/tags/**'],
},
},
],
depends_on: [
'security',
],
trigger: {
ref: ['refs/heads/master', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineDocs = {
kind: 'pipeline',
name: 'docs',
platform: {
os: 'linux',
arch: 'amd64',
},
concurrency: {
limit: 1,
},
steps: [
{
name: 'assets',
image: 'byrnedo/alpine-curl',
commands: [
'mkdir -p docs/themes/hugo-geekdoc/',
'curl -L https://github.com/xoxys/hugo-geekdoc/releases/latest/download/hugo-geekdoc.tar.gz | tar -xz -C docs/themes/hugo-geekdoc/ --strip-components=1',
],
},
{
name: 'test',
image: 'klakegg/hugo:0.59.1-ext-alpine',
commands: [
'cd docs/ && hugo-official',
],
},
{
name: 'freeze',
image: 'appleboy/drone-ssh:1.5.5',
settings: {
host: { from_secret: 'ssh_host' },
key: { from_secret: 'ssh_key' },
script: [
'cp -R /var/www/virtual/geeklab/html/docker-tidy.geekdocs.de/ /var/www/virtual/geeklab/html/dockertidy_freeze/',
'ln -sfn /var/www/virtual/geeklab/html/dockertidy_freeze /var/www/virtual/geeklab/docker-tidy.geekdocs.de',
],
username: { from_secret: 'ssh_username' },
},
},
{
name: 'publish',
image: 'appleboy/drone-scp',
settings: {
host: { from_secret: 'ssh_host' },
key: { from_secret: 'ssh_key' },
rm: true,
source: 'docs/public/*',
strip_components: 2,
target: '/var/www/virtual/geeklab/html/docker-tidy.geekdocs.de/',
username: { from_secret: 'ssh_username' },
},
},
{
name: 'cleanup',
image: 'appleboy/drone-ssh:1.5.5',
settings: {
host: { from_secret: 'ssh_host' },
key: { from_secret: 'ssh_key' },
script: [
'ln -sfn /var/www/virtual/geeklab/html/docker-tidy.geekdocs.de /var/www/virtual/geeklab/docker-tidy.geekdocs.de',
'rm -rf /var/www/virtual/geeklab/html/dockertidy_freeze/',
],
username: { from_secret: 'ssh_username' },
},
},
],
depends_on: [
'build-package',
'build-container-amd64',
'build-container-arm64',
'build-container-arm',
],
trigger: {
ref: ['refs/heads/master', 'refs/tags/**'],
},
};
local PipelineNotifications = {
kind: 'pipeline',
name: 'notifications',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
image: 'plugins/manifest',
name: 'manifest',
settings: {
ignore_missing: true,
auto_tag: true,
username: { from_secret: 'docker_username' },
password: { from_secret: 'docker_password' },
spec: 'manifest.tmpl',
},
},
{
name: 'readme',
image: 'sheogorath/readme-to-dockerhub',
environment: {
DOCKERHUB_USERNAME: { from_secret: 'docker_username' },
DOCKERHUB_PASSWORD: { from_secret: 'docker_password' },
DOCKERHUB_REPO_PREFIX: 'xoxys',
DOCKERHUB_REPO_NAME: '${DRONE_REPO_NAME}',
README_PATH: 'README.md',
SHORT_DESCRIPTION: 'docker-tidy - Keep docker hosts tidy',
},
},
{
name: 'matrix',
image: 'plugins/matrix',
settings: {
homeserver: { from_secret: 'matrix_homeserver' },
roomid: { from_secret: 'matrix_roomid' },
template: 'Status: **{{ build.status }}**<br/> Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.link }}) ({{ build.branch }}) by {{ build.author }}<br/> Message: {{ build.message }}',
username: { from_secret: 'matrix_username' },
password: { from_secret: 'matrix_password' },
},
when: {
status: ['success', 'failure'],
},
},
],
depends_on: [
'docs',
],
trigger: {
ref: ['refs/heads/master', 'refs/tags/**'],
status: ['success', 'failure'],
},
};
[
PipelineLint,
PipelineDeps,
PipelineTest,
PipelineSecurity,
PipelineBuildPackage,
PipelineBuildContainer(arch='amd64'),
PipelineBuildContainer(arch='arm64'),
PipelineBuildContainer(arch='arm'),
PipelineDocs,
PipelineNotifications,
]

534
.drone.yml Normal file
View File

@ -0,0 +1,534 @@
---
kind: pipeline
name: lint
platform:
os: linux
arch: amd64
steps:
- name: flake8
image: python:3.8
commands:
- pip install pipenv -qq
- pipenv --bare install --dev --keep-outdated
- pipenv run flake8 ./dockertidy
environment:
PY_COLORS: 1
trigger:
ref:
- refs/heads/master
- refs/tags/**
- refs/pull/**
---
kind: pipeline
name: dependencies
platform:
os: linux
arch: amd64
steps:
- name: pipenv
image: python:3.8
commands:
- pip install pipenv -qq
- pipenv --bare install --keep-outdated
- pipenv --bare install --dev --keep-outdated
- pipenv run pipenv-setup check
environment:
PY_COLORS: 1
trigger:
ref:
- refs/heads/master
- refs/tags/**
- refs/pull/**
depends_on:
- lint
---
kind: pipeline
name: test
platform:
os: linux
arch: amd64
steps:
- name: python35-pytest
image: python:3.5
commands:
- pip install pipenv -qq
- pipenv --bare install --dev --keep-outdated
- pipenv run pytest dockertidy --cov=dockertidy --no-cov-on-fail
- pip install -qq .
- docker-tidy --help
- docker-tidy --version
environment:
PY_COLORS: 1
SETUPTOOLS_SCM_PRETEND_VERSION: ${DRONE_TAG##v}
depends_on:
- clone
- name: python36-pytest
image: python:3.6
commands:
- pip install pipenv -qq
- pipenv --bare install --dev --keep-outdated
- pipenv run pytest dockertidy --cov=dockertidy --no-cov-on-fail
- pip install -qq .
- docker-tidy --help
- docker-tidy --version
environment:
PY_COLORS: 1
SETUPTOOLS_SCM_PRETEND_VERSION: ${DRONE_TAG##v}
depends_on:
- clone
- name: python37-pytest
image: python:3.7
commands:
- pip install pipenv -qq
- pipenv --bare install --dev --keep-outdated
- pipenv run pytest dockertidy --cov=dockertidy --no-cov-on-fail
- pip install -qq .
- docker-tidy --help
- docker-tidy --version
environment:
PY_COLORS: 1
SETUPTOOLS_SCM_PRETEND_VERSION: ${DRONE_TAG##v}
depends_on:
- clone
- name: python38-pytest
image: python:3.8
commands:
- pip install pipenv -qq
- pipenv --bare install --dev --keep-outdated
- pipenv run pytest dockertidy --cov=dockertidy --no-cov-on-fail
- pip install -qq .
- docker-tidy --help
- docker-tidy --version
environment:
PY_COLORS: 1
SETUPTOOLS_SCM_PRETEND_VERSION: ${DRONE_TAG##v}
depends_on:
- clone
- name: codecov
image: python:3.8
commands:
- pip install codecov -qq
- codecov --required -X gcov
environment:
CODECOV_TOKEN:
from_secret: codecov_token
PY_COLORS: 1
depends_on:
- python35-pytest
- python36-pytest
- python37-pytest
- python38-pytest
trigger:
ref:
- refs/heads/master
- refs/tags/**
- refs/pull/**
depends_on:
- dependencies
---
kind: pipeline
name: security
platform:
os: linux
arch: amd64
steps:
- name: bandit
image: python:3.8
commands:
- pip install pipenv -qq
- pipenv --bare install --dev --keep-outdated
- pipenv run bandit -r ./dockertidy -x ./dockertidy/test
environment:
PY_COLORS: 1
trigger:
ref:
- refs/heads/master
- refs/tags/**
- refs/pull/**
depends_on:
- test
---
kind: pipeline
name: build-package
platform:
os: linux
arch: amd64
steps:
- name: build
image: python:3.8
commands:
- python setup.py sdist bdist_wheel
environment:
SETUPTOOLS_SCM_PRETEND_VERSION: ${DRONE_TAG##v}
- name: checksum
image: alpine
commands:
- cd dist/ && sha256sum * > ../sha256sum.txt
- name: publish-github
image: plugins/github-release
settings:
api_key:
from_secret: github_token
files:
- dist/*
- sha256sum.txt
note: CHANGELOG.md
overwrite: true
title: ${DRONE_TAG}
when:
ref:
- refs/tags/**
- name: publish-pypi
image: plugins/pypi
settings:
password:
from_secret: pypi_password
repository: https://upload.pypi.org/legacy/
skip_build: true
username:
from_secret: pypi_username
when:
ref:
- refs/tags/**
trigger:
ref:
- refs/heads/master
- refs/tags/**
- refs/pull/**
depends_on:
- security
---
kind: pipeline
name: build-container-amd64
platform:
os: linux
arch: amd64
steps:
- name: build
image: python:3.8
commands:
- python setup.py bdist_wheel
environment:
SETUPTOOLS_SCM_PRETEND_VERSION: ${DRONE_TAG##v}
- name: dryrun
image: plugins/docker:18-linux-amd64
settings:
dockerfile: Dockerfile
dry_run: true
password:
from_secret: docker_password
repo: xoxys/${DRONE_REPO_NAME}
username:
from_secret: docker_username
when:
ref:
- refs/pull/**
- name: publish
image: plugins/docker:18-linux-amd64
settings:
auto_tag: true
auto_tag_suffix: amd64
dockerfile: Dockerfile
password:
from_secret: docker_password
repo: xoxys/${DRONE_REPO_NAME}
username:
from_secret: docker_username
when:
ref:
- refs/heads/master
- refs/tags/**
trigger:
ref:
- refs/heads/master
- refs/tags/**
- refs/pull/**
depends_on:
- security
---
kind: pipeline
name: build-container-arm64
platform:
os: linux
arch: arm64
steps:
- name: build
image: python:3.8
commands:
- python setup.py bdist_wheel
environment:
SETUPTOOLS_SCM_PRETEND_VERSION: ${DRONE_TAG##v}
- name: dryrun
image: plugins/docker:18-linux-arm64
settings:
dockerfile: Dockerfile
dry_run: true
password:
from_secret: docker_password
repo: xoxys/${DRONE_REPO_NAME}
username:
from_secret: docker_username
when:
ref:
- refs/pull/**
- name: publish
image: plugins/docker:18-linux-arm64
settings:
auto_tag: true
auto_tag_suffix: arm64
dockerfile: Dockerfile
password:
from_secret: docker_password
repo: xoxys/${DRONE_REPO_NAME}
username:
from_secret: docker_username
when:
ref:
- refs/heads/master
- refs/tags/**
trigger:
ref:
- refs/heads/master
- refs/tags/**
- refs/pull/**
depends_on:
- security
---
kind: pipeline
name: build-container-arm
platform:
os: linux
arch: arm
steps:
- name: build
image: python:3.8
commands:
- python setup.py bdist_wheel
environment:
SETUPTOOLS_SCM_PRETEND_VERSION: ${DRONE_TAG##v}
- name: dryrun
image: plugins/docker:18-linux-arm
settings:
dockerfile: Dockerfile
dry_run: true
password:
from_secret: docker_password
repo: xoxys/${DRONE_REPO_NAME}
username:
from_secret: docker_username
when:
ref:
- refs/pull/**
- name: publish
image: plugins/docker:18-linux-arm
settings:
auto_tag: true
auto_tag_suffix: arm
dockerfile: Dockerfile
password:
from_secret: docker_password
repo: xoxys/${DRONE_REPO_NAME}
username:
from_secret: docker_username
when:
ref:
- refs/heads/master
- refs/tags/**
trigger:
ref:
- refs/heads/master
- refs/tags/**
- refs/pull/**
depends_on:
- security
---
kind: pipeline
name: docs
platform:
os: linux
arch: amd64
concurrency:
limit: 1
steps:
- name: assets
image: byrnedo/alpine-curl
commands:
- mkdir -p docs/themes/hugo-geekdoc/
- curl -L https://github.com/xoxys/hugo-geekdoc/releases/latest/download/hugo-geekdoc.tar.gz | tar -xz -C docs/themes/hugo-geekdoc/ --strip-components=1
- name: test
image: klakegg/hugo:0.59.1-ext-alpine
commands:
- cd docs/ && hugo-official
- name: freeze
image: appleboy/drone-ssh:1.5.5
settings:
host:
from_secret: ssh_host
key:
from_secret: ssh_key
script:
- cp -R /var/www/virtual/geeklab/html/docker-tidy.geekdocs.de/ /var/www/virtual/geeklab/html/dockertidy_freeze/
- ln -sfn /var/www/virtual/geeklab/html/dockertidy_freeze /var/www/virtual/geeklab/docker-tidy.geekdocs.de
username:
from_secret: ssh_username
- name: publish
image: appleboy/drone-scp
settings:
host:
from_secret: ssh_host
key:
from_secret: ssh_key
rm: true
source: docs/public/*
strip_components: 2
target: /var/www/virtual/geeklab/html/docker-tidy.geekdocs.de/
username:
from_secret: ssh_username
- name: cleanup
image: appleboy/drone-ssh:1.5.5
settings:
host:
from_secret: ssh_host
key:
from_secret: ssh_key
script:
- ln -sfn /var/www/virtual/geeklab/html/docker-tidy.geekdocs.de /var/www/virtual/geeklab/docker-tidy.geekdocs.de
- rm -rf /var/www/virtual/geeklab/html/dockertidy_freeze/
username:
from_secret: ssh_username
trigger:
ref:
- refs/heads/master
- refs/tags/**
depends_on:
- build-package
- build-container-amd64
- build-container-arm64
- build-container-arm
---
kind: pipeline
name: notifications
platform:
os: linux
arch: amd64
steps:
- name: manifest
image: plugins/manifest
settings:
auto_tag: true
ignore_missing: true
password:
from_secret: docker_password
spec: manifest.tmpl
username:
from_secret: docker_username
- name: readme
image: sheogorath/readme-to-dockerhub
environment:
DOCKERHUB_PASSWORD:
from_secret: docker_password
DOCKERHUB_REPO_NAME: ${DRONE_REPO_NAME}
DOCKERHUB_REPO_PREFIX: xoxys
DOCKERHUB_USERNAME:
from_secret: docker_username
README_PATH: README.md
SHORT_DESCRIPTION: docker-tidy - Keep docker hosts tidy
- name: matrix
image: plugins/matrix
settings:
homeserver:
from_secret: matrix_homeserver
password:
from_secret: matrix_password
roomid:
from_secret: matrix_roomid
template: "Status: **{{ build.status }}**<br/> Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.link }}) ({{ build.branch }}) by {{ build.author }}<br/> Message: {{ build.message }}"
username:
from_secret: matrix_username
when:
status:
- success
- failure
trigger:
ref:
- refs/heads/master
- refs/tags/**
status:
- success
- failure
depends_on:
- docs
---
kind: signature
hmac: 1a06f37400cd549c9b7f95b9395f0ba5c5bbb8f7069e8635b3761322d0fba07c
...

18
.flake8 Normal file
View File

@ -0,0 +1,18 @@
[flake8]
ignore = D103, D107, W503
max-line-length = 99
inline-quotes = double
exclude =
.git
.tox
__pycache__
build
dist
test
*.pyc
*.egg-info
.cache
.eggs
env*
application-import-names = dockertidy
format = ${cyan}%(path)s:%(row)d:%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s

22
.github/settings.yml vendored
View File

@ -1,6 +1,7 @@
---
repository:
name: docker-tidy
description: Keep docker hosts tidy
description: Keep docker hosts tidy.
homepage: https://docker-tidy.geekdocs.de
topics: automation, python, docker
@ -8,9 +9,9 @@ repository:
has_issues: true
has_projects: false
has_wiki: false
has_downloads: true
has_downloads: false
default_branch: main
default_branch: master
allow_squash_merge: true
allow_merge_commit: true
@ -46,17 +47,14 @@ labels:
description: This will not be worked on
branches:
- name: main
- name: master
protection:
required_pull_request_reviews: null
required_status_checks:
strict: false
strict: true
contexts:
- ci/woodpecker/pr/lint
- ci/woodpecker/pr/test
- ci/woodpecker/pr/build-package
- ci/woodpecker/pr/build-container
- ci/woodpecker/pr/docs
enforce_admins: false
required_linear_history: true
- continuous-integration/drone/pr
enforce_admins: null
restrictions: null
...

3
.gitignore vendored
View File

@ -106,9 +106,6 @@ pip-wheel-metadata
docs/themes/
docs/public/
resources/_gen/
.hugo_build.lock
# Misc
.dockertidy*
CHANGELOG.md
.ruff_cache

View File

@ -1,47 +0,0 @@
---
version: "1.1"
versioning:
update-major: []
update-minor: [feat]
update-patch: [fix, perf, refactor, chore, test, ci, docs]
tag:
pattern: "v%d.%d.%d"
release-notes:
sections:
- name: Features
commit-types: [feat]
section-type: commits
- name: Bug Fixes
commit-types: [fix]
section-type: commits
- name: Performance Improvements
commit-types: [perf]
section-type: commits
- name: Code Refactoring
commit-types: [refactor]
section-type: commits
- name: Others
commit-types: [chore]
section-type: commits
- name: Testing
commit-types: [test]
section-type: commits
- name: CI Pipeline
commit-types: [ci]
section-type: commits
- name: Documentation
commit-types: [docs]
section-type: commits
- name: Breaking Changes
section-type: breaking-changes
commit-message:
footer:
issue:
key: issue
add-value-prefix: "#"
issue:
regex: "#?[0-9]+"

View File

@ -1 +0,0 @@
https://hub.docker.com/r/thegeeklab/*

View File

@ -1,6 +0,0 @@
---
default: True
MD013: False
MD041: False
MD004:
style: dash

View File

@ -1,84 +0,0 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: build
image: docker.io/library/python:3.13
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry build
- name: security-build
image: quay.io/thegeeklab/wp-docker-buildx:5
depends_on: [build]
settings:
containerfile: Containerfile.multiarch
output: type=oci,dest=oci/${CI_REPO_NAME},tar=false
repo: ${CI_REPO}
registry_config:
from_secret: DOCKER_REGISTRY_CONFIG_PULL
- name: security-scan
image: docker.io/aquasec/trivy
depends_on: [security-build]
commands:
- trivy -v
- trivy image --input oci/${CI_REPO_NAME}
environment:
TRIVY_EXIT_CODE: "1"
TRIVY_IGNORE_UNFIXED: "true"
TRIVY_NO_PROGRESS: "true"
TRIVY_SEVERITY: HIGH,CRITICAL
TRIVY_TIMEOUT: 1m
TRIVY_DB_REPOSITORY: docker.io/aquasec/trivy-db:2
- name: publish-dockerhub
image: quay.io/thegeeklab/wp-docker-buildx:5
depends_on: [security-scan]
settings:
auto_tag: true
containerfile: Containerfile.multiarch
password:
from_secret: docker_password
platforms:
- linux/amd64
- linux/arm64
provenance: false
repo: ${CI_REPO}
username:
from_secret: docker_username
when:
- event: [tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
- name: publish-quay
image: quay.io/thegeeklab/wp-docker-buildx:5
depends_on: security-scan
settings:
auto_tag: true
containerfile: Containerfile.multiarch
password:
from_secret: quay_password
platforms:
- linux/amd64
- linux/arm64
provenance: false
registry: quay.io
repo: quay.io/${CI_REPO}
username:
from_secret: quay_username
when:
- event: [tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
depends_on:
- lint
- test

View File

@ -1,56 +0,0 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: build
image: docker.io/library/python:3.13
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry build
- name: checksum
image: quay.io/thegeeklab/alpine-tools
commands:
- cd dist/ && sha256sum * > ../sha256sum.txt
- name: changelog
image: quay.io/thegeeklab/git-sv
commands:
- git sv current-version
- git sv release-notes -t ${CI_COMMIT_TAG:-next} -o CHANGELOG.md
- cat CHANGELOG.md
- name: publish-github
image: docker.io/plugins/github-release
settings:
api_key:
from_secret: github_token
files:
- dist/*
- sha256sum.txt
note: CHANGELOG.md
overwrite: true
title: ${CI_COMMIT_TAG}
when:
- event: [tag]
- name: publish-pypi
image: docker.io/library/python:3.13
environment:
POETRY_HTTP_BASIC_PYPI_PASSWORD:
from_secret: pypi_password
POETRY_HTTP_BASIC_PYPI_USERNAME:
from_secret: pypi_username
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry publish -n
when:
- event: [tag]
depends_on:
- lint
- test

View File

@ -1,101 +0,0 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: assets
image: quay.io/thegeeklab/alpine-tools
commands:
- make doc
- name: markdownlint
image: quay.io/thegeeklab/markdownlint-cli
depends_on: [assets]
commands:
- markdownlint 'README.md' 'CONTRIBUTING.md'
- name: spellcheck
image: quay.io/thegeeklab/alpine-tools
depends_on: [assets]
commands:
- spellchecker --files 'docs/**/*.md' 'README.md' 'CONTRIBUTING.md' -d .dictionary -p spell indefinite-article syntax-urls
environment:
FORCE_COLOR: "true"
- name: link-validation
image: docker.io/lycheeverse/lychee
depends_on: [assets]
commands:
- lychee --no-progress --format detailed docs/content README.md
- name: build
image: quay.io/thegeeklab/hugo:0.136.5
depends_on: [link-validation]
commands:
- hugo --panicOnWarning -s docs/
- name: beautify
image: quay.io/thegeeklab/alpine-tools
depends_on: [build]
commands:
- html-beautify -r -f 'docs/public/**/*.html'
- name: publish
image: quay.io/thegeeklab/wp-s3-action
depends_on: [beautify]
settings:
access_key:
from_secret: s3_access_key
bucket: geekdocs
delete: true
endpoint:
from_secret: s3_endpoint
path_style: true
secret_key:
from_secret: s3_secret_access_key
source: docs/public/
strip_prefix: docs/public/
target: /${CI_REPO_NAME}
when:
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
status: [success, failure]
- name: pushrm-dockerhub
image: docker.io/chko/docker-pushrm:1
depends_on: [publish]
environment:
DOCKER_PASS:
from_secret: docker_password
DOCKER_USER:
from_secret: docker_username
PUSHRM_FILE: README.md
PUSHRM_SHORT: Keep docker hosts tidy
PUSHRM_TARGET: ${CI_REPO}
when:
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
status: [success]
- name: pushrm-quay
image: docker.io/chko/docker-pushrm:1
depends_on: [publish]
environment:
APIKEY__QUAY_IO:
from_secret: quay_token
PUSHRM_FILE: README.md
PUSHRM_TARGET: quay.io/${CI_REPO}
when:
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
status: [success]
depends_on:
- build-package
- build-container

View File

@ -1,27 +0,0 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: check-format
image: docker.io/library/python:3.13
depends_on: []
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry install
- poetry run ruff format --check --diff ./${CI_REPO_NAME//-/}
environment:
PY_COLORS: "1"
- name: check-coding
image: docker.io/library/python:3.13
depends_on: []
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry install
- poetry run ruff check ./${CI_REPO_NAME//-/}
environment:
PY_COLORS: "1"

View File

@ -1,26 +0,0 @@
---
when:
- event: [tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
runs_on: [success, failure]
steps:
- name: matrix
image: quay.io/thegeeklab/wp-matrix
settings:
homeserver:
from_secret: matrix_homeserver
room_id:
from_secret: matrix_room_id
user_id:
from_secret: matrix_user_id
access_token:
from_secret: matrix_access_token
when:
- status: [success, failure]
depends_on:
- docs

View File

@ -1,35 +0,0 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
variables:
- &pytest_base
depends_on: []
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry install
- poetry run pytest --cov-append
- poetry version
- poetry run ${CI_REPO_NAME} --help
environment:
PY_COLORS: "1"
steps:
- name: python-313
image: docker.io/library/python:3.13
<<: *pytest_base
- name: python-312
image: docker.io/library/python:3.12
<<: *pytest_base
- name: python-311
image: docker.io/library/python:3.11
<<: *pytest_base
- name: python-310
image: docker.io/library/python:3.10
<<: *pytest_base

2
CHANGELOG.md Normal file
View File

@ -0,0 +1,2 @@
* INTERNAL
* maintenance and refactoring release, no changes

View File

@ -1,31 +0,0 @@
# Contributing
## Security
If you think you have found a **security issue**, please do not mention it in this repository.
Instead, send an email to `security@thegeeklab.de` with as many details as possible so it can be handled confidential.
## Bug Reports and Feature Requests
If you have found a **bug** or have a **feature request** please use the search first in case a similar issue already exists.
If not, please create an issue in this repository
## Code
If you would like to fix a bug or implement a feature, please fork the repository and create a Pull Request.
Before you start any Pull Request, it is recommended that you create an issue to discuss first if you have any
doubts about requirement or implementation. That way you can be sure that the maintainer(s) agree on what to change and how,
and you can hopefully get a quick merge afterwards.
Pull Requests can only be merged once all status checks are green.
## Do not force push to your Pull Request branch
Please do not force push to your Pull Requests branch after you have created your Pull Request, as doing so makes it harder for us to review your work.
Pull Requests will always be squashed by us when we merge your work. Commit as many times as you need in your Pull Request branch.
## Re-requesting a review
Please do not ping your reviewer(s) by mentioning them in a new comment. Instead, use the re-request review functionality.
Read more about this in the [GitHub docs, Re-requesting a review](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request#re-requesting-a-review).

View File

@ -1,26 +0,0 @@
FROM python:3.13-alpine@sha256:fcbcbbecdeae71d3b77445d9144d1914df55110f825ab62b04a66c7c33c09373
LABEL maintainer="Robert Kaussow <mail@thegeeklab.de>"
LABEL org.opencontainers.image.authors="Robert Kaussow <mail@thegeeklab.de>"
LABEL org.opencontainers.image.title="docker-tidy"
LABEL org.opencontainers.image.url="https://docker-tidy.geekdocs.de/"
LABEL org.opencontainers.image.source="https://github.com/thegeeklab/docker-tidy"
LABEL org.opencontainers.image.documentation="https://docker-tidy.geekdocs.de/"
ENV PY_COLORS=1
ENV TZ=UTC
ADD dist/docker_tidy-*.whl /
RUN apk --update add --virtual .build-deps build-base libffi-dev openssl-dev && \
pip install --upgrade --no-cache-dir pip && \
pip install --no-cache-dir $(find / -name "docker_tidy-*.whl") && \
apk del .build-deps && \
rm -f docker_tidy-*.whl && \
rm -rf /var/cache/apk/* && \
rm -rf /root/.cache/ && \
rm -rf /tmp/*
USER root
CMD []
ENTRYPOINT ["/usr/local/bin/docker-tidy", "gc"]

26
Dockerfile Normal file
View File

@ -0,0 +1,26 @@
FROM python:3.8-alpine
LABEL maintainer="Robert Kaussow <mail@geeklabor.de>" \
org.label-schema.name="docker-tidy" \
org.label-schema.vcs-url="https://github.com/xoxys/docker-tidy" \
org.label-schema.vendor="Robert Kaussow" \
org.label-schema.schema-version="1.0"
ENV PY_COLORS=1
ENV TZ=UTC
ADD dist/docker_tidy-*.whl /
RUN \
apk --update add --virtual .build-deps gcc g++ && \
pip install --upgrade --no-cache-dir pip && \
pip install --no-cache-dir docker_tidy-*.whl && \
apk del .build-deps && \
rm -f docker_tidy-*.whl && \
rm -rf /var/cache/apk/* && \
rm -rf /root/.cache/ && \
rm -rf /tmp/*
USER root
CMD []
ENTRYPOINT ["/usr/local/bin/docker-tidy", "gc"]

View File

@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022 Robert Kaussow <mail@thegeeklab.de>
Copyright 2020 Robert Kaussow <mail@geeklabor.de>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -1,20 +0,0 @@
# renovate: datasource=github-releases depName=thegeeklab/hugo-geekdoc
THEME_VERSION := v1.2.1
THEME := hugo-geekdoc
BASEDIR := docs
THEMEDIR := $(BASEDIR)/themes
.PHONY: all
all: doc
.PHONY: doc
doc: doc-assets
.PHONY: doc-assets
doc-assets:
mkdir -p $(THEMEDIR)/$(THEME)/ ; \
curl -sSL "https://github.com/thegeeklab/$(THEME)/releases/download/${THEME_VERSION}/$(THEME).tar.gz" | tar -xz -C $(THEMEDIR)/$(THEME)/ --strip-components=1
.PHONY: clean
clean:
rm -rf $(THEMEDIR)

51
Pipfile Normal file
View File

@ -0,0 +1,51 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
pipenv-setup = "*"
pydocstyle = "<4.0.0"
flake8 = "*"
flake8-colors = "*"
flake8-blind-except = "*"
flake8-builtins = "*"
flake8-docstrings = "<=3.0.0"
flake8-isort = "*"
flake8-logging-format = "*"
flake8-polyfill = "*"
flake8-quotes = "*"
flake8-pep3101 = "*"
flake8-eradicate = {version = "*",markers = "python_version>='3.6'"}
pep8-naming = "*"
pytest = "*"
pytest-mock = "*"
pytest-cov = "*"
bandit = "*"
docker-tidy = {editable = true,path = "."}
autopep8 = "*"
yapf = "*"
pathlib2 = "*"
[packages]
zipp = "<2.0.0"
importlib-metadata = {version = "*",markers = "python_version<'3.8'"}
certifi = "*"
chardet = "*"
docker = "*"
docker-pycreds = "*"
idna = "*"
ipaddress = "*"
python-dateutil = "*"
requests = "*"
appdirs = "*"
colorama = "*"
anyconfig = "*"
pathspec = "*"
python-json-logger = "*"
jsonschema = "*"
environs = "*"
nested-lookup = "*"
"ruamel.yaml" = "*"
websocket-client = "*"
dateparser = "*"

1060
Pipfile.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,21 @@
# docker-tidy
Keep docker hosts tidy
[![Build Status](https://ci.thegeeklab.de/api/badges/thegeeklab/docker-tidy/status.svg)](https://ci.thegeeklab.de/repos/thegeeklab/docker-tidy)
[![Docker Hub](https://img.shields.io/badge/docker-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/thegeeklab/docker-tidy)
[![Build Status](https://img.shields.io/drone/build/xoxys/docker-tidy?logo=drone)](https://cloud.drone.io/xoxys/docker-tidy)
[![Docker Hub](https://img.shields.io/badge/docker-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/xoxys/docker-tidy)
[![Python Version](https://img.shields.io/pypi/pyversions/docker-tidy.svg)](https://pypi.org/project/docker-tidy/)
[![PyPI Status](https://img.shields.io/pypi/status/docker-tidy.svg)](https://pypi.org/project/docker-tidy/)
[![PyPI Release](https://img.shields.io/pypi/v/docker-tidy.svg)](https://pypi.org/project/docker-tidy/)
[![GitHub contributors](https://img.shields.io/github/contributors/thegeeklab/docker-tidy)](https://github.com/thegeeklab/docker-tidy/graphs/contributors)
[![Source: GitHub](https://img.shields.io/badge/source-github-blue.svg?logo=github&logoColor=white)](https://github.com/thegeeklab/docker-tidy)
[![License: Apache-2.0](https://img.shields.io/github/license/thegeeklab/docker-tidy)](https://github.com/thegeeklab/docker-tidy/blob/main/LICENSE)
[![PyPi Status](https://img.shields.io/pypi/status/docker-tidy.svg)](https://pypi.org/project/docker-tidy/)
[![PyPi Release](https://img.shields.io/pypi/v/docker-tidy.svg)](https://pypi.org/project/docker-tidy/)
[![Codecov](https://img.shields.io/codecov/c/github/xoxys/docker-tidy)](https://codecov.io/gh/xoxys/docker-tidy)
[![License: MIT](https://img.shields.io/github/license/xoxys/docker-tidy)](LICENSE)
This project is a fork of [Yelp/docker-custodian](https://github.com/Yelp/docker-custodian). Keep docker hosts tidy.
You can find the full documentation at [https://docker-tidy.geekdocs.de](https://docker-tidy.geekdocs.de/).
## Contributors
Special thanks to all [contributors](https://github.com/thegeeklab/docker-tidy/graphs/contributors). If you would like to contribute,
please see the [instructions](https://github.com/thegeeklab/docker-tidy/blob/main/CONTRIBUTING.md).
## License
This project is licensed under the Apache-2.0 License - see the [LICENSE](https://github.com/thegeeklab/docker-tidy/blob/main/LICENSE) file for details.
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
## Maintainers and Contributors
[Robert Kaussow](https://github.com/xoxys)

7
bin/docker-tidy Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env python
import sys
import dockertidy.__main__
sys.exit(dockertidy.__main__.main())

View File

@ -1,21 +0,0 @@
codecov:
require_ci_to_pass: true
coverage:
status:
project:
default:
target: auto
threshold: 5%
branches:
- main
if_ci_failed: error
informational: false
only_pulls: false
patch:
default:
target: auto
threshold: 5%
branches:
- main
if_ci_failed: error
only_pulls: false

View File

@ -6,9 +6,9 @@ import docker
import docker.errors
import requests.exceptions
from dockertidy.config import SingleConfig
from dockertidy.logger import SingleLog
from dockertidy.parser import timedelta
from dockertidy.Config import SingleConfig
from dockertidy.Logger import SingleLog
from dockertidy.Parser import timedelta
class AutoStop:
@ -45,7 +45,9 @@ class AutoStop:
) or (not prefix and self._has_been_running_since(container, max_run_time)):
self.logger.info(
"Stopping container {id} {name}: running since {started}".format(
id=container["Id"][:16], name=name, started=container["State"]["StartedAt"]
id=container["Id"][:16],
name=name,
started=container["State"]["StartedAt"]
)
)
@ -56,11 +58,12 @@ class AutoStop:
try:
client.stop(cid)
except requests.exceptions.Timeout as e:
self.logger.warning(f"Failed to stop container {cid}: {e!s}")
self.logger.warn("Failed to stop container {id}: {msg}".format(id=cid, msg=str(e)))
except docker.errors.APIError as e:
self.logger.warning(f"Error stopping {cid}: {e!s}")
self.logger.warn("Error stopping {id}: {msg}".format(id=cid, msg=str(e)))
def _build_container_matcher(self, prefixes):
def matcher(name):
return any(name.startswith(prefix) for prefix in prefixes)
@ -86,4 +89,4 @@ class AutoStop:
self.stop_containers()
if not config["stop"]["max_run_time"]:
self.logger.warning("Skipped, no arguments given")
self.logger.warn("Skipped, no arguments given")

View File

@ -3,13 +3,13 @@
import argparse
import dockertidy.exception
import dockertidy.Exception
from dockertidy import __version__
from dockertidy.autostop import AutoStop
from dockertidy.config import SingleConfig
from dockertidy.garbage_collector import GarbageCollector
from dockertidy.logger import SingleLog
from dockertidy.parser import timedelta_validator
from dockertidy.Autostop import AutoStop
from dockertidy.Config import SingleConfig
from dockertidy.GarbageCollector import GarbageCollector
from dockertidy.Logger import SingleLog
from dockertidy.Parser import timedelta_validator
class DockerTidy:
@ -36,7 +36,7 @@ class DockerTidy:
action="store_true",
default=None,
dest="dry_run",
help="only log actions, don't stop anything",
help="only log actions, don't stop anything"
)
parser.add_argument(
"-t",
@ -44,7 +44,7 @@ class DockerTidy:
type=int,
dest="http_timeout",
metavar="HTTP_TIMEOUT",
help="HTTP timeout in seconds for making docker API calls",
help="HTTP timeout in seconds for making docker API calls"
)
parser.add_argument(
"-v", dest="logging.level", action="append_const", const=-1, help="increase log level"
@ -52,7 +52,9 @@ class DockerTidy:
parser.add_argument(
"-q", dest="logging.level", action="append_const", const=1, help="decrease log level"
)
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
parser.add_argument(
"--version", action="version", version="%(prog)s {}".format(__version__)
)
subparsers = parser.add_subparsers(dest="command", help="sub-command help")
subparsers.required = True
@ -64,7 +66,7 @@ class DockerTidy:
dest="gc.max_container_age",
metavar="MAX_CONTAINER_AGE",
help="maximum age for a container, containers older than this age "
"will be removed (dateparser value)",
"will be removed (dateparser value)"
)
parser_gc.add_argument(
"--max-image-age",
@ -72,13 +74,13 @@ class DockerTidy:
dest="gc.max_image_age",
metavar="MAX_IMAGE_AGE",
help="maxium age for an image, images older than this age will be "
"removed (dateparser value)",
"removed (dateparser value)"
)
parser_gc.add_argument(
"--dangling-volumes",
action="store_true",
dest="gc.dangling_volumes",
help="dangling volumes will be removed",
help="dangling volumes will be removed"
)
parser_gc.add_argument(
"--exclude-image",
@ -86,7 +88,7 @@ class DockerTidy:
type=str,
dest="gc.exclude_images",
metavar="EXCLUDE_IMAGE",
help="never remove images with this tag",
help="never remove images with this tag"
)
parser_gc.add_argument(
"--exclude-container-label",
@ -94,7 +96,8 @@ class DockerTidy:
type=str,
dest="gc.exclude_container_labels",
metavar="EXCLUDE_CONTAINER_LABEL",
help="never remove containers with this label key or label key=value",
help="never remove containers with this label key "
"or label key=value"
)
parser_stop = subparsers.add_parser(
@ -105,7 +108,7 @@ class DockerTidy:
type=timedelta_validator,
dest="stop.max_run_time",
metavar="MAX_RUN_TIME",
help="maximum time a container is allows to run (dateparser value)",
help="maximum time a container is allows to run (dateparser value)"
)
parser_stop.add_argument(
"--prefix",
@ -113,7 +116,7 @@ class DockerTidy:
type=str,
dest="stop.prefix",
metavar="PREFIX",
help="only stop containers which match one of the prefix",
help="only stop containers which match one of the prefix"
)
return parser.parse_args().__dict__
@ -121,16 +124,16 @@ class DockerTidy:
def _get_config(self):
try:
config = SingleConfig(args=self.args)
except dockertidy.exception.ConfigError as e:
except dockertidy.Exception.ConfigError as e:
self.log.sysexit_with_message(e)
try:
self.log.set_level(config.config["logging"]["level"])
except ValueError as e:
self.log.sysexit_with_message(f"Can not set log level.\n{e!s}")
self.log.sysexit_with_message("Can not set log level.\n{}".format(str(e)))
self.logger.info(f"Using config file {config.config_file}")
self.logger.debug(f"Config dump: {config.config}")
self.logger.info("Using config file {}".format(config.config_file))
self.logger.debug("Config dump: {}".format(config.config))
return config
@ -140,7 +143,3 @@ class DockerTidy:
self.gc.run()
elif self.config.config["command"] == "stop":
self.stop.run()
def main():
DockerTidy()

View File

@ -10,18 +10,18 @@ import ruamel.yaml
from appdirs import AppDirs
from jsonschema._utils import format_as_index
import dockertidy.exception
import dockertidy.parser
from dockertidy.parser import env
from dockertidy.utils import Singleton, dict_intersect
import dockertidy.Exception
import dockertidy.Parser
from dockertidy.Parser import env
from dockertidy.Utils import Singleton
from dockertidy.Utils import dict_intersect
config_dir = AppDirs("docker-tidy").user_config_dir
default_config_file = os.path.join(config_dir, "config.yml")
class Config:
"""
Create an object with all necessary settings.
class Config():
"""Create an object with all necessary settings.
Settings are loade from multiple locations in defined order (last wins):
- default settings defined by `self._get_defaults()`
@ -36,77 +36,77 @@ class Config:
"config_file": {
"default": "",
"env": "CONFIG_FILE",
"type": environs.Env().str,
"type": environs.Env().str
},
"dry_run": {
"default": False,
"env": "DRY_RUN",
"file": True,
"type": environs.Env().bool,
"type": environs.Env().bool
},
"http_timeout": {
"default": 60,
"env": "HTTP_TIMEOUT",
"file": True,
"type": environs.Env().int,
"type": environs.Env().int
},
"logging.level": {
"default": "WARNING",
"env": "LOG_LEVEL",
"file": True,
"type": environs.Env().str,
"type": environs.Env().str
},
"logging.json": {
"default": False,
"env": "LOG_JSON",
"file": True,
"type": environs.Env().bool,
"type": environs.Env().bool
},
"gc.max_container_age": {
"default": "",
"env": "GC_MAX_CONTAINER_AGE",
"file": True,
"type": env.timedelta_validator,
"type": env.timedelta_validator
},
"gc.max_image_age": {
"default": "",
"env": "GC_MAX_IMAGE_AGE",
"file": True,
"type": env.timedelta_validator,
"type": env.timedelta_validator
},
"gc.dangling_volumes": {
"default": False,
"env": "GC_DANGLING_VOLUMES",
"file": True,
"type": environs.Env().bool,
"type": environs.Env().bool
},
"gc.exclude_images": {
"default": [],
"env": "GC_EXCLUDE_IMAGES",
"file": True,
"type": environs.Env().list,
"type": environs.Env().list
},
"gc.exclude_container_labels": {
"default": [],
"env": "GC_EXCLUDE_CONTAINER_LABELS",
"file": True,
"type": environs.Env().list,
"type": environs.Env().list
},
"stop.max_run_time": {
"default": "",
"env": "STOP_MAX_RUN_TIME",
"file": True,
"type": env.timedelta_validator,
"type": env.timedelta_validator
},
"stop.prefix": {
"default": [],
"env": "STOP_PREFIX",
"file": True,
"type": environs.Env().list,
"type": environs.Env().list
},
}
def __init__(self, args=None):
def __init__(self, args={}):
"""
Initialize a new settings class.
@ -115,9 +115,6 @@ class Config:
:returns: None
"""
if args is None:
self._args = {}
else:
self._args = args
self._schema = None
self.config_file = default_config_file
@ -162,12 +159,12 @@ class Config:
value = item["type"](envname)
normalized = self._add_dict_branch(normalized, key.split("."), value)
except environs.EnvError as e:
if f'"{envname}" not set' in str(e):
if '"{}" not set'.format(envname) in str(e):
pass
else:
raise dockertidy.exception.ConfigError(
raise dockertidy.Exception.ConfigError(
"Unable to read environment variable", str(e)
) from e
)
return normalized
@ -191,15 +188,15 @@ class Config:
source_files.append(os.path.join(os.getcwd(), ".dockertidy.yaml"))
for config in [i for i in source_files if os.path.exists(i)]:
with open(config, encoding="utf8") as stream:
with open(config, "r", encoding="utf8") as stream:
s = stream.read()
try:
normalized = ruamel.yaml.YAML(typ="safe", pure=True).load(s)
normalized = ruamel.yaml.safe_load(s)
except (ruamel.yaml.composer.ComposerError, ruamel.yaml.scanner.ScannerError) as e:
message = f"{e.context} {e.problem}"
raise dockertidy.exception.ConfigError(
f"Unable to read config file {config}", message
) from e
message = "{} {}".format(e.context, e.problem)
raise dockertidy.Exception.ConfigError(
"Unable to read config file {}".format(config), message
)
if self._validate(normalized):
anyconfig.merge(files_raw, normalized, ac_merge=anyconfig.MS_DICTS)
@ -227,26 +224,27 @@ class Config:
if not os.path.isabs(path):
base = os.path.join(os.getcwd(), path)
return os.path.abspath(os.path.expanduser(os.path.expandvars(base)))
else:
return path
def _validate(self, config):
try:
anyconfig.validate(config, self.schema, ac_schema_safe=False)
except jsonschema.exceptions.ValidationError as e:
schema = format_as_index(list(e.relative_schema_path)[:-1])
schema_error = f"Failed validating '{e.validator}' in schema {schema}\n{e.message}"
raise dockertidy.exception.ConfigError("Configuration error", schema_error) from e
schema_error = "Failed validating '{validator}' in schema{schema}\n{message}".format(
validator=e.validator,
schema=format_as_index(list(e.relative_schema_path)[:-1]),
message=e.message
)
raise dockertidy.Exception.ConfigError("Configuration error", schema_error)
return True
def _add_dict_branch(self, tree, vector, value):
key = vector[0]
tree[key] = (
value
if len(vector) == 1
else self._add_dict_branch(tree.get(key, {}), vector[1:], value)
)
tree[key] = value \
if len(vector) == 1 \
else self._add_dict_branch(tree[key] if key in tree else {}, vector[1:], value)
return tree

View File

@ -6,7 +6,7 @@ class TidyError(Exception):
"""Generic exception class for docker-tidy."""
def __init__(self, msg, original_exception=""):
super().__init__(f"{msg}\n{original_exception}")
super(TidyError, self).__init__("{msg}\n{org}".format(msg=msg, org=original_exception))
self.original_exception = original_exception

View File

@ -9,9 +9,9 @@ import docker
import docker.errors
import requests.exceptions
from dockertidy.config import SingleConfig
from dockertidy.logger import SingleLog
from dockertidy.parser import timedelta
from dockertidy.Config import SingleConfig
from dockertidy.Logger import SingleLog
from dockertidy.Parser import timedelta
class GarbageCollector:
@ -55,8 +55,7 @@ class GarbageCollector:
self.logger.info(
"Removing container {} {} {}".format(
container["Id"][:16],
container.get("Name", "").lstrip("/"),
container["State"]["FinishedAt"],
container.get("Name", "").lstrip("/"), container["State"]["FinishedAt"]
)
)
@ -74,7 +73,9 @@ class GarbageCollector:
return containers
def include_container(container):
return not self._should_exclude_container_with_labels(container)
if self._should_exclude_container_with_labels(container):
return False
return True
return filter(include_container, containers)
@ -162,6 +163,7 @@ class GarbageCollector:
self._remove_image(image_summary, timedelta(config["gc"]["max_image_age"]))
def _filter_excluded_images(self, images, exclude_set):
def include_image(image_summary):
image_tags = image_summary.get("RepoTags")
if self._no_image_tags(image_tags):
@ -174,11 +176,12 @@ class GarbageCollector:
return filter(include_image, images)
def _filter_images_in_use(self, images, image_tags_in_use):
def get_tag_set(image_summary):
image_tags = image_summary.get("RepoTags")
if self._no_image_tags(image_tags):
# The repr of the image Id used by client.containers()
return {"{id}:latest".format(id=image_summary["Id"][:12])}
return set(["{id}:latest".format(id=image_summary["Id"][:12])])
return set(image_tags)
def image_not_in_use(image_summary):
@ -187,6 +190,7 @@ class GarbageCollector:
return filter(image_not_in_use, images)
def _filter_images_in_use_by_id(self, images, image_ids_in_use):
def image_not_in_use(image_summary):
return image_summary["Id"] not in image_ids_in_use
@ -206,7 +210,9 @@ class GarbageCollector:
if not image or not self._is_image_old(image, min_date):
return
self.logger.info(f"Removing image {self._format_image(image, image_summary)}")
self.logger.info(
"Removing image {name}".format(name=self._format_image(image, image_summary))
)
if config["dry_run"]:
return
@ -245,13 +251,22 @@ class GarbageCollector:
try:
return func(**kwargs)
except requests.exceptions.Timeout as e:
params = ",".join("%s=%s" % item for item in kwargs.items()) # noqa:UP031
self.logger.warning(f"Failed to call {func.__name__} {params} {e!s}")
params = ",".join("%s=%s" % item for item in kwargs.items()) # noqa
self.logger.warn(
"Failed to call {name} {params} {msg}".format(
name=func.__name__, params=params, msg=str(e)
)
)
except docker.errors.APIError as e:
params = ",".join("%s=%s" % item for item in kwargs.items()) # noqa:UP031
self.logger.warning(f"Error calling {func.__name__} {params} {e!s}")
params = ",".join("%s=%s" % item for item in kwargs.items()) # noqa
self.logger.warn(
"Error calling {name} {params} {msg}".format(
name=func.__name__, params=params, msg=str(e)
)
)
def _format_image(self, image, image_summary):
def get_tags():
tags = image_summary.get("RepoTags")
if not tags or tags == ["<none>:<none>"]:
@ -262,11 +277,12 @@ class GarbageCollector:
def _build_exclude_set(self):
config = self.config.config
exclude_set = set(config["gc"]["exclude_images"])
def is_image_tag(line):
return line and not line.startswith("#")
return set(config["gc"]["exclude_images"])
return exclude_set
def _format_exclude_labels(self):
config = self.config.config
@ -275,7 +291,10 @@ class GarbageCollector:
for exclude_label_arg in config["gc"]["exclude_container_labels"]:
split_exclude_label = exclude_label_arg.split("=", 1)
exclude_label_key = split_exclude_label[0]
exclude_label_value = split_exclude_label[1] if len(split_exclude_label) == 2 else None
if len(split_exclude_label) == 2:
exclude_label_value = split_exclude_label[1]
else:
exclude_label_value = None
exclude_labels.append(
self.ExcludeLabel(
key=exclude_label_key,
@ -289,7 +308,7 @@ class GarbageCollector:
try:
return docker.APIClient(version="auto", timeout=config["http_timeout"])
except docker.errors.DockerException as e:
self.log.sysexit_with_message(f"Can't create docker client\n{e}")
self.log.sysexit_with_message("Can't create docker client\n{}".format(e))
def run(self):
"""Garbage collector main method."""
@ -308,8 +327,7 @@ class GarbageCollector:
self.cleanup_volumes()
if (
not config["gc"]["max_container_age"]
and not config["gc"]["max_image_age"]
not config["gc"]["max_container_age"] and not config["gc"]["max_image_age"]
and not config["gc"]["dangling_volumes"]
):
self.logger.ing("Skipped, no arguments given")
self.logger.warn("Skipped, no arguments given")

View File

@ -8,10 +8,11 @@ import sys
import colorama
from pythonjsonlogger import jsonlogger
from dockertidy.utils import Singleton, to_bool
from dockertidy.Utils import Singleton
from dockertidy.Utils import to_bool
CONSOLE_FORMAT = "{}[%(levelname)s]{} %(message)s"
JSON_FORMAT = "%(asctime)s %(levelname)s %(message)s"
JSON_FORMAT = "(asctime) (levelname) (message)"
def _should_do_markup():
@ -25,7 +26,7 @@ def _should_do_markup():
colorama.init(autoreset=True, strip=not _should_do_markup())
class LogFilter:
class LogFilter(object):
"""A custom log filter which excludes log messages above the logged level."""
def __init__(self, level):
@ -46,15 +47,15 @@ class LogFilter:
class MultilineFormatter(logging.Formatter):
"""Logging Formatter to reset color after newline characters."""
def format(self, record):
record.msg = record.msg.replace("\n", f"\n{colorama.Style.RESET_ALL}... ")
def format(self, record): # noqa
record.msg = record.msg.replace("\n", "\n{}... ".format(colorama.Style.RESET_ALL))
return logging.Formatter.format(self, record)
class MultilineJsonFormatter(jsonlogger.JsonFormatter):
"""Logging Formatter to remove newline characters."""
def format(self, record):
def format(self, record): # noqa
record.msg = record.msg.replace("\n", " ")
return jsonlogger.JsonFormatter.format(self, record)
@ -62,11 +63,11 @@ class MultilineJsonFormatter(jsonlogger.JsonFormatter):
class Log:
"""Base logging object."""
def __init__(self, level=logging.WARNING, name="dockertidy", json=False):
def __init__(self, level=logging.WARN, name="dockertidy", json=False):
self.logger = logging.getLogger(name)
self.logger.setLevel(level)
self.logger.addHandler(self._get_error_handler(json=json))
self.logger.addHandler(self._get_warning_handler(json=json))
self.logger.addHandler(self._get_warn_handler(json=json))
self.logger.addHandler(self._get_info_handler(json=json))
self.logger.addHandler(self._get_critical_handler(json=json))
self.logger.addHandler(self._get_debug_handler(json=json))
@ -87,13 +88,13 @@ class Log:
return handler
def _get_warning_handler(self, json=False):
def _get_warn_handler(self, json=False):
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.WARNING)
handler.addFilter(LogFilter(logging.WARNING))
handler.setLevel(logging.WARN)
handler.addFilter(LogFilter(logging.WARN))
handler.setFormatter(
MultilineFormatter(
self.warning(CONSOLE_FORMAT.format(colorama.Fore.YELLOW, colorama.Style.RESET_ALL))
self.warn(CONSOLE_FORMAT.format(colorama.Fore.YELLOW, colorama.Style.RESET_ALL))
)
)
@ -163,8 +164,8 @@ class Log:
"""Format error messages and return string."""
return msg
def warning(self, msg):
"""Format warning messages and return string."""
def warn(self, msg):
"""Format warn messages and return string."""
return msg
def info(self, msg):
@ -180,7 +181,7 @@ class Log:
:returns: string
"""
return f"{color}{msg}{colorama.Style.RESET_ALL}"
return "{}{}{}".format(color, msg, colorama.Style.RESET_ALL)
def sysexit(self, code=1):
"""Exit running program with given exit code."""

View File

@ -10,8 +10,7 @@ env = environs.Env()
def timedelta_validator(value):
"""
Return the dateparser string for a time in the past.
"""Return the dateparser string for a time in the past.
:param value: a string containing a time format supported by
mod:`dateparser`
@ -20,14 +19,13 @@ def timedelta_validator(value):
return None
if not dateparser.parse(value):
raise ArgumentTypeError(f"'{value}' is not a valid timedelta string")
raise ArgumentTypeError("'{}' is not a valid timedelta string".format(value))
return value
def timedelta(value, dt_format=None):
"""
Return the :class:`datetime.datetime.DateTime` for a time in the past.
"""Return the :class:`datetime.datetime.DateTime` for a time in the past.
:param value: a string containing a time format supported by
mod:`dateparser`
@ -36,7 +34,10 @@ def timedelta(value, dt_format=None):
return None
timedelta = dateparser.parse(
value, settings={"TO_TIMEZONE": "UTC", "RETURN_AS_TIMEZONE_AWARE": True}
value, settings={
"TO_TIMEZONE": "UTC",
"RETURN_AS_TIMEZONE_AWARE": True
}
)
if dt_format:
@ -51,4 +52,4 @@ def timedelta_parser(value):
timedelta_validator(value)
return value
except ArgumentTypeError as e:
raise environs.EnvError(e) from e
raise environs.EnvError(e)

27
dockertidy/Utils.py Normal file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env python3
"""Global utility methods and classes."""
from distutils.util import strtobool
def to_bool(string):
return bool(strtobool(str(string)))
def dict_intersect(d1, d2):
return {
k: dict_intersect(d1[k], d2[k]) if isinstance(d1[k], dict) else d1[k]
for k in d1.keys() & d2.keys()
}
class Singleton(type):
"""Singleton metaclass."""
_instances = {}
def __call__(cls, *args, **kwargs): # noqa
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]

View File

@ -1,3 +1,15 @@
#!/usr/bin/env python3
"""Default package."""
__version__ = "0.0.0"
try:
from importlib import metadata
except ImportError: # for Python<3.8
import importlib_metadata as metadata
__author__ = "Robert Kaussow"
__project__ = "docker-tidy"
__license__ = "Apache-2.0"
__maintainer__ = "Robert Kaussow"
__email__ = "mail@geeklabor.de"
__url__ = "https://github.com/xoxys/docker-tidy"
__version__ = metadata.version("docker-tidy")

12
dockertidy/__main__.py Normal file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env python3
"""Main program."""
from dockertidy.Cli import DockerTidy
def main():
DockerTidy()
if __name__ == "__main__":
main()

View File

@ -3,7 +3,7 @@
import docker
import pytest
from dockertidy import autostop
from dockertidy import Autostop
pytest_plugins = [
"dockertidy.test.fixtures.fixtures",
@ -11,28 +11,28 @@ pytest_plugins = [
@pytest.fixture
def autostop_fixture(mocker):
def autostop(mocker):
mocker.patch.object(
autostop.AutoStop,
Autostop.AutoStop,
"_get_docker_client",
return_value=mocker.create_autospec(docker.APIClient)
)
stop = autostop.AutoStop()
stop = Autostop.AutoStop()
return stop
def test_stop_container(autostop_fixture, mocker):
def test_stop_container(autostop, mocker):
client = mocker.create_autospec(docker.APIClient)
cid = "asdb"
autostop_fixture._stop_container(client, cid)
autostop._stop_container(client, cid)
client.stop.assert_called_once_with(cid)
def test_build_container_matcher(autostop_fixture, mocker):
def test_build_container_matcher(autostop, mocker):
prefixes = ["one_", "two_"]
matcher = autostop_fixture._build_container_matcher(prefixes)
matcher = autostop._build_container_matcher(prefixes)
assert matcher("one_container")
assert matcher("two_container")
@ -40,9 +40,9 @@ def test_build_container_matcher(autostop_fixture, mocker):
assert not matcher("one")
def test_has_been_running_since_true(autostop_fixture, container, later_time):
assert autostop_fixture._has_been_running_since(container, later_time)
def test_has_been_running_since_true(autostop, container, later_time):
assert autostop._has_been_running_since(container, later_time)
def test_has_been_running_since_false(autostop_fixture, container, earlier_time):
assert not autostop_fixture._has_been_running_since(container, earlier_time)
def test_has_been_running_since_false(autostop, container, earlier_time):
assert not autostop._has_been_running_since(container, earlier_time)

View File

@ -4,7 +4,7 @@ import docker
import pytest
import requests
from dockertidy import garbage_collector
from dockertidy import GarbageCollector
pytest_plugins = [
"dockertidy.test.fixtures.fixtures",
@ -14,12 +14,12 @@ pytest_plugins = [
@pytest.fixture
def gc(mocker):
mocker.patch.object(
garbage_collector.GarbageCollector,
GarbageCollector.GarbageCollector,
"_get_docker_client",
return_value=mocker.create_autospec(docker.APIClient)
)
gc = garbage_collector.GarbageCollector()
gc = GarbageCollector.GarbageCollector()
return gc
@ -358,15 +358,13 @@ def test_api_call_with_timeout(mocker, gc):
gc._api_call(func, image=image)
func.assert_called_once_with(image="abcd")
mock_log.warning.assert_called_once_with("Failed to call remove_image " + "image=abcd msg")
mock_log.warn.assert_called_once_with("Failed to call remove_image " + "image=abcd msg")
def test_api_call_with_api_error(mocker, gc):
func = mocker.Mock(
side_effect=docker.errors.APIError(
"Ooops",
mocker.Mock(status_code=409, reason="Conflict", url="dummy"),
explanation="failed"
"Ooops", mocker.Mock(status_code=409, reason="Conflict"), explanation="failed"
),
__name__="remove_image"
)
@ -376,9 +374,9 @@ def test_api_call_with_api_error(mocker, gc):
gc._api_call(func, image=image)
func.assert_called_once_with(image="abcd")
mock_log.warning.assert_called_once_with(
mock_log.warn.assert_called_once_with(
"Error calling remove_image image=abcd "
'409 Client Error for dummy: Conflict ("failed")'
'409 Client Error: Conflict ("failed")'
)

View File

@ -1,48 +0,0 @@
#!/usr/bin/env python3
"""Global utility methods and classes."""
def strtobool(value):
"""Convert a string representation of truth to true or false."""
_map = {
"y": True,
"yes": True,
"t": True,
"true": True,
"on": True,
"1": True,
"n": False,
"no": False,
"f": False,
"false": False,
"off": False,
"0": False,
}
try:
return _map[str(value).lower()]
except KeyError as err:
raise ValueError(f'"{value}" is not a valid bool value') from err
def to_bool(string):
return bool(strtobool(str(string)))
def dict_intersect(d1, d2):
return {
k: dict_intersect(d1[k], d2[k]) if isinstance(d1[k], dict) else d1[k]
for k in d1.keys() & d2.keys()
}
class Singleton(type):
"""Singleton metaclass."""
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]

View File

@ -18,19 +18,14 @@ markup:
startLevel: 1
params:
description: >
Keep docker hosts tidy. docker-tidy is a tool to remove outdated and unused docker resources based on filters.
images:
- "socialmedia2.png"
geekdocMenuBundle: true
geekdocToC: 3
geekdocRepo: https://github.com/thegeeklab/docker-tidy
geekdocEditPath: edit/main/docs
geekdocRepo: https://github.com/xoxys/docker-tidy
geekdocEditPath: edit/master/docs/content
geekdocDateFormat: "Jan 2, 2006"
geekdocSearch: true
geekdocLegalNotice: https://thegeeklab.de/legal-notice/#contact-information
geekdocPrivacyPolicy: https://thegeeklab.de/legal-notice/#privacy-policy
geekdocLegalNotice: https://geeklabor.de/legal-notice/#impressum
geekdocPrivacyPolicy: https://geeklabor.de/legal-notice/#datenschutzerkl%C3%A4rung

View File

@ -2,13 +2,12 @@
title: Documentation
---
[![Build Status](https://ci.thegeeklab.de/api/badges/thegeeklab/docker-tidy/status.svg)](https://ci.thegeeklab.de/repos/thegeeklab/docker-tidy)
[![Docker Hub](https://img.shields.io/badge/docker-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/thegeeklab/docker-tidy)
[![Build Status](https://img.shields.io/drone/build/xoxys/docker-tidy?logo=drone)](https://cloud.drone.io/xoxys/docker-tidy)
[![Docker Hub](https://img.shields.io/badge/docker-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/xoxys/docker-tidy)
[![Python Version](https://img.shields.io/pypi/pyversions/docker-tidy.svg)](https://pypi.org/project/docker-tidy/)
[![PyPI Status](https://img.shields.io/pypi/status/docker-tidy.svg)](https://pypi.org/project/docker-tidy/)
[![PyPI Release](https://img.shields.io/pypi/v/docker-tidy.svg)](https://pypi.org/project/docker-tidy/)
[![GitHub contributors](https://img.shields.io/github/contributors/thegeeklab/docker-tidy)](https://github.com/thegeeklab/docker-tidy/graphs/contributors)
[![Source: GitHub](https://img.shields.io/badge/source-github-blue.svg?logo=github&logoColor=white)](https://github.com/thegeeklab/docker-tidy)
[![License: Apache-2.0](https://img.shields.io/github/license/thegeeklab/docker-tidy)](https://github.com/thegeeklab/docker-tidy/blob/main/LICENSE)
[![PyPi Status](https://img.shields.io/pypi/status/docker-tidy.svg)](https://pypi.org/project/docker-tidy/)
[![PyPi Release](https://img.shields.io/pypi/v/docker-tidy.svg)](https://pypi.org/project/docker-tidy/)
[![Codecov](https://img.shields.io/codecov/c/github/xoxys/docker-tidy)](https://codecov.io/gh/xoxys/docker-tidy)
[![License: MIT](https://img.shields.io/github/license/xoxys/docker-tidy)](LICENSE)
This project is a fork of [Yelp/docker-custodian](https://github.com/Yelp/docker-custodian). Keep docker hosts tidy.

View File

@ -2,25 +2,21 @@
title: Configuration
---
<!-- spellchecker-disable -->
{{< toc >}}
<!-- spellchecker-enable -->
_docker-tidy_ comes with default settings which should be sufficient for most users to start, but you can adjust most settings to your needs.
*docker-tidy* comes with default settings which should be sufficient for most users to start, but you can adjust most settings to your needs.
Changes can be made on different locations which will be processed in the following order (last wins):
- default configuration (build-in)
- global configuration file (path depends on your operating system)
- directory based configuration file (.dockertidy.yml|.dockertidy.yaml|.dockertidy in current working directory)
- environment variables
- CLI options
* default config (build-in)
* global config file (path depends on your operating system)
* folder-based config file (.dockertidy.yml|.dockertidy.yaml|.dockertidy in current working dir)
* environment variables
* cli options
## Default settings
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<!-- spellchecker-disable -->
{{< highlight YAML "linenos=table" >}}
---
# don't do anything
@ -44,15 +40,11 @@ stop:
max_run_time:
prefix: []
{{< /highlight >}}
<!-- spellchecker-enable -->
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- markdownlint-enable -->
## Environment Variables
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<!-- spellchecker-disable -->
{{< highlight Shell "linenos=table" >}}
TIDY_CONFIG_FILE=
TIDY_DRY_RUN=False
@ -70,17 +62,13 @@ TIDY_STOP_MAX_RUN_TIME=
# comma-separated list
TIDY_STOP_PREFIX=
{{< /highlight >}}
<!-- spellchecker-enable -->
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- markdownlint-enable -->
## CLI options
You can get all available CLI options by running `docker-tidy --help`:
You can get all available cli options by running `docker-tidy --help`:
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<!-- spellchecker-disable -->
{{< highlight Shell "linenos=table" >}}
$ docker-tidy --help
usage: docker-tidy [-h] [--dry-run] [-t HTTP_TIMEOUT] [-v] [-q] [--version]
@ -102,6 +90,4 @@ optional arguments:
-q decrease log level
--version show program's version number and exit
{{< /highlight >}}
<!-- spellchecker-enable -->
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- markdownlint-enable -->

View File

@ -2,54 +2,41 @@
title: Setup
---
<!-- prettier-ignore-start -->
<!-- spellchecker-disable -->
{{< toc >}}
<!-- spellchecker-enable -->
<!-- prettier-ignore-end -->
## Pip
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<!-- spellchecker-disable -->
{{< highlight Shell "linenos=table" >}}
# From PyPI as unprivileged user
# From PyPI as unprivilegd user
$ pip install docker-tidy --user
# .. or as root
$ sudo pip install docker-tidy
# From Wheel file
$ pip install https://github.com/thegeeklab/docker-tidy/releases/download/v0.1.0/docker_tidy-0.1.0-py2.py3-none-any.whl
$ pip install https://github.com/xoxys/docker-tidy/releases/download/v0.1.0/docker_tidy-0.1.0-py2.py3-none-any.whl
{{< /highlight >}}
<!-- spellchecker-enable -->
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- markdownlint-enable -->
## Docker
The default entrypoint is set to the `gc` sub-command and you have to overwrite it
if you want to use other sub-commands like `stop`.
The default entrypoint is set to the `gc` subcommand and you have to overwrite it
if you want to use other subcommands like `stop`.
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<!-- spellchecker-disable -->
{{< highlight Shell "linenos=table" >}}
docker run \
-e TIDY_GC_MAX_CONTAINER_AGE="3 days ago" \
-e TIDY_GC_MAX_IMAGE_AGE="5 days ago" \
-v /var/run/docker.sock:/var/run/docker.sock \
thegeeklab/docker-tidy
xoxys/docker-tidy
{{< /highlight >}}
<!-- spellchecker-enable -->
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- markdownlint-enable -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
{{< hint type=note >}}
Keep in mind, that you have to pass SELinux labels (:Z or :z) to your mount option if you are working on SELinux enabled systems.
{{< hint info >}}
**Info**\
Keep in mind, that you have to pass selinux labels (:Z or :z) to your mount option if you are working on selinux enabled systems.
{{< /hint >}}
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- markdownlint-enable -->

View File

@ -2,19 +2,20 @@
title: Usage
---
<!-- spellchecker-disable -->
{{< toc >}}
<!-- spellchecker-enable -->
## Garbage Collector
Remove old docker containers and docker images.
`docker-tidy gc` will remove stopped containers and unused images that are older than \"max age\". Running containers, and images which are used by a container are never removed.
`docker-tidy gc` will remove stopped containers and unused images that are older
than \"max age\". Running containers, and images which are used by a
container are never removed.
Maximum age can be specified with any format supported by [dateparser](https://dateparser.readthedocs.io/en/latest/index.html#features).
Maximum age can be specificied with any format supported by
[dateparser](https://dateparser.readthedocs.io/en/latest/index.html#features).
**Example:**
__Example:__
```Shell
docker-tidy gc --max-container-age "3 days ago" --max-image-age "30 days ago"
@ -22,7 +23,9 @@ docker-tidy gc --max-container-age "3 days ago" --max-image-age "30 days ago"
### Prevent images from being removed
`docker-tidy gc` supports an image exclude list. If you have images that you\'d like to keep around forever you can use the exclude list to prevent them from being removed.
`docker-tidy gc` supports an image exclude list. If you have images that you\'d
like to keep around forever you can use the exclude list to prevent them
from being removed.
```Shell
--exclude-image
@ -31,7 +34,9 @@ docker-tidy gc --max-container-age "3 days ago" --max-image-age "30 days ago"
### Prevent containers and associated images from being removed
`docker-tidy gc` also supports a container exclude list based on labels. If there are stopped containers that you\'d like to keep, then you can check the labels to prevent them from being removed.
`docker-tidy gc` also supports a container exclude list based on labels. If there
are stopped containers that you\'d like to keep, then you can check the
labels to prevent them from being removed.
```Shell
--exclude-container-label
@ -44,11 +49,13 @@ docker-tidy gc --max-container-age "3 days ago" --max-image-age "30 days ago"
Stop containers that have been running for too long.
`docker-tidy stop` will `docker stop` containers where the container name starts with [\--prefix]{.title-ref} and/or it has been running for longer than [\--max-run-time]{.title-ref}.
`docker-tidy stop` will `docker stop` containers where the container name starts
with [\--prefix]{.title-ref} and/or it has been running for longer than
[\--max-run-time]{.title-ref}.
If no prefix is set, **all** containers matching the `max-run-time` will be stopped!
If no prefix is set, __all__ containers matching the `max-run-time` will be stopped!
**Example:**
__Example:__
```Shell
docker-tidy stop --max-run-time "2 days ago" --prefix "projectprefix_"

View File

@ -1,10 +1,10 @@
---
more:
- name: Releases
ref: "https://github.com/thegeeklab/docker-tidy/releases"
ref: "https://github.com/xoxys/docker-tidy/releases"
external: true
icon: "gdoc_download"
icon: "download"
- name: "View Source"
ref: "https://github.com/thegeeklab/docker-tidy"
ref: "https://github.com/xoxys/docker-tidy"
external: true
icon: "gdoc_github"
icon: "github"

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

24
manifest.tmpl Normal file
View File

@ -0,0 +1,24 @@
image: xoxys/docker-tidy:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
{{/if}}
manifests:
- image: xoxys/docker-tidy:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}amd64
platform:
architecture: amd64
os: linux
- image: xoxys/docker-tidy:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}arm64
platform:
architecture: arm64
os: linux
variant: v8
- image: xoxys/docker-tidy:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}arm
platform:
architecture: arm
os: linux
variant: v7

1078
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,148 +0,0 @@
[tool.poetry]
authors = ["Robert Kaussow <mail@thegeeklab.de>"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"License :: OSI Approved :: Apache Software License",
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"Intended Audience :: System Administrators",
"Natural Language :: English",
"Operating System :: POSIX",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: System :: Systems Administration",
"Topic :: Utilities",
"Topic :: Software Development",
]
description = "Keep docker hosts tidy."
documentation = "https://docker-tidy.geekdocs.de/"
homepage = "https://docker-tidy.geekdocs.de/"
include = ["LICENSE"]
keywords = ["docker", "gc", "prune", "garbage"]
license = "Apache-2.0"
name = "docker-tidy"
packages = [{ include = "dockertidy" }]
readme = "README.md"
repository = "https://github.com/thegeeklab/docker-tidy/"
version = "0.0.0"
[tool.poetry.dependencies]
anyconfig = "0.14.0"
appdirs = "1.4.4"
certifi = "2024.8.30"
colorama = "0.4.6"
dateparser = "1.2.0"
docker = "7.1.0"
docker-pycreds = "0.4.0"
environs = "11.2.0"
idna = "3.10"
ipaddress = "1.0.23"
jsonschema = "4.23.0"
nested-lookup = "0.2.25"
pathspec = "0.12.1"
python = "^3.10.0"
python-dateutil = "2.9.0.post0"
python-json-logger = "2.0.7"
requests = "2.32.3"
"ruamel.yaml" = "0.18.6"
websocket_client = "1.8.0"
zipp = "3.21.0"
[tool.poetry.scripts]
docker-tidy = "dockertidy.cli:main"
[tool.poetry.group.dev.dependencies]
ruff = "0.7.3"
pytest = "8.3.3"
pytest-mock = "3.14.0"
pytest-cov = "6.0.0"
toml = "0.10.2"
[tool.poetry-dynamic-versioning]
enable = true
style = "semver"
vcs = "git"
[tool.pytest.ini_options]
addopts = "dockertidy --cov=dockertidy --cov-report=xml:coverage.xml --cov-report=term --no-cov-on-fail"
filterwarnings = [
"ignore::FutureWarning",
"ignore::DeprecationWarning",
"ignore:.*pep8.*:FutureWarning",
]
[tool.coverage.run]
omit = ["**/test/*"]
[build-system]
build-backend = "poetry_dynamic_versioning.backend"
requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning"]
[tool.ruff]
exclude = [
".git",
"__pycache__",
"build",
"dist",
"test",
"*.pyc",
"*.egg-info",
".cache",
".eggs",
"env*",
]
line-length = 99
indent-width = 4
[tool.ruff.lint]
# Explanation of errors
#
# D102: Missing docstring in public method
# D103: Missing docstring in public function
# D105: Missing docstring in magic method
# D107: Missing docstring in __init__
# D202: No blank lines allowed after function docstring
# D203: One blank line required before class docstring
# D212: Multi-line docstring summary should start at the first line
ignore = [
"D102",
"D103",
"D105",
"D107",
"D202",
"D203",
"D212",
"UP038",
"RUF012",
]
select = [
"D",
"E",
"F",
"Q",
"W",
"I",
"S",
"BLE",
"N",
"UP",
"B",
"A",
"C4",
"T20",
"SIM",
"RET",
"ARG",
"ERA",
"RUF",
]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
line-ending = "lf"

View File

@ -1,12 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>thegeeklab/renovate-presets"],
"packageRules": [
{
"matchManagers": ["woodpecker"],
"matchFileNames": [".woodpecker/test.yml"],
"matchPackageNames": ["docker.io/library/python"],
"enabled": false
}
]
}

30
setup.cfg Normal file
View File

@ -0,0 +1,30 @@
[metadata]
description-file = README.md
license_file = LICENSE
[bdist_wheel]
universal = 1
[isort]
default_section = THIRDPARTY
known_first_party = dockertidy
sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
force_single_line = true
line_length = 99
skip_glob = **/.env*,**/env/*,**/docs/*
[yapf]
based_on_style = google
column_limit = 99
dedent_closing_brackets = true
coalesce_brackets = true
split_before_logical_operator = true
[tool:pytest]
filterwarnings =
ignore::FutureWarning
ignore:.*collections.*:DeprecationWarning
ignore:.*pep8.*:FutureWarning
[coverage:run]
omit = **/test/*

103
setup.py Normal file
View File

@ -0,0 +1,103 @@
#!/usr/bin/env python
"""Setup script for the package."""
import io
import os
import re
from setuptools import find_packages
from setuptools import setup
PACKAGE_NAME = "dockertidy"
def get_property(prop, project):
current_dir = os.path.dirname(os.path.realpath(__file__))
result = re.search(
r'{}\s*=\s*[\'"]([^\'"]*)[\'"]'.format(prop),
open(os.path.join(current_dir, project, "__init__.py")).read(),
)
return result.group(1)
def get_readme(filename="README.md"):
this = os.path.abspath(os.path.dirname(__file__))
with io.open(os.path.join(this, filename), encoding="utf-8") as f:
long_description = f.read()
return long_description
setup(
name=get_property("__project__", PACKAGE_NAME),
use_scm_version={
"version_scheme": "python-simplified-semver",
"local_scheme": "no-local-version",
"fallback_version": "unknown",
},
description="Keep docker hosts tidy",
keywords="docker gc prune garbage",
author=get_property("__author__", PACKAGE_NAME),
author_email=get_property("__email__", PACKAGE_NAME),
url=get_property("__url__", PACKAGE_NAME),
license=get_property("__license__", PACKAGE_NAME),
long_description=get_readme(),
long_description_content_type="text/markdown",
packages=find_packages(exclude=["*.test", "test", "test.*"]),
include_package_data=True,
zip_safe=False,
python_requires=">=3.5,<4",
classifiers=[
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"License :: OSI Approved :: Apache Software License",
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"Intended Audience :: System Administrators",
"Natural Language :: English",
"Operating System :: POSIX",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Topic :: System :: Systems Administration",
"Topic :: Utilities",
"Topic :: Software Development",
],
install_requires=[
"anyconfig==0.9.10",
"appdirs==1.4.3",
"attrs==19.3.0",
"certifi==2020.4.5.1",
"chardet==3.0.4",
"colorama==0.4.3",
"dateparser==0.7.4",
"docker==4.2.0",
"docker-pycreds==0.4.0",
"environs==7.3.1",
"idna==2.9",
"importlib-metadata==1.6.0; python_version < '3.8'",
"ipaddress==1.0.23",
"jsonschema==3.2.0",
"marshmallow==3.5.1",
"nested-lookup==0.2.21",
"pathspec==0.8.0",
"pyrsistent==0.16.0",
"python-dateutil==2.8.1",
"python-dotenv==0.12.0",
"python-json-logger==0.1.11",
"pytz==2019.3",
"regex==2020.4.4",
"requests==2.23.0",
"ruamel.yaml==0.16.10",
"ruamel.yaml.clib==0.2.0; platform_python_implementation == 'CPython' and python_version < '3.9'",
"six==1.14.0",
"tzlocal==2.0.0",
"urllib3==1.25.8",
"websocket-client==0.57.0",
"zipp==1.2.0",
],
dependency_links=[],
setup_requires=["setuptools_scm"],
entry_points={"console_scripts": ["docker-tidy = dockertidy.__main__:main"]},
)