Repository: circa10a/ouroboros Branch: master Commit: fee45f2dbf6e Files: 23 Total size: 95.7 KB Directory structure: gitextract_imqw0m8b/ ├── .github/ │ ├── FUNDING.yml │ └── dependabot.yml ├── .gitignore ├── .gitlab-ci.yml ├── CHANGELOG.md ├── Dockerfile ├── Dockerfile.arm ├── Dockerfile.arm64 ├── LICENSE ├── MANIFEST.in ├── README.md ├── docker-compose.yml ├── ouroboros ├── pyouroboros/ │ ├── __init__.py │ ├── config.py │ ├── dataexporters.py │ ├── dockerclient.py │ ├── helpers.py │ ├── logger.py │ ├── notifiers.py │ └── ouroboros.py ├── requirements.txt └── setup.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ custom: https://buymeacoff.ee/ouroboros ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: github-actions directory: "/" schedule: interval: daily open-pull-requests-limit: 10 - package-ecosystem: docker directory: "/" schedule: interval: daily open-pull-requests-limit: 10 - package-ecosystem: pip directory: "/" schedule: interval: daily open-pull-requests-limit: 10 ================================================ FILE: .gitignore ================================================ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json #vscode .vscode/ # JetBrains .idea/ *.iml ================================================ FILE: .gitlab-ci.yml ================================================ stages: - test - build and push - manifests - release .common_build: &common_build image: docker:stable stage: build and push variables: DOCKER_HOST: tcp://docker:2375/ DOCKER_DRIVER: overlay2 services: - docker:dind except: - tags only: changes: - "Dockerfile*" - ouroboros - pyouroboros/* - .gitlab-ci.yml before_script: - mkdir $HOME/.docker - echo '{"experimental":"enabled"}' > $HOME/.docker/config.json - if [[ $CI_COMMIT_REF_NAME == "master" ]]; then export TAG=$(grep -i version pyouroboros/__init__.py | cut -d \ -f3 | tr -d \"); else export TAG="develop"; fi - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USER" --password-stdin - export CI_PROJECT_PATH=$(echo $CI_PROJECT_PATH | tr "[:upper:]" "[:lower:]") .common_release: &common_release <<: *common_build tags: - docker - shared only: refs: - master flake8: image: python:3.7.3-alpine stage: test cache: key: "ouroboros-cache" paths: - .cache/pip - venv/ variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" tags: - shared before_script: - python -m venv venv - source venv/bin/activate - pip install -U flake8 script: - flake8 --max-line-length 120 ouroboros *.py pyouroboros/*.py amd64: <<: *common_build tags: - docker - shared script: - docker build --pull --cache-from "$CI_PROJECT_PATH":"$TAG-amd64" -t "$CI_PROJECT_PATH":"$TAG-amd64" . - docker push "$CI_PROJECT_PATH":"$TAG-amd64" armv6: <<: *common_build tags: - docker - arm script: - docker build --pull --cache-from "$CI_PROJECT_PATH":"$TAG-arm" -t "$CI_PROJECT_PATH":"$TAG-arm" -f Dockerfile.arm . - docker push "$CI_PROJECT_PATH":"$TAG-arm" arm64v8: <<: *common_build tags: - docker - arm64 script: - docker build --pull --cache-from "$CI_PROJECT_PATH":"$TAG-arm64" -t "$CI_PROJECT_PATH":"$TAG-arm64" -f Dockerfile.arm64 . - docker push "$CI_PROJECT_PATH":"$TAG-arm64" versioned: <<: *common_build stage: manifests tags: - docker - shared script: - docker manifest create "$CI_PROJECT_PATH":"$TAG" "$CI_PROJECT_PATH":"$TAG-amd64" "$CI_PROJECT_PATH":"$TAG-arm64" "$CI_PROJECT_PATH":"$TAG-arm" - docker manifest push -p "$CI_PROJECT_PATH":"$TAG" latest: <<: *common_release stage: manifests tags: - docker script: - docker manifest create "$CI_PROJECT_PATH":latest "$CI_PROJECT_PATH":"$TAG-amd64" "$CI_PROJECT_PATH":"$TAG-arm64" "$CI_PROJECT_PATH":"$TAG-arm" - docker manifest push -p "$CI_PROJECT_PATH":latest github: <<: *common_release stage: release script: - apk add git - git remote set-url origin "https://$GITHUB_USER:$GITHUB_TOKEN@github.com/$CI_PROJECT_PATH.git" - git tag $TAG - git push --tags pypi: <<: *common_release image: python:3.7.3-alpine stage: release cache: key: "ouroboros-cache" paths: - .cache/pip - venv/ variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" tags: - shared before_script: - python -m venv venv - source venv/bin/activate - pip install -U twine script: - venv/bin/python setup.py sdist && venv/bin/python -m twine upload --skip-existing -u ${PYPI_CREDS_USR} -p ${PYPI_CREDS_PSW} dist/* ================================================ FILE: CHANGELOG.md ================================================ # Change Log ## [1.4.3](https://github.com/pyouroboros/ouroboros/tree/1.4.3) (2019-12-11) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.4.3...1.4.2) **Fixed bugs:** - Join Notifications truncated and icon returning 404 [\#325](https://github.com/pyouroboros/ouroboros/issues/325) **Other Pull Requests** - v1.4.3 Merge [\#354](https://github.com/pyouroboros/ouroboros/pull/354) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - v1.4.3 to develop [\#353](https://github.com/pyouroboros/ouroboros/pull/353) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Update requirements.txt [\#349](https://github.com/pyouroboros/ouroboros/pull/349) ([nemchik](https://github.com/nemchik)) ## [1.4.2](https://github.com/pyouroboros/ouroboros/tree/1.4.2) (2019-08-01) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.4.1...1.4.2) **Other Pull Requests** - 1.4.2 Merge [\#327](https://github.com/pyouroboros/ouroboros/pull/327) ([circa10a](https://github.com/circa10a)) - 1.4.2 to develop [\#326](https://github.com/pyouroboros/ouroboros/pull/326) ([circa10a](https://github.com/circa10a)) ## [1.4.1](https://github.com/pyouroboros/ouroboros/tree/1.4.1) (2019-06-04) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/v1.4.0...1.4.1) **Other Pull Requests** - v1.4.1 Merge [\#315](https://github.com/pyouroboros/ouroboros/pull/315) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - v1.4.1 to develop [\#314](https://github.com/pyouroboros/ouroboros/pull/314) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) ## [v1.4.0](https://github.com/pyouroboros/ouroboros/tree/v1.4.0) (2019-04-25) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.3.1...v1.4.0) **Implemented enhancements:** - Make startup notification optional [\#253](https://github.com/pyouroboros/ouroboros/issues/253) **Fixed bugs:** - Missing MANIFEST.in file causes pypi install to fail [\#284](https://github.com/pyouroboros/ouroboros/issues/284) - Healthcheck section not re-applied after container update [\#275](https://github.com/pyouroboros/ouroboros/issues/275) - docker-compose local and remote tls logger location [\#273](https://github.com/pyouroboros/ouroboros/issues/273) - Self update errors. Not deleting old self [\#262](https://github.com/pyouroboros/ouroboros/issues/262) - ouroboros sets fixed IP addresses [\#254](https://github.com/pyouroboros/ouroboros/issues/254) **Closed issues:** - Update apprise to v0.7.4 [\#266](https://github.com/pyouroboros/ouroboros/issues/266) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] - Docker TLS verify: Does it support server and client-side auth, or only server-side auth? [\#256](https://github.com/pyouroboros/ouroboros/issues/256) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] **Other Pull Requests** - v1.4.0 Merge [\#299](https://github.com/pyouroboros/ouroboros/pull/299) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Remove Watchtower Reference [\#298](https://github.com/pyouroboros/ouroboros/pull/298) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - v1.4.0 to develop [\#297](https://github.com/pyouroboros/ouroboros/pull/297) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - fixes \#284 [\#296](https://github.com/pyouroboros/ouroboros/pull/296) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Patches [\#295](https://github.com/pyouroboros/ouroboros/pull/295) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Fix healthcheck attr [\#294](https://github.com/pyouroboros/ouroboros/pull/294) ([circa10a](https://github.com/circa10a)) - Patch/catch up [\#271](https://github.com/pyouroboros/ouroboros/pull/271) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Arg skip startup notifications [\#261](https://github.com/pyouroboros/ouroboros/pull/261) ([circa10a](https://github.com/circa10a)) - Revert "add option to skip startup notifications" [\#260](https://github.com/pyouroboros/ouroboros/pull/260) ([circa10a](https://github.com/circa10a)) - add option to skip startup notifications [\#259](https://github.com/pyouroboros/ouroboros/pull/259) ([circa10a](https://github.com/circa10a)) ## [1.3.1](https://github.com/pyouroboros/ouroboros/tree/1.3.1) (2019-02-28) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.3.0...1.3.1) **Fixed bugs:** - Since 1.3.0, docker login fails [\#243](https://github.com/pyouroboros/ouroboros/issues/243) - Catch Failed self-updates [\#230](https://github.com/pyouroboros/ouroboros/issues/230) **Other Pull Requests** - v1.3.1 Merge [\#249](https://github.com/pyouroboros/ouroboros/pull/249) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - v1.3.1 to develop [\#248](https://github.com/pyouroboros/ouroboros/pull/248) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - fix name subscript for \#243 [\#247](https://github.com/pyouroboros/ouroboros/pull/247) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - fixes \#230 and \#243 [\#242](https://github.com/pyouroboros/ouroboros/pull/242) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) ## [1.3.0](https://github.com/pyouroboros/ouroboros/tree/1.3.0) (2019-02-25) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.2.1...1.3.0) **Implemented enhancements:** - Start new container in detached mode [\#222](https://github.com/pyouroboros/ouroboros/pull/222) ([nightvisi0n](https://github.com/nightvisi0n)) - Optimise dockerfile layers [\#218](https://github.com/pyouroboros/ouroboros/pull/218) ([nightvisi0n](https://github.com/nightvisi0n)) **Fixed bugs:** - Cron scheduled missed following successful runs [\#229](https://github.com/pyouroboros/ouroboros/issues/229) - Catch attribute.id error [\#226](https://github.com/pyouroboros/ouroboros/issues/226) - AttachStdout and AttachStderr are not carried over properly [\#221](https://github.com/pyouroboros/ouroboros/issues/221) - Exception when updating container started with --rm \(autoremove\) [\#219](https://github.com/pyouroboros/ouroboros/issues/219) - Issue with Swarm Mode V2 [\#216](https://github.com/pyouroboros/ouroboros/issues/216) - Fix docker swarm mode [\#227](https://github.com/pyouroboros/ouroboros/pull/227) ([mathcantin](https://github.com/mathcantin)) **Other Pull Requests** - v1.3.0 Merge [\#241](https://github.com/pyouroboros/ouroboros/pull/241) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - v1.3.0 to develop [\#240](https://github.com/pyouroboros/ouroboros/pull/240) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Catch self update apierror [\#238](https://github.com/pyouroboros/ouroboros/pull/238) ([circa10a](https://github.com/circa10a)) - Catch attribute error [\#237](https://github.com/pyouroboros/ouroboros/pull/237) ([circa10a](https://github.com/circa10a)) - Check for autoremove [\#236](https://github.com/pyouroboros/ouroboros/pull/236) ([circa10a](https://github.com/circa10a)) - Add misfire\_grace\_time for cron scheduler [\#234](https://github.com/pyouroboros/ouroboros/pull/234) ([circa10a](https://github.com/circa10a)) - Check all services by default on swarm mode [\#228](https://github.com/pyouroboros/ouroboros/pull/228) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([mathcantin](https://github.com/mathcantin)) - remove git in pypi + branch develop + version bump + maintainer\_email [\#214](https://github.com/pyouroboros/ouroboros/pull/214) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) ## [1.2.1](https://github.com/pyouroboros/ouroboros/tree/1.2.1) (2019-02-14) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.2.0...1.2.1) **Fixed bugs:** - Broken when no :tag specified [\#210](https://github.com/pyouroboros/ouroboros/issues/210) **Other Pull Requests** - v1.2.1 Merge [\#213](https://github.com/pyouroboros/ouroboros/pull/213) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - v1.2.1 to develop [\#212](https://github.com/pyouroboros/ouroboros/pull/212) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - fixes \#210 [\#211](https://github.com/pyouroboros/ouroboros/pull/211) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - version bump to 1.2.1 + develop branch + twine fix + … [\#209](https://github.com/pyouroboros/ouroboros/pull/209) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) ## [1.2.0](https://github.com/pyouroboros/ouroboros/tree/1.2.0) (2019-02-14) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.1.2...1.2.0) **Implemented enhancements:** - Move "Interval container update" messages to debug log level [\#194](https://github.com/pyouroboros/ouroboros/issues/194) - \[Feature Request\] Support for Swarm Services [\#178](https://github.com/pyouroboros/ouroboros/issues/178) - Add Warning for label\_enable not set while using labels\_only [\#202](https://github.com/pyouroboros/ouroboros/pull/202) ([larsderidder](https://github.com/larsderidder)) **Fixed bugs:** - Change depends\_on logic [\#198](https://github.com/pyouroboros/ouroboros/issues/198) - Containers relying upon network namespace of a container that gets updated breaks when the parent container is recreated [\#197](https://github.com/pyouroboros/ouroboros/issues/197) - Exception when trying to update container with complex compose networks [\#196](https://github.com/pyouroboros/ouroboros/issues/196) - Problem with network IPv4 address carry-over [\#193](https://github.com/pyouroboros/ouroboros/issues/193) - Monitor Ignored Re-Address + jenkins cleanup [\#191](https://github.com/pyouroboros/ouroboros/pull/191) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([DirtyCajunRice](https://github.com/DirtyCajunRice)) **Closed issues:** - Remove legacy --latest [\#206](https://github.com/pyouroboros/ouroboros/issues/206) [[breaking change](https://github.com/pyouroboros/ouroboros/labels/breaking%20change)] [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] - Add environment variables in Wiki [\#203](https://github.com/pyouroboros/ouroboros/issues/203) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] - Slack notifications via webhook not working [\#187](https://github.com/pyouroboros/ouroboros/issues/187) **Other Pull Requests** - v1.2.0 Merge [\#208](https://github.com/pyouroboros/ouroboros/pull/208) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - v1.2.0 to develop [\#207](https://github.com/pyouroboros/ouroboros/pull/207) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Patch/tag bug [\#205](https://github.com/pyouroboros/ouroboros/pull/205) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Patch/group 5 [\#201](https://github.com/pyouroboros/ouroboros/pull/201) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Fix bug in user defined network detection [\#200](https://github.com/pyouroboros/ouroboros/pull/200) ([nightvisi0n](https://github.com/nightvisi0n)) - Adjust apscheduler logger [\#199](https://github.com/pyouroboros/ouroboros/pull/199) ([circa10a](https://github.com/circa10a)) - Carry over network config [\#195](https://github.com/pyouroboros/ouroboros/pull/195) ([nightvisi0n](https://github.com/nightvisi0n)) - Jenkins tweaks [\#192](https://github.com/pyouroboros/ouroboros/pull/192) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Swarm + Jenkins [\#188](https://github.com/pyouroboros/ouroboros/pull/188) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) ## [1.1.2](https://github.com/pyouroboros/ouroboros/tree/1.1.2) (2019-02-02) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.1.1...1.1.2) **Fixed bugs:** - No default timezone [\#176](https://github.com/pyouroboros/ouroboros/issues/176) **Closed issues:** - cron documentation example update [\#182](https://github.com/pyouroboros/ouroboros/issues/182) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] **Other Pull Requests** - v1.1.2 Merge [\#186](https://github.com/pyouroboros/ouroboros/pull/186) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - v1.1.2 to develop [\#183](https://github.com/pyouroboros/ouroboros/pull/183) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Fix default timezone [\#177](https://github.com/pyouroboros/ouroboros/pull/177) ([circa10a](https://github.com/circa10a)) ## [1.1.1](https://github.com/pyouroboros/ouroboros/tree/1.1.1) (2019-02-01) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.1.0...1.1.1) **Implemented enhancements:** - Support for adding an identifier \(hostname?\) to notifications [\#158](https://github.com/pyouroboros/ouroboros/issues/158) - Influx config data + ocd cleanup [\#162](https://github.com/pyouroboros/ouroboros/pull/162) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - add cli arg for cron [\#157](https://github.com/pyouroboros/ouroboros/pull/157) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) **Fixed bugs:** - Ouroboros does not respect MONITOR= [\#166](https://github.com/pyouroboros/ouroboros/issues/166) - Docker TLS over TCP connections [\#154](https://github.com/pyouroboros/ouroboros/issues/154) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] - Patch/group 4 [\#169](https://github.com/pyouroboros/ouroboros/pull/169) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Recheck properly for only non lists [\#164](https://github.com/pyouroboros/ouroboros/pull/164) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - add some missing passthrough info for restart [\#163](https://github.com/pyouroboros/ouroboros/pull/163) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) **Other Pull Requests** - v1.1.1 Merge [\#173](https://github.com/pyouroboros/ouroboros/pull/173) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - v1.1.1 to develop [\#172](https://github.com/pyouroboros/ouroboros/pull/172) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Patch/group 3 [\#167](https://github.com/pyouroboros/ouroboros/pull/167) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Add hostname to the notifications [\#161](https://github.com/pyouroboros/ouroboros/pull/161) ([tlkamp](https://github.com/tlkamp)) - Patch/group 2 [\#155](https://github.com/pyouroboros/ouroboros/pull/155) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) ## [1.1.0](https://github.com/pyouroboros/ouroboros/tree/1.1.0) (2019-01-26) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.0.0...1.1.0) **Implemented enhancements:** - Notification via Telegram [\#146](https://github.com/pyouroboros/ouroboros/issues/146) - Add flag to allow a labels\_only condition [\#142](https://github.com/pyouroboros/ouroboros/issues/142) - DRY\_RUN flag [\#140](https://github.com/pyouroboros/ouroboros/issues/140) - Notification on startup [\#138](https://github.com/pyouroboros/ouroboros/issues/138) - Start/Stop containers in sequence [\#106](https://github.com/pyouroboros/ouroboros/issues/106) - Refactor/notifications with apprise [\#151](https://github.com/pyouroboros/ouroboros/pull/151) [[breaking change](https://github.com/pyouroboros/ouroboros/labels/breaking%20change)] [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] ([DirtyCajunRice](https://github.com/DirtyCajunRice)) **Fixed bugs:** - Catch invalid docker socket config [\#148](https://github.com/pyouroboros/ouroboros/issues/148) - Explicitly Define true/false [\#141](https://github.com/pyouroboros/ouroboros/issues/141) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] **Other Pull Requests** - v1.1.0 Merge [\#153](https://github.com/pyouroboros/ouroboros/pull/153) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - v1.1.0 to develop [\#152](https://github.com/pyouroboros/ouroboros/pull/152) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Patch/group 1 [\#150](https://github.com/pyouroboros/ouroboros/pull/150) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Add volume for docker socket path [\#144](https://github.com/pyouroboros/ouroboros/pull/144) ([mauvehed](https://github.com/mauvehed)) ## [1.0.0](https://github.com/pyouroboros/ouroboros/tree/1.0.0) (2019-01-23) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.6.0...1.0.0) **Implemented enhancements:** - Stop containers with alternate signal [\#107](https://github.com/pyouroboros/ouroboros/issues/107) - Docker Socket secure connections [\#105](https://github.com/pyouroboros/ouroboros/issues/105) - Selectively monitor containers with label [\#104](https://github.com/pyouroboros/ouroboros/issues/104) - Allow stop-signal label [\#133](https://github.com/pyouroboros/ouroboros/pull/133) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Docker TLS Verify option [\#132](https://github.com/pyouroboros/ouroboros/pull/132) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - add label priority feature for watch/ignore. Addresses \#104 [\#121](https://github.com/pyouroboros/ouroboros/pull/121) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) **Fixed bugs:** - Unexpected docker API causes program to quit ‘500 Server Error: Internal Server Error’ [\#130](https://github.com/pyouroboros/ouroboros/issues/130) - Error tag handling under the registry with port [\#129](https://github.com/pyouroboros/ouroboros/issues/129) - a fatal error when none tag image [\#122](https://github.com/pyouroboros/ouroboros/issues/122) - Bug/ignore logic [\#135](https://github.com/pyouroboros/ouroboros/pull/135) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Bug/registry logic [\#131](https://github.com/pyouroboros/ouroboros/pull/131) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - catch no tags in get\_running [\#124](https://github.com/pyouroboros/ouroboros/pull/124) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - fixed logic for latest vs develop, and added -f to specify file [\#119](https://github.com/pyouroboros/ouroboros/pull/119) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) **Closed issues:** - Missing docker-compose.yml from documentation [\#120](https://github.com/pyouroboros/ouroboros/issues/120) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] - Wiki usage docs reference old argument names [\#115](https://github.com/pyouroboros/ouroboros/issues/115) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] **Other Pull Requests** - v1.0.0 Merge [\#137](https://github.com/pyouroboros/ouroboros/pull/137) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - v1.0.0 to develop [\#136](https://github.com/pyouroboros/ouroboros/pull/136) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Clean old legacy files [\#134](https://github.com/pyouroboros/ouroboros/pull/134) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Cleanup/qemu logic [\#128](https://github.com/pyouroboros/ouroboros/pull/128) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - fix readme wording for monitoring remote hosts [\#126](https://github.com/pyouroboros/ouroboros/pull/126) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] ([circa10a](https://github.com/circa10a)) ## [0.6.0](https://github.com/pyouroboros/ouroboros/tree/0.6.0) (2019-01-17) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.5.0...0.6.0) **Implemented enhancements:** - Support multi-architecture Docker images [\#78](https://github.com/pyouroboros/ouroboros/issues/78) - Mail notification [\#59](https://github.com/pyouroboros/ouroboros/issues/59) - Multi architecture docker [\#110](https://github.com/pyouroboros/ouroboros/pull/110) ([circa10a](https://github.com/circa10a)) - added logo to readme [\#109](https://github.com/pyouroboros/ouroboros/pull/109) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Feature/ouroboros self\_update [\#103](https://github.com/pyouroboros/ouroboros/pull/103) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - add version cli arg [\#100](https://github.com/pyouroboros/ouroboros/pull/100) ([circa10a](https://github.com/circa10a)) - added email notifications. Addresses \#59 [\#97](https://github.com/pyouroboros/ouroboros/pull/97) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Documentation [\#96](https://github.com/pyouroboros/ouroboros/pull/96) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) **Fixed bugs:** - Ignore not working as expected [\#98](https://github.com/pyouroboros/ouroboros/issues/98) - specify for specificity! [\#114](https://github.com/pyouroboros/ouroboros/pull/114) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - manifesting failures [\#113](https://github.com/pyouroboros/ouroboros/pull/113) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - sigh. [\#112](https://github.com/pyouroboros/ouroboros/pull/112) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Multiarch/fine tuning [\#111](https://github.com/pyouroboros/ouroboros/pull/111) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - catch index error and account for shared images, x [\#102](https://github.com/pyouroboros/ouroboros/pull/102) ([circa10a](https://github.com/circa10a)) - add monitor/ignore to list sanity check. Fixes \#98 [\#99](https://github.com/pyouroboros/ouroboros/pull/99) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) **Other Pull Requests** - v0.6.0 to develop [\#118](https://github.com/pyouroboros/ouroboros/pull/118) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - v0.6.0 Merge [\#117](https://github.com/pyouroboros/ouroboros/pull/117) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - add changelog formatting and fix all labels going back to 1 [\#116](https://github.com/pyouroboros/ouroboros/pull/116) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] ([DirtyCajunRice](https://github.com/DirtyCajunRice)) ## [0.5.0](https://github.com/pyouroboros/ouroboros/tree/0.5.0) (2019-01-13) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.4.3...0.5.0) **Implemented enhancements:** - Auto discover slack/discord notifications in WEBHOOK\_URLS [\#83](https://github.com/pyouroboros/ouroboros/issues/83) - Add to schedule logic run now [\#75](https://github.com/pyouroboros/ouroboros/issues/75) - add pushover functionality. Finishes other half of \#80 [\#93](https://github.com/pyouroboros/ouroboros/pull/93) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - add keep\_alive url for healthchecks. Addresses half of \#80 [\#89](https://github.com/pyouroboros/ouroboros/pull/89) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - changed webhook json to auto-deciding + fixed RUN\_ONCE no underscore [\#86](https://github.com/pyouroboros/ouroboros/pull/86) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Refactor [\#79](https://github.com/pyouroboros/ouroboros/pull/79) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) **Fixed bugs:** - Fix log level case sensitivity [\#82](https://github.com/pyouroboros/ouroboros/issues/82) - Invalid URL 'h': No schema supplied. Perhaps you meant http://h? [\#76](https://github.com/pyouroboros/ouroboros/issues/76) - Installation via pip fails [\#73](https://github.com/pyouroboros/ouroboros/issues/73) - Added try except [\#95](https://github.com/pyouroboros/ouroboros/pull/95) ([circa10a](https://github.com/circa10a)) - Fix dockerfile [\#92](https://github.com/pyouroboros/ouroboros/pull/92) ([circa10a](https://github.com/circa10a)) - use ouroboros script in dockerfile [\#91](https://github.com/pyouroboros/ouroboros/pull/91) ([circa10a](https://github.com/circa10a)) - fix deploy script to push git tags [\#90](https://github.com/pyouroboros/ouroboros/pull/90) ([circa10a](https://github.com/circa10a)) - change pypi travis username [\#88](https://github.com/pyouroboros/ouroboros/pull/88) ([circa10a](https://github.com/circa10a)) - install flake8 for travis, run on appropriate directories [\#87](https://github.com/pyouroboros/ouroboros/pull/87) ([circa10a](https://github.com/circa10a)) - Removed old test related items, removed the need for duplicate bin sc… [\#85](https://github.com/pyouroboros/ouroboros/pull/85) ([circa10a](https://github.com/circa10a)) - change loglevel to use upper\(\) [\#84](https://github.com/pyouroboros/ouroboros/pull/84) ([circa10a](https://github.com/circa10a)) - Prometheus bind fix, org rename [\#81](https://github.com/pyouroboros/ouroboros/pull/81) ([circa10a](https://github.com/circa10a)) ## [0.4.3](https://github.com/pyouroboros/ouroboros/tree/0.4.3) (2019-01-09) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.4.2...0.4.3) **Implemented enhancements:** - grafana to metrics/prometheus endpoint [\#74](https://github.com/pyouroboros/ouroboros/issues/74) - add aarch64 docker image [\#77](https://github.com/pyouroboros/ouroboros/pull/77) ([circa10a](https://github.com/circa10a)) ## [0.4.2](https://github.com/pyouroboros/ouroboros/tree/0.4.2) (2019-01-08) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.4.1...0.4.2) **Implemented enhancements:** - Add autopep8 to the pre-merge checks [\#30](https://github.com/pyouroboros/ouroboros/issues/30) ## [0.4.1](https://github.com/pyouroboros/ouroboros/tree/0.4.1) (2018-12-30) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.4.0...0.4.1) **Implemented enhancements:** - Pre merge code quality checks [\#72](https://github.com/pyouroboros/ouroboros/pull/72) ([circa10a](https://github.com/circa10a)) ## [0.4.0](https://github.com/pyouroboros/ouroboros/tree/0.4.0) (2018-12-30) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.3.7...0.4.0) **Implemented enhancements:** - Slack notification [\#61](https://github.com/pyouroboros/ouroboros/issues/61) - Webhook notifications [\#71](https://github.com/pyouroboros/ouroboros/pull/71) ([circa10a](https://github.com/circa10a)) ## [0.3.7](https://github.com/pyouroboros/ouroboros/tree/0.3.7) (2018-12-26) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.3.6...0.3.7) **Implemented enhancements:** - Timezone Support [\#68](https://github.com/pyouroboros/ouroboros/issues/68) - Add output to log at container start [\#66](https://github.com/pyouroboros/ouroboros/issues/66) - Enable Timezone Configuration [\#69](https://github.com/pyouroboros/ouroboros/pull/69) ([circa10a](https://github.com/circa10a)) ## [0.3.6](https://github.com/pyouroboros/ouroboros/tree/0.3.6) (2018-12-21) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.3.5...0.3.6) **Implemented enhancements:** - print ouroboros configuration on startup [\#67](https://github.com/pyouroboros/ouroboros/pull/67) ([circa10a](https://github.com/circa10a)) ## [0.3.5](https://github.com/pyouroboros/ouroboros/tree/0.3.5) (2018-12-20) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.3.4...0.3.5) **Implemented enhancements:** - Raspberry Pi compatible docker image [\#62](https://github.com/pyouroboros/ouroboros/issues/62) - Scheduling docs [\#65](https://github.com/pyouroboros/ouroboros/pull/65) ([circa10a](https://github.com/circa10a)) ## [0.3.4](https://github.com/pyouroboros/ouroboros/tree/0.3.4) (2018-12-19) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.3.3...0.3.4) **Implemented enhancements:** - Rpi docker image [\#64](https://github.com/pyouroboros/ouroboros/pull/64) ([circa10a](https://github.com/circa10a)) ## [0.3.3](https://github.com/pyouroboros/ouroboros/tree/0.3.3) (2018-11-29) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.3.2...0.3.3) **Implemented enhancements:** - add docs, bump version [\#58](https://github.com/pyouroboros/ouroboros/pull/58) ([circa10a](https://github.com/circa10a)) **Fixed bugs:** - Problem accessing private registry [\#55](https://github.com/pyouroboros/ouroboros/issues/55) **Closed issues:** - Q: Add config file? [\#46](https://github.com/pyouroboros/ouroboros/issues/46) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] ## [0.3.2](https://github.com/pyouroboros/ouroboros/tree/0.3.2) (2018-11-28) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.3.1...0.3.2) **Fixed bugs:** - unrecognized arguments [\#52](https://github.com/pyouroboros/ouroboros/issues/52) - Fix config json [\#56](https://github.com/pyouroboros/ouroboros/pull/56) ([circa10a](https://github.com/circa10a)) ## [0.3.1](https://github.com/pyouroboros/ouroboros/tree/0.3.1) (2018-11-16) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.3.0...0.3.1) **Implemented enhancements:** - Add Prometheus endpoint [\#23](https://github.com/pyouroboros/ouroboros/issues/23) [[hacktoberfest](https://github.com/pyouroboros/ouroboros/labels/hacktoberfest)] **Fixed bugs:** - fix bind address bug [\#53](https://github.com/pyouroboros/ouroboros/pull/53) ([circa10a](https://github.com/circa10a)) ## [0.3.0](https://github.com/pyouroboros/ouroboros/tree/0.3.0) (2018-11-15) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.2.3...0.3.0) **Implemented enhancements:** - Q: continue to update to latest or same tag [\#43](https://github.com/pyouroboros/ouroboros/issues/43) - Metrics [\#51](https://github.com/pyouroboros/ouroboros/pull/51) ([circa10a](https://github.com/circa10a)) - Disable pip cache in Dockerfile [\#50](https://github.com/pyouroboros/ouroboros/pull/50) ([Strayer](https://github.com/Strayer)) ## [0.2.3](https://github.com/pyouroboros/ouroboros/tree/0.2.3) (2018-11-08) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.2.2...0.2.3) **Implemented enhancements:** - Keep tags [\#48](https://github.com/pyouroboros/ouroboros/pull/48) ([circa10a](https://github.com/circa10a)) ## [0.2.2](https://github.com/pyouroboros/ouroboros/tree/0.2.2) (2018-11-03) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.2.1...0.2.2) **Implemented enhancements:** - Add ability to ignore select containers [\#35](https://github.com/pyouroboros/ouroboros/issues/35) - Ignore containers [\#45](https://github.com/pyouroboros/ouroboros/pull/45) ([tlkamp](https://github.com/tlkamp)) - Update setup.py, travis param [\#42](https://github.com/pyouroboros/ouroboros/pull/42) ([circa10a](https://github.com/circa10a)) ## [0.2.1](https://github.com/pyouroboros/ouroboros/tree/0.2.1) (2018-10-28) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.1.3...0.2.1) **Implemented enhancements:** - Option precedence [\#32](https://github.com/pyouroboros/ouroboros/issues/32) - Add ouroboros to the user's path automagically [\#28](https://github.com/pyouroboros/ouroboros/issues/28) - Deploy to Pypi [\#41](https://github.com/pyouroboros/ouroboros/pull/41) ([circa10a](https://github.com/circa10a)) - Add setup.py [\#40](https://github.com/pyouroboros/ouroboros/pull/40) ([tlkamp](https://github.com/tlkamp)) - change branch to master [\#39](https://github.com/pyouroboros/ouroboros/pull/39) ([circa10a](https://github.com/circa10a)) - Move api client out of cli.py [\#38](https://github.com/pyouroboros/ouroboros/pull/38) ([tlkamp](https://github.com/tlkamp)) - Handle the exceptions better in cli.py [\#36](https://github.com/pyouroboros/ouroboros/pull/36) ([tlkamp](https://github.com/tlkamp)) **Closed issues:** - \[question\] network\_mode: "service:XXX" ? [\#33](https://github.com/pyouroboros/ouroboros/issues/33) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] **Other Pull Requests** - Remove global hosts variable [\#37](https://github.com/pyouroboros/ouroboros/pull/37) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([tlkamp](https://github.com/tlkamp)) - update docs [\#34](https://github.com/pyouroboros/ouroboros/pull/34) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] ([circa10a](https://github.com/circa10a)) ## [0.1.3](https://github.com/pyouroboros/ouroboros/tree/0.1.3) (2018-10-25) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.1.2...0.1.3) **Implemented enhancements:** - Make CLI expose fewer globals, formatting [\#31](https://github.com/pyouroboros/ouroboros/pull/31) ([tlkamp](https://github.com/tlkamp)) ## [0.1.2](https://github.com/pyouroboros/ouroboros/tree/0.1.2) (2018-10-24) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.1.1...0.1.2) **Implemented enhancements:** - Rewrite script to use vendor packages if possible [\#25](https://github.com/pyouroboros/ouroboros/pull/25) ([dannysauer](https://github.com/dannysauer)) - Improve URL matching Regex [\#24](https://github.com/pyouroboros/ouroboros/pull/24) ([dannysauer](https://github.com/dannysauer)) - Add environment files to the project for those working with Conda [\#22](https://github.com/pyouroboros/ouroboros/pull/22) ([tlkamp](https://github.com/tlkamp)) **Other Pull Requests** - regex changes, cli cleanup. [\#29](https://github.com/pyouroboros/ouroboros/pull/29) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([circa10a](https://github.com/circa10a)) - Clean up cli.py [\#27](https://github.com/pyouroboros/ouroboros/pull/27) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([tlkamp](https://github.com/tlkamp)) ## [0.1.1](https://github.com/pyouroboros/ouroboros/tree/0.1.1) (2018-10-21) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/0.1.0...0.1.1) ## [0.1.0](https://github.com/pyouroboros/ouroboros/tree/0.1.0) (2018-10-21) **Implemented enhancements:** - account for environment variables [\#19](https://github.com/pyouroboros/ouroboros/issues/19) - Support private repos [\#10](https://github.com/pyouroboros/ouroboros/issues/10) - Deploy to pypi [\#5](https://github.com/pyouroboros/ouroboros/issues/5) - Create travis build [\#4](https://github.com/pyouroboros/ouroboros/issues/4) - Rewrite new container class [\#3](https://github.com/pyouroboros/ouroboros/issues/3) - Write Unit Tests [\#2](https://github.com/pyouroboros/ouroboros/issues/2) - Add CLI Args [\#1](https://github.com/pyouroboros/ouroboros/issues/1) - added support for private registries [\#12](https://github.com/pyouroboros/ouroboros/pull/12) ([circa10a](https://github.com/circa10a)) - Torpus cli args [\#11](https://github.com/pyouroboros/ouroboros/pull/11) ([Torpus](https://github.com/Torpus)) - single client [\#9](https://github.com/pyouroboros/ouroboros/pull/9) ([circa10a](https://github.com/circa10a)) - the less code the better [\#8](https://github.com/pyouroboros/ouroboros/pull/8) ([circa10a](https://github.com/circa10a)) - Initial stuff [\#6](https://github.com/pyouroboros/ouroboros/pull/6) ([circa10a](https://github.com/circa10a)) **Closed issues:** - Create good docs [\#7](https://github.com/pyouroboros/ouroboros/issues/7) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] **Other Pull Requests** - Docs [\#21](https://github.com/pyouroboros/ouroboros/pull/21) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] ([circa10a](https://github.com/circa10a)) - Tests [\#20](https://github.com/pyouroboros/ouroboros/pull/20) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([circa10a](https://github.com/circa10a)) - Add travis [\#18](https://github.com/pyouroboros/ouroboros/pull/18) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([circa10a](https://github.com/circa10a)) - Tests [\#17](https://github.com/pyouroboros/ouroboros/pull/17) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([circa10a](https://github.com/circa10a)) - Tests [\#16](https://github.com/pyouroboros/ouroboros/pull/16) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([circa10a](https://github.com/circa10a)) - Tests [\#15](https://github.com/pyouroboros/ouroboros/pull/15) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([circa10a](https://github.com/circa10a)) - Tests [\#14](https://github.com/pyouroboros/ouroboros/pull/14) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([circa10a](https://github.com/circa10a)) - Tests [\#13](https://github.com/pyouroboros/ouroboros/pull/13) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([circa10a](https://github.com/circa10a)) ================================================ FILE: Dockerfile ================================================ FROM amd64/python:3.8.5-alpine LABEL maintainers="dirtycajunrice,circa10a" ENV TZ UTC WORKDIR /app COPY /requirements.txt /setup.py /ouroboros /README.md /app/ RUN apk add --no-cache tzdata && \ pip install --no-cache-dir -r requirements.txt COPY /pyouroboros /app/pyouroboros RUN pip install --no-cache-dir . ENTRYPOINT ["ouroboros"] ================================================ FILE: Dockerfile.arm ================================================ FROM arm32v6/python:3.8.5-alpine LABEL maintainers="dirtycajunrice,circa10a" ENV TZ UTC WORKDIR /app COPY /requirements.txt /setup.py /ouroboros /README.md /app/ RUN apk add --no-cache tzdata && \ pip install --no-cache-dir -r requirements.txt COPY /pyouroboros /app/pyouroboros RUN pip install --no-cache-dir . ENTRYPOINT ["ouroboros"] ================================================ FILE: Dockerfile.arm64 ================================================ FROM arm64v8/python:3.8.5-alpine LABEL maintainers="dirtycajunrice,circa10a" ENV TZ UTC WORKDIR /app COPY /requirements.txt /setup.py /ouroboros /README.md /app/ RUN apk add --no-cache tzdata && \ pip install --no-cache-dir -r requirements.txt COPY /pyouroboros /app/pyouroboros RUN pip install --no-cache-dir . ENTRYPOINT ["ouroboros"] ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Caleb Lemoine Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: MANIFEST.in ================================================ include requirements.txt include README.md ================================================ FILE: README.md ================================================ ⚠️⚠️⚠️ ouroboros is no longer in development. It does its job (more or less) and the devs have succumb to real life! Please feel free to fork and maintain as you wish. We appreciate all of the support in the last year :). After support from the community, automated version bumps will continue to try to keep ouroboros in check with dependencies. ⚠️⚠️⚠️ Ouroboros Logo [![Discord](https://img.shields.io/discord/532695326117593112.svg?colorB=7289DA&label=Discord&logo=Discord&logoColor=7289DA&style=flat-square)](https://discord.gg/qHNByUW) [![Release](https://img.shields.io/github/release/pyouroboros/ouroboros.svg?style=flat-square)](https://hub.docker.com/r/pyouroboros/ouroboros/) [![Python Version](https://img.shields.io/pypi/pyversions/ouroboros-cli.svg?style=flat-square)](https://pypi.org/project/ouroboros-cli/) [![Docker Pulls](https://img.shields.io/docker/pulls/pyouroboros/ouroboros.svg?style=flat-square)](https://hub.docker.com/r/pyouroboros/ouroboros/) [![Layers](https://images.microbadger.com/badges/image/pyouroboros/ouroboros.svg)](https://microbadger.com/images/pyouroboros/ouroboros) Automatically update your running Docker containers to the latest available image. The de-facto standard for docker update automation ## Overview Ouroboros will monitor (all or specified) running docker containers and update them to the (latest or tagged) available image in the remote registry. The updated container uses the same tag and parameters that were used when the container was first created such as volume/bind mounts, docker network connections, environment variables, restart policies, entrypoints, commands, etc. - Push your image to your registry and simply wait your defined interval for ouroboros to find the new image and redeploy your container autonomously. - Notify you via many platforms courtesy of [Apprise](https://github.com/caronc/apprise) - Serve metrics for trend monitoring (Currently: Prometheus/Influxdb) - Limit your server ssh access - `ssh -i key server.domainname "docker pull ... && docker run ..."` is for scrubs - `docker-compose pull && docker-compose up -d` is for fancier scrubs ## Getting Started More detailed usage and configuration can be found on [the wiki](https://github.com/pyouroboros/ouroboros/wiki). ### Docker Ouroboros is deployed via docker image like so: ```bash docker run -d --name ouroboros \ -v /var/run/docker.sock:/var/run/docker.sock \ pyouroboros/ouroboros ``` > This is image is compatible for amd64, arm32, and arm64 CPU architectures or via `docker-compose`: [Official Example](https://github.com/pyouroboros/ouroboros/blob/master/docker-compose.yml) ### Pip Ouroboros can also be installed via `pip`: ```bash pip install ouroboros-cli ``` And can then be invoked using the `ouroboros` command: ```bash $ ouroboros --interval 300 --log-level debug ``` > This can be useful if you would like to create a `systemd` service or similar daemon that doesn't run in a container ## Examples Per-command and scenario examples can be found in the [wiki](https://github.com/pyouroboros/ouroboros/wiki/Usage) ## Contributing All contributions are welcome! Contributing guidelines are in the works ================================================ FILE: docker-compose.yml ================================================ version: '3' services: ouroboros: container_name: ouroboros hostname: ouroboros image: pyouroboros/ouroboros environment: - CLEANUP=true - INTERVAL=300 - LOG_LEVEL=info - SELF_UPDATE=true - IGNORE=mongo influxdb postgres mariadb - TZ=America/Chicago restart: unless-stopped volumes: - /var/run/docker.sock:/var/run/docker.sock ================================================ FILE: ouroboros ================================================ #!/usr/bin/env python3 from pyouroboros.ouroboros import main # stub to allow cli call if __name__ == "__main__": main() ================================================ FILE: pyouroboros/__init__.py ================================================ VERSION = "1.4.3" BRANCH = "master" ================================================ FILE: pyouroboros/config.py ================================================ from os import environ from logging import getLogger from pyouroboros.logger import BlacklistFilter class Config(object): options = ['INTERVAL', 'PROMETHEUS', 'DOCKER_SOCKETS', 'MONITOR', 'IGNORE', 'LOG_LEVEL', 'PROMETHEUS_ADDR', 'PROMETHEUS_PORT', 'NOTIFIERS', 'REPO_USER', 'REPO_PASS', 'CLEANUP', 'RUN_ONCE', 'CRON', 'INFLUX_URL', 'INFLUX_PORT', 'INFLUX_USERNAME', 'INFLUX_PASSWORD', 'INFLUX_DATABASE', 'INFLUX_SSL', 'INFLUX_VERIFY_SSL', 'DATA_EXPORT', 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS', 'LABELS_ONLY', 'DRY_RUN', 'HOSTNAME', 'DOCKER_TLS_VERIFY', 'SWARM', 'SKIP_STARTUP_NOTIFICATIONS'] hostname = environ.get('HOSTNAME') interval = 300 cron = None docker_sockets = 'unix://var/run/docker.sock' docker_tls = False docker_tls_verify = True swarm = False monitor = [] ignore = [] data_export = None log_level = 'info' cleanup = False run_once = False dry_run = False self_update = False label_enable = False labels_only = False repo_user = None repo_pass = None auth_json = None prometheus = False prometheus_addr = '127.0.0.1' prometheus_port = 8000 influx_url = '127.0.0.1' influx_port = 8086 influx_ssl = False influx_verify_ssl = False influx_username = 'root' influx_password = 'root' influx_database = None notifiers = [] skip_startup_notifications = False def __init__(self, environment_vars, cli_args): self.cli_args = cli_args self.environment_vars = environment_vars self.filtered_strings = None self.logger = getLogger() self.parse() def config_blacklist(self): filtered_strings = [getattr(self, key.lower()) for key in Config.options if key.lower() in BlacklistFilter.blacklisted_keys] # Clear None values self.filtered_strings = list(filter(None, filtered_strings)) # take lists inside of list and append to list for index, value in enumerate(self.filtered_strings, 0): if isinstance(value, list): self.filtered_strings.extend(self.filtered_strings.pop(index)) self.filtered_strings.insert(index, self.filtered_strings[-1:][0]) # Added matching for ports ports = [string.split(':')[0] for string in self.filtered_strings if ':' in string] self.filtered_strings.extend(ports) # Added matching for tcp sockets. ConnectionPool ignores the tcp:// tcp_sockets = [string.split('//')[1] for string in self.filtered_strings if '//' in string] self.filtered_strings.extend(tcp_sockets) # Get JUST hostname from tcp//unix for socket in getattr(self, 'docker_sockets'): self.filtered_strings.append(socket.split('//')[1].split(':')[0]) for handler in self.logger.handlers: handler.addFilter(BlacklistFilter(set(self.filtered_strings))) def parse(self): for option in Config.options: if self.environment_vars.get(option): env_opt = self.environment_vars[option] if isinstance(env_opt, str): # Clean out quotes, both single/double and whitespace env_opt = env_opt.strip("'").strip('"').strip(' ') if option in ['INTERVAL', 'PROMETHEUS_PORT', 'INFLUX_PORT']: try: opt = int(env_opt) setattr(self, option.lower(), opt) except ValueError as e: print(e) elif option in ['CLEANUP', 'RUN_ONCE', 'INFLUX_SSL', 'INFLUX_VERIFY_SSL', 'DRY_RUN', 'SWARM', 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS', 'LABELS_ONLY', 'DOCKER_TLS_VERIFY', 'SKIP_STARTUP_NOTIFICATIONS']: if env_opt.lower() in ['true', 'yes']: setattr(self, option.lower(), True) elif env_opt.lower() in ['false', 'no']: setattr(self, option.lower(), False) else: self.logger.error('%s is not true/yes, nor false/no for %s. Assuming %s', env_opt, option, getattr(self, option)) else: setattr(self, option.lower(), env_opt) elif vars(self.cli_args).get(option): setattr(self, option.lower(), vars(self.cli_args).get(option)) # Specific var changes if self.repo_user and self.repo_pass: self.auth_json = {'Username': self.repo_user, 'Password': self.repo_pass} if self.interval < 30: self.interval = 30 if self.labels_only and not self.label_enable: self.logger.warning('labels_only enabled but not in use without label_enable') for option in ['docker_sockets', 'notifiers', 'monitor', 'ignore']: if isinstance(getattr(self, option), str): string_list = getattr(self, option) setattr(self, option, [string for string in string_list.split(' ')]) # Config sanity checks if self.cron: cron_times = self.cron.strip().split(' ') if len(cron_times) != 5: self.logger.error("Cron must be in cron syntax. e.g. * * * * * (5 places). Ignoring and using interval") self.cron = None else: self.logger.info("Cron configuration is valid. Using Cron schedule %s", cron_times) self.cron = cron_times self.interval = None if self.data_export == 'influxdb' and not self.influx_database: self.logger.error("You need to specify an influx database if you want to export to influxdb. Disabling " "influxdb data export.") if self.data_export == 'prometheus' and self.self_update: self.logger.warning("If you bind a port to ouroboros, it will be lost when it updates itself.") if self.dry_run and not self.run_once: self.logger.warning("Dry run is designed to be ran with run once. Setting for you.") self.run_once = True # Remove default config that is not used for cleaner logs if self.data_export != 'prometheus': self.prometheus_addr, self.prometheus_port = None, None if self.data_export != 'influxdb': self.influx_url, self.influx_port, self.influx_username, self.influx_password = None, None, None, None self.config_blacklist() ================================================ FILE: pyouroboros/dataexporters.py ================================================ import prometheus_client from logging import getLogger from influxdb import InfluxDBClient from datetime import datetime, timezone class DataManager(object): def __init__(self, config): self.config = config self.logger = getLogger() self.enabled = True self.monitored_containers = {} self.total_updated = {} self.prometheus = PrometheusExporter(self, config) if self.config.data_export == "prometheus" else None self.influx = InfluxClient(self, config) if self.config.data_export == "influxdb" else None def add(self, label, socket): if self.config.data_export == "prometheus" and self.enabled: self.prometheus.update(label, socket) elif self.config.data_export == "influxdb" and self.enabled: if label == "all": self.logger.debug("Total containers updated %s", self.total_updated[socket]) self.influx.write_points(label, socket) def set(self, socket): if self.config.data_export == "prometheus" and self.enabled: self.prometheus.set_monitored(socket) class PrometheusExporter(object): def __init__(self, data_manager, config): self.config = config self.data_manager = data_manager self.http_server = prometheus_client.start_http_server( self.config.prometheus_port, addr=self.config.prometheus_addr ) self.updated_containers_counter = prometheus_client.Counter( 'containers_updated', 'Count of containers updated', ['socket', 'container'] ) self.monitored_containers_gauge = prometheus_client.Gauge( 'containers_being_monitored', 'Gauge of containers being monitored', ['socket'] ) self.updated_all_containers_gauge = prometheus_client.Gauge( 'all_containers_updated', 'Count of total updated', ['socket'] ) self.logger = getLogger() def set_monitored(self, socket): """Set number of containers being monitoring with a gauge""" self.monitored_containers_gauge.labels(socket=socket).set(self.data_manager.monitored_containers[socket]) self.logger.debug("Prometheus Exporter monitored containers gauge set to %s", self.data_manager.monitored_containers[socket]) def update(self, label, socket): """Set container update count based on label""" if label == "all": self.updated_all_containers_gauge.labels(socket=socket).set(self.data_manager.total_updated[socket]) else: self.updated_containers_counter.labels(socket=socket, container=label).inc() self.logger.debug("Prometheus Exporter container update counter incremented for %s", label) class InfluxClient(object): def __init__(self, data_manger, config): self.data_manager = data_manger self.config = config self.logger = getLogger() self.influx = InfluxDBClient( host=self.config.influx_url, port=self.config.influx_port, username=self.config.influx_username, password=self.config.influx_password, database=self.config.influx_database, ssl=self.config.influx_ssl, verify_ssl=self.config.influx_verify_ssl ) self.db_check() def db_check(self): database_dicts = self.influx.get_list_database() databases = [d['name'] for d in database_dicts] if self.config.influx_database in databases: self.logger.debug("Influxdb database existence check passed for %s", self.config.influx_database) else: self.logger.debug("Influxdb database existence failed for %s. Disabling exports.", self.config.influx_database) self.data_manager.enabled = False def write_points(self, label, socket): clean_socket = socket.split("//")[1] now = datetime.now(timezone.utc).astimezone().isoformat() influx_payload = [ { "measurement": "Ouroboros", "tags": {'socket': clean_socket}, "time": now, "fields": {} }, { "measurement": "Ouroboros", "tags": {'configuration': self.config.hostname}, "time": now, "fields": {key: (value if not isinstance(value, list) else ' '.join(value)) for key, value in vars(self.config).items() if key.upper() in self.config.options} } ] if label == "all": influx_payload[0]['tags']["type"] = "stats" influx_payload[0]['fields'] = { "monitored_containers": self.data_manager.monitored_containers[socket], "updated_count": self.data_manager.total_updated[socket] } else: influx_payload[0]['tags'].update( { "type": "container_update", "container": label } ) influx_payload[0]['fields'] = {"count": 1} self.logger.debug("Writing data to influxdb: %s", influx_payload) self.influx.write_points(influx_payload) ================================================ FILE: pyouroboros/dockerclient.py ================================================ from time import sleep from logging import getLogger from docker import DockerClient, tls from os.path import isdir, isfile, join from docker.errors import DockerException, APIError, NotFound from pyouroboros.helpers import set_properties, remove_sha_prefix, get_digest class Docker(object): def __init__(self, socket, config, data_manager, notification_manager): self.config = config self.socket = socket self.client = self.connect() self.data_manager = data_manager self.logger = getLogger() self.notification_manager = notification_manager def connect(self): if self.config.docker_tls: try: cert_paths = { 'cert_top_dir': '/etc/docker/certs.d/', 'clean_socket': self.socket.split('//')[1] } cert_paths['cert_dir'] = join(cert_paths['cert_top_dir'], cert_paths['clean_socket']) cert_paths['cert_files'] = { 'client_cert': join(cert_paths['cert_dir'], 'client.cert'), 'client_key': join(cert_paths['cert_dir'], 'client.key'), 'ca_crt': join(cert_paths['cert_dir'], 'ca.crt') } if not isdir(cert_paths['cert_dir']): self.logger.error('%s is not a valid cert folder', cert_paths['cert_dir']) raise ValueError for cert_file in cert_paths['cert_files'].values(): if not isfile(cert_file): self.logger.error('%s does not exist', cert_file) raise ValueError tls_config = tls.TLSConfig( ca_cert=cert_paths['cert_files']['ca_crt'], verify=cert_paths['cert_files']['ca_crt'] if self.config.docker_tls_verify else False, client_cert=(cert_paths['cert_files']['client_cert'], cert_paths['cert_files']['client_key']) ) client = DockerClient(base_url=self.socket, tls=tls_config) except ValueError: self.logger.error('Invalid Docker TLS config for %s, reverting to unsecured', self.socket) client = DockerClient(base_url=self.socket) else: client = DockerClient(base_url=self.socket) return client class BaseImageObject(object): def __init__(self, docker_client): self.docker = docker_client self.logger = self.docker.logger self.config = self.docker.config self.client = self.docker.client self.socket = self.docker.socket self.data_manager = self.docker.data_manager self.data_manager.total_updated[self.socket] = 0 self.notification_manager = self.docker.notification_manager def _pull(self, tag): """Docker pull image tag""" self.logger.debug('Checking tag: %s', tag) try: if self.config.dry_run: # The authentication doesn't work with this call # See bugs https://github.com/docker/docker-py/issues/2225 return self.client.images.get_registry_data(tag) else: if self.config.auth_json: return_image = self.client.images.pull(tag, auth_config=self.config.auth_json) else: return_image = self.client.images.pull(tag) return return_image except APIError as e: if '' in str(e): self.logger.debug("Docker api issue. Ignoring") raise ConnectionError elif 'unauthorized' in str(e): if self.config.dry_run: self.logger.error('dry run : Upstream authentication issue while checking %s. See: ' 'https://github.com/docker/docker-py/issues/2225', tag) raise ConnectionError else: self.logger.critical("Invalid Credentials. Exiting") exit(1) elif 'Client.Timeout' in str(e): self.logger.critical( "Couldn't find an image on docker.com for %s. Local Build?", tag) raise ConnectionError elif ('pull access' or 'TLS handshake') in str(e): self.logger.critical("Couldn't pull. Skipping. Error: %s", e) raise ConnectionError class Container(BaseImageObject): mode = 'container' def __init__(self, docker_client): super().__init__(docker_client) self.monitored = self.monitor_filter() # Container sub functions def stop(self, container): self.logger.debug('Stopping container: %s', container.name) stop_signal = container.labels.get('com.ouroboros.stop_signal', False) if stop_signal: try: container.kill(signal=stop_signal) except APIError as e: self.logger.error('Cannot kill container using signal %s. stopping normally. Error: %s', stop_signal, e) container.stop() else: container.stop() def remove(self, container): self.logger.debug('Removing container: %s', container.name) try: container.remove() except NotFound as e: self.logger.error("Could not remove container. Error: %s", e) return def recreate(self, container, latest_image): new_config = set_properties(old=container, new=latest_image) self.stop(container) self.remove(container) created = self.client.api.create_container(**new_config) new_container = self.client.containers.get(created.get("Id")) # connect the new container to all networks of the old container for network_name, network_config in container.attrs['NetworkSettings']['Networks'].items(): network = self.client.networks.get(network_config['NetworkID']) try: network.disconnect(new_container.id, force=True) except APIError: pass new_network_config = { 'container': new_container, 'aliases': network_config['Aliases'], 'links': network_config['Links'] } if network_config['IPAMConfig']: new_network_config.update( { 'ipv4_address': network_config['IPAddress'], 'ipv6_address': network_config['GlobalIPv6Address'] } ) try: network.connect(**new_network_config) except APIError as e: if any(err in str(e) for err in ['user configured subnets', 'user defined networks']): if new_network_config.get('ipv4_address'): del new_network_config['ipv4_address'] if new_network_config.get('ipv6_address'): del new_network_config['ipv6_address'] network.connect(**new_network_config) else: self.logger.error('Unable to attach updated container to network "%s". Error: %s', network.name, e) new_container.start() def pull(self, current_tag): """Docker pull image tag""" tag = current_tag if not tag: self.logger.error('Missing tag. Skipping...') raise ConnectionError elif ':' not in tag: tag = f'{tag}:latest' return self._pull(tag) # Filters def running_filter(self): """Return running container objects list, except ouroboros itself""" running_containers = [] try: for container in self.client.containers.list(filters={'status': 'running'}): if self.config.self_update: running_containers.append(container) else: try: if 'ouroboros' not in container.image.tags[0]: if container.attrs['HostConfig']['AutoRemove']: self.logger.debug("Skipping %s due to --rm property.", container.name) else: running_containers.append(container) except IndexError: self.logger.error("%s has no tags.. you should clean it up! Ignoring.", container.id) continue except DockerException: self.logger.critical("Can't connect to Docker API at %s", self.config.docker_socket) exit(1) return running_containers def monitor_filter(self): """Return filtered running container objects list""" running_containers = self.running_filter() monitored_containers = [] for container in running_containers: ouro_label = container.labels.get('com.ouroboros.enable', False) # if labels enabled, use the label. 'true/yes' trigger monitoring. if self.config.label_enable and ouro_label: if ouro_label.lower() in ["true", "yes"]: monitored_containers.append(container) else: continue elif not self.config.labels_only: if self.config.monitor: if container.name in self.config.monitor and container.name not in self.config.ignore: monitored_containers.append(container) elif container.name not in self.config.ignore: monitored_containers.append(container) self.data_manager.monitored_containers[self.socket] = len(monitored_containers) self.data_manager.set(self.socket) return monitored_containers # Socket Functions def self_check(self): if self.config.self_update: me_list = [container for container in self.client.containers.list() if 'ouroboros' in container.name] if len(me_list) > 1: self.update_self(count=2, me_list=me_list) def socket_check(self): depends_on_names = [] hard_depends_on_names = [] updateable = [] self.monitored = self.monitor_filter() if not self.monitored: self.logger.info('No containers are running or monitored on %s', self.socket) return for container in self.monitored: current_image = container.image current_tag = container.attrs['Config']['Image'] try: latest_image = self.pull(current_tag) except ConnectionError: continue try: if current_image.id != latest_image.id: updateable.append((container, current_image, latest_image)) else: continue except AttributeError: self.logger.error("Issue detecting %s's image tag. Skipping...", container.name) # Get container list to restart after update complete depends_on = container.labels.get('com.ouroboros.depends_on', False) hard_depends_on = container.labels.get('com.ouroboros.hard_depends_on', False) if depends_on: depends_on_names.extend([name.strip() for name in depends_on.split(',')]) if hard_depends_on: hard_depends_on_names.extend([name.strip() for name in hard_depends_on.split(',')]) hard_depends_on_containers = [] hard_depends_on_names = list(set(hard_depends_on_names)) for name in hard_depends_on_names: try: hard_depends_on_containers.append(self.client.containers.get(name)) except NotFound: self.logger.error("Could not find dependant container %s on socket %s. Ignoring", name, self.socket) depends_on_containers = [] depends_on_names = list(set(depends_on_names)) depends_on_names = [name for name in depends_on_names if name not in hard_depends_on_names] for name in depends_on_names: try: depends_on_containers.append(self.client.containers.get(name)) except NotFound: self.logger.error("Could not find dependant container %s on socket %s. Ignoring", name, self.socket) return updateable, depends_on_containers, hard_depends_on_containers def update(self): updated_count = 0 try: updateable, depends_on_containers, hard_depends_on_containers = self.socket_check() except TypeError: return for container in depends_on_containers + hard_depends_on_containers: self.stop(container) for container, current_image, latest_image in updateable: if self.config.dry_run: # Ugly hack for repo digest repo_digest_id = current_image.attrs['RepoDigests'][0].split('@')[1] if repo_digest_id != latest_image.id: self.logger.info('dry run : %s would be updated', container.name) continue if container.name in ['ouroboros', 'ouroboros-updated']: self.data_manager.total_updated[self.socket] += 1 self.data_manager.add(label=container.name, socket=self.socket) self.data_manager.add(label='all', socket=self.socket) self.notification_manager.send(container_tuples=updateable, socket=self.socket, kind='update') self.update_self(old_container=container, new_image=latest_image, count=1) self.logger.info('%s will be updated', container.name) self.recreate(container, latest_image) if self.config.cleanup: try: self.client.images.remove(current_image.id) except APIError as e: self.logger.error("Could not delete old image for %s, Error: %s", container.name, e) updated_count += 1 self.logger.debug("Incrementing total container updated count") self.data_manager.total_updated[self.socket] += 1 self.data_manager.add(label=container.name, socket=self.socket) self.data_manager.add(label='all', socket=self.socket) for container in depends_on_containers: # Reload container to ensure it isn't referencing the old image container.reload() container.start() for container in hard_depends_on_containers: self.recreate(container, container.image) if updated_count > 0: self.notification_manager.send(container_tuples=updateable, socket=self.socket, kind='update') def update_self(self, count=None, old_container=None, me_list=None, new_image=None): if count == 2: self.logger.debug('God im messy... cleaning myself up.') old_me_id = me_list[0].id if me_list[0].attrs['Created'] < me_list[1].attrs['Created'] else me_list[1].id old_me = self.client.containers.get(old_me_id) old_me_image_id = old_me.image.id old_me.stop() old_me.remove() self.client.images.remove(old_me_image_id) self.logger.debug('Ahhh. All better.') self.monitored = self.monitor_filter() elif count == 1: self.logger.debug('I need to update! Starting the ouroboros ;)') self_name = 'ouroboros-updated' if old_container.name == 'ouroboros' else 'ouroboros' new_config = set_properties(old=old_container, new=new_image, self_name=self_name) try: me_created = self.client.api.create_container(**new_config) new_me = self.client.containers.get(me_created.get("Id")) new_me.start() self.logger.debug('If you strike me down, I shall become ' 'more powerful than you could possibly imagine.') self.logger.debug('https://bit.ly/2VVY7GH') sleep(30) except APIError as e: self.logger.error("Self update failed.") self.logger.error(e) class Service(BaseImageObject): mode = 'service' def __init__(self, docker_client): super().__init__(docker_client) self.monitored = self.monitor_filter() def monitor_filter(self): """Return filtered service objects list""" services = self.client.services.list(filters={'label': 'com.ouroboros.enable'}) monitored_services = [] for service in services: ouro_label = service.attrs['Spec']['Labels'].get('com.ouroboros.enable') if not self.config.label_enable or ouro_label.lower() in ["true", "yes"]: monitored_services.append(service) self.data_manager.monitored_containers[self.socket] = len(monitored_services) self.data_manager.set(self.socket) return monitored_services def pull(self, tag): """Docker pull image tag""" return self._pull(tag) def update(self): updated_service_tuples = [] self.monitored = self.monitor_filter() if not self.monitored: self.logger.info('No services monitored') for service in self.monitored: image_string = service.attrs['Spec']['TaskTemplate']['ContainerSpec']['Image'] if '@' in image_string: tag = image_string.split('@')[0] sha256 = remove_sha_prefix(image_string.split('@')[1]) else: self.logger.error('No image SHA for %s. Skipping', image_string) continue try: latest_image = self.pull(tag) except ConnectionError: continue latest_image_sha256 = get_digest(latest_image) self.logger.debug('Latest sha256 for %s is %s', tag, latest_image_sha256) if sha256 != latest_image_sha256: if self.config.dry_run: # Ugly hack for repo digest self.logger.info('dry run : %s would be updated', service.name) continue updated_service_tuples.append( (service, sha256[-10:], latest_image) ) if 'ouroboros' in service.name and self.config.self_update: self.data_manager.total_updated[self.socket] += 1 self.data_manager.add(label=service.name, socket=self.socket) self.data_manager.add(label='all', socket=self.socket) self.notification_manager.send(container_tuples=updated_service_tuples, socket=self.socket, kind='update', mode='service') self.logger.info('%s will be updated', service.name) service.update(image=f"{tag}@sha256:{latest_image_sha256}") self.data_manager.total_updated[self.socket] += 1 self.data_manager.add(label=service.name, socket=self.socket) self.data_manager.add(label='all', socket=self.socket) if updated_service_tuples: self.notification_manager.send( container_tuples=updated_service_tuples, socket=self.socket, kind='update', mode='service' ) ================================================ FILE: pyouroboros/helpers.py ================================================ def set_properties(old, new, self_name=None): """Store object for spawning new container in place of the one with outdated image""" properties = { 'name': self_name if self_name else old.name, 'hostname': old.attrs['Config']['Hostname'], 'user': old.attrs['Config']['User'], 'detach': True, 'domainname': old.attrs['Config']['Domainname'], 'tty': old.attrs['Config']['Tty'], 'ports': None if not old.attrs['Config'].get('ExposedPorts') else [ (p.split('/')[0], p.split('/')[1]) for p in old.attrs['Config']['ExposedPorts'].keys() ], 'volumes': None if not old.attrs['Config'].get('Volumes') else [ v for v in old.attrs['Config']['Volumes'].keys() ], 'working_dir': old.attrs['Config']['WorkingDir'], 'image': new.tags[0], 'command': old.attrs['Config']['Cmd'], 'host_config': old.attrs['HostConfig'], 'labels': old.attrs['Config']['Labels'], 'entrypoint': old.attrs['Config']['Entrypoint'], 'environment': old.attrs['Config']['Env'], 'healthcheck': old.attrs['Config'].get('Healthcheck', None) } return properties def remove_sha_prefix(digest): if digest.startswith("sha256:"): return digest[7:] return digest def get_digest(image): digest = image.attrs.get( "Descriptor", {} ).get("digest") or image.attrs.get( "RepoDigests" )[0].split('@')[1] or image.id return remove_sha_prefix(digest) ================================================ FILE: pyouroboros/logger.py ================================================ from logging import Filter, getLogger, Formatter, StreamHandler class BlacklistFilter(Filter): """ Log filter for blacklisted tokens and passwords """ blacklisted_keys = ['repo_user', 'repo_pass', 'auth_json', 'docker_sockets', 'prometheus_addr', 'influx_username', 'influx_password', 'influx_url', 'notifiers'] def __init__(self, filteredstrings): super().__init__() self.filtered_strings = filteredstrings def filter(self, record): for item in self.filtered_strings: try: if item in record.msg: record.msg = record.msg.replace(item, 8 * '*' + item[-5:]) if any(item in str(arg) for arg in record.args): record.args = tuple(arg.replace(item, 8 * '*' + item[-5:]) if isinstance(arg, str) else arg for arg in record.args) except TypeError: pass return True class OuroborosLogger(object): def __init__(self, level='INFO'): # Create the Logger self.logger = getLogger() try: self.logger.setLevel(level.upper()) except ValueError: level = "INFO" self.logger.setLevel(level.upper()) # Create a Formatter for formatting the log messages logger_formatter = Formatter('%(asctime)s : %(levelname)s : %(module)s : %(message)s', '%Y-%m-%d %H:%M:%S') # Add the console logger console_logger = StreamHandler() console_logger.setFormatter(logger_formatter) console_logger.setLevel(level.upper()) # Add the Handler to the Logger self.logger.addHandler(console_logger) # Less verbose apscheduler logging if info if self.logger.getEffectiveLevel() == 20: getLogger('apscheduler').setLevel('WARNING') ================================================ FILE: pyouroboros/notifiers.py ================================================ import apprise from logging import getLogger from datetime import datetime, timezone class NotificationManager(object): def __init__(self, config, data_manager): self.config = config self.data_manager = data_manager self.logger = getLogger() self.apprise = self.build_apprise() def build_apprise(self): asset = apprise.AppriseAsset( image_url_mask='https://i.imgur.com/L40ksWY.png', default_extension='.png' ) asset.app_id = "Ouroboros" asset.app_desc = "Ouroboros" asset.app_url = "https://github.com/pyouroboros/ouroboros" asset.html_notify_map['info'] = '#5F87C6' asset.image_url_logo = 'https://bin.cajun.pro/images/ouroboros/notifications/ouroboros-logo-256x256.png' apprise_obj = apprise.Apprise(asset=asset) for notifier in self.config.notifiers: add = apprise_obj.add(notifier) if not add: self.logger.error('Could not add notifier %s', notifier) return apprise_obj def send(self, container_tuples=None, socket=None, kind='update', next_run=None, mode='container'): if kind == 'startup': now = datetime.now(timezone.utc).astimezone() title = f'Ouroboros has started' body_fields = [ f'Host: {self.config.hostname}', f'Time: {now.strftime("%Y-%m-%d %H:%M:%S")}', f'Next Run: {next_run}'] else: title = 'Ouroboros has updated containers!' body_fields = [ f"Host/Socket: {self.config.hostname} / {socket.split('//')[1]}", f"Containers Monitored: {self.data_manager.monitored_containers[socket]}", f"Total Containers Updated: {self.data_manager.total_updated[socket]}", f"Containers updated this pass: {len(container_tuples)}" ] body_fields.extend( [ "{} updated from {} to {}".format( container.name, old_image if mode == 'service' else old_image.short_id.split(':')[1], new_image.short_id.split(':')[1] ) for container, old_image, new_image in container_tuples ] ) body = '\r\n'.join(body_fields) if self.apprise.servers: self.apprise.notify(title=title, body=body) ================================================ FILE: pyouroboros/ouroboros.py ================================================ from time import sleep from os import environ from requests.exceptions import ConnectionError from datetime import datetime, timezone, timedelta from argparse import ArgumentParser, RawTextHelpFormatter from apscheduler.schedulers.background import BackgroundScheduler from pyouroboros.config import Config from pyouroboros import VERSION, BRANCH from pyouroboros.logger import OuroborosLogger from pyouroboros.dataexporters import DataManager from pyouroboros.notifiers import NotificationManager from pyouroboros.dockerclient import Docker, Container, Service def main(): """Declare command line options""" parser = ArgumentParser(description='ouroboros', formatter_class=RawTextHelpFormatter, epilog='EXAMPLE: ouroboros -d tcp://1.2.3.4:5678 -i 20 -m container1 container2 -l warn') core_group = parser.add_argument_group("Core", "Configuration of core functionality") core_group.add_argument('-v', '--version', action='version', version=VERSION) core_group.add_argument('-d', '--docker-sockets', nargs='+', default=Config.docker_sockets, dest='DOCKER_SOCKETS', help='Sockets for docker management\n' 'DEFAULT: "unix://var/run/docker.sock"\n' 'EXAMPLE: -d unix://var/run/docker.sock tcp://192.168.1.100:2376') core_group.add_argument('-t', '--docker-tls', default=Config.docker_tls, dest='DOCKER_TLS', action='store_true', help='Enable docker TLS\n' 'REQUIRES: docker cert mount') core_group.add_argument('-T', '--docker-tls-verify', default=Config.docker_tls_verify, dest='DOCKER_TLS_VERIFY', action='store_false', help='Verify the CA Certificate mounted for TLS\n' 'DEFAULT: True') core_group.add_argument('-i', '--interval', type=int, default=Config.interval, dest='INTERVAL', help='Interval in seconds between checking for updates\n' 'DEFAULT: 300') core_group.add_argument('-C', '--cron', default=Config.cron, dest='CRON', help='Cron formatted string for scheduling\n' 'EXAMPLE: "*/5 * * * *"') core_group.add_argument('-l', '--log-level', choices=['debug', 'info', 'warn', 'error', 'critical'], dest='LOG_LEVEL', default=Config.log_level, help='Set logging level\n' 'DEFAULT: info') core_group.add_argument('-u', '--self-update', default=Config.self_update, dest='SELF_UPDATE', action='store_true', help='Let ouroboros update itself') core_group.add_argument('-S', '--swarm', default=Config.swarm, dest='SWARM', action='store_true', help='Put ouroboros in swarm mode') core_group.add_argument('-o', '--run-once', default=Config.run_once, action='store_true', dest='RUN_ONCE', help='Single run') core_group.add_argument('-A', '--dry-run', default=Config.dry_run, action='store_true', dest='DRY_RUN', help='Run without making changes. Best used with run-once') core_group.add_argument('-N', '--notifiers', nargs='+', default=Config.notifiers, dest='NOTIFIERS', help='Apprise formatted notifiers\n' 'EXAMPLE: -N discord://1234123412341234/jasdfasdfasdfasddfasdf ' 'mailto://user:pass@gmail.com') docker_group = parser.add_argument_group("Docker", "Configuration of docker functionality") docker_group.add_argument('-m', '--monitor', nargs='+', default=Config.monitor, dest='MONITOR', help='Which container(s) to monitor\n' 'DEFAULT: All') docker_group.add_argument('-n', '--ignore', nargs='+', default=Config.ignore, dest='IGNORE', help='Container(s) to ignore\n' 'EXAMPLE: -n container1 container2') docker_group.add_argument('-k', '--label-enable', default=Config.label_enable, dest='LABEL_ENABLE', action='store_true', help='Enable label monitoring for ouroboros label options\n' 'Note: labels take precedence' 'DEFAULT: False') docker_group.add_argument('-M', '--labels-only', default=Config.labels_only, dest='LABELS_ONLY', action='store_true', help='Only watch containers that utilize labels\n' 'This allows a more strict compliance for environments' 'DEFAULT: False') docker_group.add_argument('-c', '--cleanup', default=Config.cleanup, dest='CLEANUP', action='store_true', help='Remove old images after updating') docker_group.add_argument('-r', '--repo-user', default=Config.repo_user, dest='REPO_USER', help='Private docker registry username\n' 'EXAMPLE: foo@bar.baz') docker_group.add_argument('-R', '--repo-pass', default=Config.repo_pass, dest='REPO_PASS', help='Private docker registry password\n' 'EXAMPLE: MyPa$$w0rd') data_group = parser.add_argument_group('Data Export', 'Configuration of data export functionality') data_group.add_argument('-D', '--data-export', choices=['prometheus', 'influxdb'], default=Config.data_export, dest='DATA_EXPORT', help='Enable exporting of data for chosen option') data_group.add_argument('-a', '--prometheus-addr', default=Config.prometheus_addr, dest='PROMETHEUS_ADDR', help='Bind address to run Prometheus exporter on\n' 'DEFAULT: 127.0.0.1') data_group.add_argument('-p', '--prometheus-port', type=int, default=Config.prometheus_port, dest='PROMETHEUS_PORT', help='Port to run Prometheus exporter on\n' 'DEFAULT: 8000') data_group.add_argument('-I', '--influx-url', default=Config.influx_url, dest='INFLUX_URL', help='URL for influxdb\n' 'DEFAULT: 127.0.0.1') data_group.add_argument('-P', '--influx-port', type=int, default=Config.influx_port, dest='INFLUX_PORT', help='PORT for influxdb\n' 'DEFAULT: 8086') data_group.add_argument('-U', '--influx-username', default=Config.influx_username, dest='INFLUX_USERNAME', help='Username for influxdb\n' 'DEFAULT: root') data_group.add_argument('-x', '--influx-password', default=Config.influx_password, dest='INFLUX_PASSWORD', help='Password for influxdb\n' 'DEFAULT: root') data_group.add_argument('-X', '--influx-database', default=Config.influx_database, dest='INFLUX_DATABASE', help='Influx database name. Required if using influxdb') data_group.add_argument('-s', '--influx-ssl', default=Config.influx_ssl, dest='INFLUX_SSL', action='store_true', help='Use SSL when connecting to influxdb') data_group.add_argument('-V', '--influx-verify-ssl', default=Config.influx_verify_ssl, dest='INFLUX_VERIFY_SSL', action='store_true', help='Verify SSL certificate when connecting to influxdb') docker_group.add_argument('--skip-startup-notifications', default=Config.skip_startup_notifications, dest='SKIP_STARTUP_NOTIFICATIONS', action='store_true', help='Do not send ouroboros notifications when starting') args = parser.parse_args() if environ.get('LOG_LEVEL'): log_level = environ.get('LOG_LEVEL') else: log_level = args.LOG_LEVEL ol = OuroborosLogger(level=log_level) ol.logger.info('Version: %s-%s', VERSION, BRANCH) config = Config(environment_vars=environ, cli_args=args) config_dict = {key: value for key, value in vars(config).items() if key.upper() in config.options} ol.logger.debug("Ouroboros configuration: %s", config_dict) data_manager = DataManager(config) notification_manager = NotificationManager(config, data_manager) scheduler = BackgroundScheduler() scheduler.start() for socket in config.docker_sockets: try: docker = Docker(socket, config, data_manager, notification_manager) if config.swarm: mode = Service(docker) else: mode = Container(docker) if config.run_once: scheduler.add_job(mode.update, name=f'Run Once container update for {socket}') else: if mode.mode == 'container': scheduler.add_job(mode.self_check, name=f'Self Check for {socket}') if config.cron: scheduler.add_job( mode.update, name=f'Cron container update for {socket}', trigger='cron', minute=config.cron[0], hour=config.cron[1], day=config.cron[2], month=config.cron[3], day_of_week=config.cron[4], misfire_grace_time=15 ) else: scheduler.add_job( mode.update, name=f'Initial run interval container update for {socket}' ) scheduler.add_job( mode.update, name=f'Interval container update for {socket}', trigger='interval', seconds=config.interval ) except ConnectionError: ol.logger.error("Could not connect to socket %s. Check your config", socket) if config.run_once: next_run = None elif config.cron: next_run = scheduler.get_jobs()[0].next_run_time else: now = datetime.now(timezone.utc).astimezone() next_run = (now + timedelta(0, config.interval)).strftime("%Y-%m-%d %H:%M:%S") if not config.skip_startup_notifications: notification_manager.send(kind='startup', next_run=next_run) while scheduler.get_jobs(): sleep(1) scheduler.shutdown() if __name__ == "__main__": main() ================================================ FILE: requirements.txt ================================================ docker>=3.7.0 prometheus_client>=0.5.0 requests>=2.21.0 influxdb>=5.2.1 apprise>=0.8.5 apscheduler>=3.5.3 ================================================ FILE: setup.py ================================================ from setuptools import setup, find_packages from pyouroboros import VERSION def read(filename): with open(filename) as f: return f.read() def get_requirements(filename="requirements.txt"): """returns a list of all requirements""" requirements = read(filename) return list(filter(None, [req.strip() for req in requirements.split() if not req.startswith('#')])) setup( name='ouroboros-cli', version=VERSION, maintainer='circa10a', maintainer_email='caleblemoine@gmail.com', description='Automatically update running docker containers', long_description=read('README.md'), long_description_content_type='text/markdown', url='https://github.com/pyouroboros/ouroboros', license='MIT', classifiers=['Programming Language :: Python', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7'], packages=find_packages(), scripts=['ouroboros'], install_requires=get_requirements(), python_requires='>=3.6.2' )