mirror of
https://github.com/containrrr/watchtower.git
synced 2025-09-22 05:40:50 +02:00
Merge branch 'main' into healthcheck
This commit is contained in:
commit
246da4da68
31 changed files with 745 additions and 986 deletions
|
@ -849,6 +849,15 @@
|
|||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "testwill",
|
||||
"name": "guangwu",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/8717479?v=4",
|
||||
"profile": "https://github.com/testwill",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
@ -857,5 +866,6 @@
|
|||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"commitConvention": "none",
|
||||
"skipCi": true
|
||||
"skipCi": true,
|
||||
"commitType": "docs"
|
||||
}
|
||||
|
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
|
@ -31,7 +31,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
|
|
2
.github/workflows/publish-docs.yml
vendored
2
.github/workflows/publish-docs.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup python
|
||||
|
|
8
.github/workflows/pull-request.yml
vendored
8
.github/workflows/pull-request.yml
vendored
|
@ -12,7 +12,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
|
@ -37,7 +37,7 @@ jobs:
|
|||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
|
@ -56,7 +56,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
|
@ -64,7 +64,7 @@ jobs:
|
|||
with:
|
||||
go-version: 1.18.x
|
||||
- name: Build
|
||||
uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 #v3
|
||||
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 #v3
|
||||
with:
|
||||
version: v0.155.0
|
||||
args: --snapshot --skip-publish --debug
|
||||
|
|
6
.github/workflows/release-dev.yaml
vendored
6
.github/workflows/release-dev.yaml
vendored
|
@ -10,7 +10,7 @@ jobs:
|
|||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
|
@ -20,7 +20,7 @@ jobs:
|
|||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
|
@ -37,7 +37,7 @@ jobs:
|
|||
- test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Publish to Docker Hub
|
||||
uses: jerray/publish-docker-action@87d84711629b0dc9f6bb127b568413cc92a2088e #master@2022-10-14
|
||||
with:
|
||||
|
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
|
@ -15,7 +15,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
|
@ -40,7 +40,7 @@ jobs:
|
|||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
|
@ -62,7 +62,7 @@ jobs:
|
|||
TAG: ${{ github.event.release.tag_name }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
|
@ -81,7 +81,7 @@ jobs:
|
|||
password: ${{ secrets.BOT_GHCR_PAT }}
|
||||
registry: ghcr.io
|
||||
- name: Build
|
||||
uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 #v3
|
||||
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 #v3
|
||||
with:
|
||||
version: v0.155.0
|
||||
args: --debug
|
||||
|
|
188
README.md
188
README.md
|
@ -44,126 +44,126 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center"><a href="http://codelica.com"><img src="https://avatars3.githubusercontent.com/u/386101?v=4?s=100" width="100px;" alt="James"/><br /><sub><b>James</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Codelica" title="Tests">⚠️</a> <a href="#ideas-Codelica" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center"><a href="https://kopfkrieg.org"><img src="https://avatars2.githubusercontent.com/u/5047813?v=4?s=100" width="100px;" alt="Florian"/><br /><sub><b>Florian</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3AKopfKrieg" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/containrrr/watchtower/commits?author=KopfKrieg" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/bdehamer"><img src="https://avatars1.githubusercontent.com/u/398027?v=4?s=100" width="100px;" alt="Brian DeHamer"/><br /><sub><b>Brian DeHamer</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=bdehamer" title="Code">💻</a> <a href="#maintenance-bdehamer" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://github.com/rosscado"><img src="https://avatars1.githubusercontent.com/u/16578183?v=4?s=100" width="100px;" alt="Ross Cadogan"/><br /><sub><b>Ross Cadogan</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=rosscado" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/stffabi"><img src="https://avatars0.githubusercontent.com/u/9464631?v=4?s=100" width="100px;" alt="stffabi"/><br /><sub><b>stffabi</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=stffabi" title="Code">💻</a> <a href="#maintenance-stffabi" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://github.com/ATCUSA"><img src="https://avatars3.githubusercontent.com/u/3581228?v=4?s=100" width="100px;" alt="Austin"/><br /><sub><b>Austin</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=ATCUSA" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://labs.ctl.io"><img src="https://avatars2.githubusercontent.com/u/6181487?v=4?s=100" width="100px;" alt="David Gardner"/><br /><sub><b>David Gardner</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3Adavidgardner11" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/containrrr/watchtower/commits?author=davidgardner11" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://codelica.com"><img src="https://avatars3.githubusercontent.com/u/386101?v=4?s=100" width="100px;" alt="James"/><br /><sub><b>James</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Codelica" title="Tests">⚠️</a> <a href="#ideas-Codelica" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://kopfkrieg.org"><img src="https://avatars2.githubusercontent.com/u/5047813?v=4?s=100" width="100px;" alt="Florian"/><br /><sub><b>Florian</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3AKopfKrieg" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/containrrr/watchtower/commits?author=KopfKrieg" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bdehamer"><img src="https://avatars1.githubusercontent.com/u/398027?v=4?s=100" width="100px;" alt="Brian DeHamer"/><br /><sub><b>Brian DeHamer</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=bdehamer" title="Code">💻</a> <a href="#maintenance-bdehamer" title="Maintenance">🚧</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rosscado"><img src="https://avatars1.githubusercontent.com/u/16578183?v=4?s=100" width="100px;" alt="Ross Cadogan"/><br /><sub><b>Ross Cadogan</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=rosscado" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/stffabi"><img src="https://avatars0.githubusercontent.com/u/9464631?v=4?s=100" width="100px;" alt="stffabi"/><br /><sub><b>stffabi</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=stffabi" title="Code">💻</a> <a href="#maintenance-stffabi" title="Maintenance">🚧</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ATCUSA"><img src="https://avatars3.githubusercontent.com/u/3581228?v=4?s=100" width="100px;" alt="Austin"/><br /><sub><b>Austin</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=ATCUSA" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://labs.ctl.io"><img src="https://avatars2.githubusercontent.com/u/6181487?v=4?s=100" width="100px;" alt="David Gardner"/><br /><sub><b>David Gardner</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3Adavidgardner11" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/containrrr/watchtower/commits?author=davidgardner11" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/dolanor"><img src="https://avatars3.githubusercontent.com/u/928722?v=4?s=100" width="100px;" alt="Tanguy ⧓ Herrmann"/><br /><sub><b>Tanguy ⧓ Herrmann</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=dolanor" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/rdamazio"><img src="https://avatars3.githubusercontent.com/u/997641?v=4?s=100" width="100px;" alt="Rodrigo Damazio Bovendorp"/><br /><sub><b>Rodrigo Damazio Bovendorp</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=rdamazio" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=rdamazio" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.taisun.io/"><img src="https://avatars3.githubusercontent.com/u/1852688?v=4?s=100" width="100px;" alt="Ryan Kuba"/><br /><sub><b>Ryan Kuba</b></sub></a><br /><a href="#infra-thelamer" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
<td align="center"><a href="https://github.com/cnrmck"><img src="https://avatars2.githubusercontent.com/u/22061955?v=4?s=100" width="100px;" alt="cnrmck"/><br /><sub><b>cnrmck</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=cnrmck" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://harrywalter.co.uk"><img src="https://avatars3.githubusercontent.com/u/338588?v=4?s=100" width="100px;" alt="Harry Walter"/><br /><sub><b>Harry Walter</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=haswalt" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://projectsperanza.com"><img src="https://avatars3.githubusercontent.com/u/74515?v=4?s=100" width="100px;" alt="Robotex"/><br /><sub><b>Robotex</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Robotex" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://geraldpape.io"><img src="https://avatars0.githubusercontent.com/u/1494211?v=4?s=100" width="100px;" alt="Gerald Pape"/><br /><sub><b>Gerald Pape</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=ubergesundheit" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dolanor"><img src="https://avatars3.githubusercontent.com/u/928722?v=4?s=100" width="100px;" alt="Tanguy ⧓ Herrmann"/><br /><sub><b>Tanguy ⧓ Herrmann</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=dolanor" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rdamazio"><img src="https://avatars3.githubusercontent.com/u/997641?v=4?s=100" width="100px;" alt="Rodrigo Damazio Bovendorp"/><br /><sub><b>Rodrigo Damazio Bovendorp</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=rdamazio" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=rdamazio" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.taisun.io/"><img src="https://avatars3.githubusercontent.com/u/1852688?v=4?s=100" width="100px;" alt="Ryan Kuba"/><br /><sub><b>Ryan Kuba</b></sub></a><br /><a href="#infra-thelamer" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cnrmck"><img src="https://avatars2.githubusercontent.com/u/22061955?v=4?s=100" width="100px;" alt="cnrmck"/><br /><sub><b>cnrmck</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=cnrmck" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://harrywalter.co.uk"><img src="https://avatars3.githubusercontent.com/u/338588?v=4?s=100" width="100px;" alt="Harry Walter"/><br /><sub><b>Harry Walter</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=haswalt" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://projectsperanza.com"><img src="https://avatars3.githubusercontent.com/u/74515?v=4?s=100" width="100px;" alt="Robotex"/><br /><sub><b>Robotex</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Robotex" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://geraldpape.io"><img src="https://avatars0.githubusercontent.com/u/1494211?v=4?s=100" width="100px;" alt="Gerald Pape"/><br /><sub><b>Gerald Pape</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=ubergesundheit" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/fomk"><img src="https://avatars0.githubusercontent.com/u/17636183?v=4?s=100" width="100px;" alt="fomk"/><br /><sub><b>fomk</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=fomk" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/svengo"><img src="https://avatars3.githubusercontent.com/u/2502366?v=4?s=100" width="100px;" alt="Sven Gottwald"/><br /><sub><b>Sven Gottwald</b></sub></a><br /><a href="#infra-svengo" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
<td align="center"><a href="https://liberapay.com/techknowlogick/"><img src="https://avatars1.githubusercontent.com/u/164197?v=4?s=100" width="100px;" alt="techknowlogick"/><br /><sub><b>techknowlogick</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=techknowlogick" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://log.c5t.org/about/"><img src="https://avatars1.githubusercontent.com/u/1449568?v=4?s=100" width="100px;" alt="waja"/><br /><sub><b>waja</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=waja" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://scottalbertson.com"><img src="https://avatars2.githubusercontent.com/u/154463?v=4?s=100" width="100px;" alt="Scott Albertson"/><br /><sub><b>Scott Albertson</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=salbertson" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/huddlesj"><img src="https://avatars1.githubusercontent.com/u/11966535?v=4?s=100" width="100px;" alt="Jason Huddleston"/><br /><sub><b>Jason Huddleston</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=huddlesj" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://npstr.space/"><img src="https://avatars3.githubusercontent.com/u/6048348?v=4?s=100" width="100px;" alt="Napster"/><br /><sub><b>Napster</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=napstr" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fomk"><img src="https://avatars0.githubusercontent.com/u/17636183?v=4?s=100" width="100px;" alt="fomk"/><br /><sub><b>fomk</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=fomk" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/svengo"><img src="https://avatars3.githubusercontent.com/u/2502366?v=4?s=100" width="100px;" alt="Sven Gottwald"/><br /><sub><b>Sven Gottwald</b></sub></a><br /><a href="#infra-svengo" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://liberapay.com/techknowlogick/"><img src="https://avatars1.githubusercontent.com/u/164197?v=4?s=100" width="100px;" alt="techknowlogick"/><br /><sub><b>techknowlogick</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=techknowlogick" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://log.c5t.org/about/"><img src="https://avatars1.githubusercontent.com/u/1449568?v=4?s=100" width="100px;" alt="waja"/><br /><sub><b>waja</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=waja" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://scottalbertson.com"><img src="https://avatars2.githubusercontent.com/u/154463?v=4?s=100" width="100px;" alt="Scott Albertson"/><br /><sub><b>Scott Albertson</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=salbertson" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/huddlesj"><img src="https://avatars1.githubusercontent.com/u/11966535?v=4?s=100" width="100px;" alt="Jason Huddleston"/><br /><sub><b>Jason Huddleston</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=huddlesj" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://npstr.space/"><img src="https://avatars3.githubusercontent.com/u/6048348?v=4?s=100" width="100px;" alt="Napster"/><br /><sub><b>Napster</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=napstr" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/darknode"><img src="https://avatars1.githubusercontent.com/u/809429?v=4?s=100" width="100px;" alt="Maxim"/><br /><sub><b>Maxim</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=darknode" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=darknode" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://schmitt.cat"><img src="https://avatars0.githubusercontent.com/u/17984549?v=4?s=100" width="100px;" alt="Max Schmitt"/><br /><sub><b>Max Schmitt</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mxschmitt" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/cron410"><img src="https://avatars1.githubusercontent.com/u/3082899?v=4?s=100" width="100px;" alt="cron410"/><br /><sub><b>cron410</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=cron410" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Cardoso222"><img src="https://avatars3.githubusercontent.com/u/7026517?v=4?s=100" width="100px;" alt="Paulo Henrique"/><br /><sub><b>Paulo Henrique</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Cardoso222" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://coded.io"><img src="https://avatars0.githubusercontent.com/u/107097?v=4?s=100" width="100px;" alt="Kaleb Elwert"/><br /><sub><b>Kaleb Elwert</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=belak" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/wmbutler"><img src="https://avatars1.githubusercontent.com/u/1254810?v=4?s=100" width="100px;" alt="Bill Butler"/><br /><sub><b>Bill Butler</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=wmbutler" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.mariotacke.io"><img src="https://avatars2.githubusercontent.com/u/4942019?v=4?s=100" width="100px;" alt="Mario Tacke"/><br /><sub><b>Mario Tacke</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mariotacke" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/darknode"><img src="https://avatars1.githubusercontent.com/u/809429?v=4?s=100" width="100px;" alt="Maxim"/><br /><sub><b>Maxim</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=darknode" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=darknode" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://schmitt.cat"><img src="https://avatars0.githubusercontent.com/u/17984549?v=4?s=100" width="100px;" alt="Max Schmitt"/><br /><sub><b>Max Schmitt</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mxschmitt" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/cron410"><img src="https://avatars1.githubusercontent.com/u/3082899?v=4?s=100" width="100px;" alt="cron410"/><br /><sub><b>cron410</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=cron410" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Cardoso222"><img src="https://avatars3.githubusercontent.com/u/7026517?v=4?s=100" width="100px;" alt="Paulo Henrique"/><br /><sub><b>Paulo Henrique</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Cardoso222" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://coded.io"><img src="https://avatars0.githubusercontent.com/u/107097?v=4?s=100" width="100px;" alt="Kaleb Elwert"/><br /><sub><b>Kaleb Elwert</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=belak" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/wmbutler"><img src="https://avatars1.githubusercontent.com/u/1254810?v=4?s=100" width="100px;" alt="Bill Butler"/><br /><sub><b>Bill Butler</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=wmbutler" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.mariotacke.io"><img src="https://avatars2.githubusercontent.com/u/4942019?v=4?s=100" width="100px;" alt="Mario Tacke"/><br /><sub><b>Mario Tacke</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mariotacke" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://markwoodbridge.com"><img src="https://avatars2.githubusercontent.com/u/1101318?v=4?s=100" width="100px;" alt="Mark Woodbridge"/><br /><sub><b>Mark Woodbridge</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mrw34" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://simme.dev"><img src="https://avatars0.githubusercontent.com/u/1596025?v=4?s=100" width="100px;" alt="Simon Aronsson"/><br /><sub><b>Simon Aronsson</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=simskij" title="Code">💻</a> <a href="#maintenance-simskij" title="Maintenance">🚧</a> <a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3Asimskij" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/containrrr/watchtower/commits?author=simskij" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/Ansem93"><img src="https://avatars3.githubusercontent.com/u/6626218?v=4?s=100" width="100px;" alt="Ansem93"/><br /><sub><b>Ansem93</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Ansem93" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/lukapeschke"><img src="https://avatars1.githubusercontent.com/u/17085536?v=4?s=100" width="100px;" alt="Luka Peschke"/><br /><sub><b>Luka Peschke</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=lukapeschke" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=lukapeschke" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/zoispag"><img src="https://avatars0.githubusercontent.com/u/21138205?v=4?s=100" width="100px;" alt="Zois Pagoulatos"/><br /><sub><b>Zois Pagoulatos</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=zoispag" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3Azoispag" title="Reviewed Pull Requests">👀</a> <a href="#maintenance-zoispag" title="Maintenance">🚧</a></td>
|
||||
<td align="center"><a href="https://alexandre.menif.name"><img src="https://avatars0.githubusercontent.com/u/16152103?v=4?s=100" width="100px;" alt="Alexandre Menif"/><br /><sub><b>Alexandre Menif</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=alexandremenif" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/chugunov"><img src="https://avatars1.githubusercontent.com/u/4140479?v=4?s=100" width="100px;" alt="Andrey"/><br /><sub><b>Andrey</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=chugunov" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://markwoodbridge.com"><img src="https://avatars2.githubusercontent.com/u/1101318?v=4?s=100" width="100px;" alt="Mark Woodbridge"/><br /><sub><b>Mark Woodbridge</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mrw34" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://simme.dev"><img src="https://avatars0.githubusercontent.com/u/1596025?v=4?s=100" width="100px;" alt="Simon Aronsson"/><br /><sub><b>Simon Aronsson</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=simskij" title="Code">💻</a> <a href="#maintenance-simskij" title="Maintenance">🚧</a> <a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3Asimskij" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/containrrr/watchtower/commits?author=simskij" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Ansem93"><img src="https://avatars3.githubusercontent.com/u/6626218?v=4?s=100" width="100px;" alt="Ansem93"/><br /><sub><b>Ansem93</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Ansem93" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lukapeschke"><img src="https://avatars1.githubusercontent.com/u/17085536?v=4?s=100" width="100px;" alt="Luka Peschke"/><br /><sub><b>Luka Peschke</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=lukapeschke" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=lukapeschke" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zoispag"><img src="https://avatars0.githubusercontent.com/u/21138205?v=4?s=100" width="100px;" alt="Zois Pagoulatos"/><br /><sub><b>Zois Pagoulatos</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=zoispag" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3Azoispag" title="Reviewed Pull Requests">👀</a> <a href="#maintenance-zoispag" title="Maintenance">🚧</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://alexandre.menif.name"><img src="https://avatars0.githubusercontent.com/u/16152103?v=4?s=100" width="100px;" alt="Alexandre Menif"/><br /><sub><b>Alexandre Menif</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=alexandremenif" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/chugunov"><img src="https://avatars1.githubusercontent.com/u/4140479?v=4?s=100" width="100px;" alt="Andrey"/><br /><sub><b>Andrey</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=chugunov" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://noplanman.ch"><img src="https://avatars3.githubusercontent.com/u/9423417?v=4?s=100" width="100px;" alt="Armando Lüscher"/><br /><sub><b>Armando Lüscher</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=noplanman" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/rjbudke"><img src="https://avatars2.githubusercontent.com/u/273485?v=4?s=100" width="100px;" alt="Ryan Budke"/><br /><sub><b>Ryan Budke</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=rjbudke" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="http://kaloyan.raev.name"><img src="https://avatars2.githubusercontent.com/u/468091?v=4?s=100" width="100px;" alt="Kaloyan Raev"/><br /><sub><b>Kaloyan Raev</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=kaloyan-raev" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=kaloyan-raev" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/sixth"><img src="https://avatars3.githubusercontent.com/u/11591445?v=4?s=100" width="100px;" alt="sixth"/><br /><sub><b>sixth</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=sixth" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://foosel.net"><img src="https://avatars0.githubusercontent.com/u/83657?v=4?s=100" width="100px;" alt="Gina Häußge"/><br /><sub><b>Gina Häußge</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=foosel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/8ear"><img src="https://avatars0.githubusercontent.com/u/10329648?v=4?s=100" width="100px;" alt="Max H."/><br /><sub><b>Max H.</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=8ear" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://pjknkda.github.io"><img src="https://avatars0.githubusercontent.com/u/4986524?v=4?s=100" width="100px;" alt="Jungkook Park"/><br /><sub><b>Jungkook Park</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=pjknkda" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://noplanman.ch"><img src="https://avatars3.githubusercontent.com/u/9423417?v=4?s=100" width="100px;" alt="Armando Lüscher"/><br /><sub><b>Armando Lüscher</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=noplanman" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rjbudke"><img src="https://avatars2.githubusercontent.com/u/273485?v=4?s=100" width="100px;" alt="Ryan Budke"/><br /><sub><b>Ryan Budke</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=rjbudke" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://kaloyan.raev.name"><img src="https://avatars2.githubusercontent.com/u/468091?v=4?s=100" width="100px;" alt="Kaloyan Raev"/><br /><sub><b>Kaloyan Raev</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=kaloyan-raev" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=kaloyan-raev" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sixth"><img src="https://avatars3.githubusercontent.com/u/11591445?v=4?s=100" width="100px;" alt="sixth"/><br /><sub><b>sixth</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=sixth" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://foosel.net"><img src="https://avatars0.githubusercontent.com/u/83657?v=4?s=100" width="100px;" alt="Gina Häußge"/><br /><sub><b>Gina Häußge</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=foosel" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/8ear"><img src="https://avatars0.githubusercontent.com/u/10329648?v=4?s=100" width="100px;" alt="Max H."/><br /><sub><b>Max H.</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=8ear" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://pjknkda.github.io"><img src="https://avatars0.githubusercontent.com/u/4986524?v=4?s=100" width="100px;" alt="Jungkook Park"/><br /><sub><b>Jungkook Park</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=pjknkda" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://achfrag.net"><img src="https://avatars1.githubusercontent.com/u/5753622?v=4?s=100" width="100px;" alt="Jan Kristof Nidzwetzki"/><br /><sub><b>Jan Kristof Nidzwetzki</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=jnidzwetzki" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://www.lukaselsner.de"><img src="https://avatars0.githubusercontent.com/u/1413542?v=4?s=100" width="100px;" alt="lukas"/><br /><sub><b>lukas</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mindrunner" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://codingcoffee.dev"><img src="https://avatars3.githubusercontent.com/u/13611153?v=4?s=100" width="100px;" alt="Ameya Shenoy"/><br /><sub><b>Ameya Shenoy</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=codingCoffee" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/raymondelooff"><img src="https://avatars0.githubusercontent.com/u/9716806?v=4?s=100" width="100px;" alt="Raymon de Looff"/><br /><sub><b>Raymon de Looff</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=raymondelooff" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://codemonkeylabs.com"><img src="https://avatars2.githubusercontent.com/u/704034?v=4?s=100" width="100px;" alt="John Clayton"/><br /><sub><b>John Clayton</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=jsclayton" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Germs2004"><img src="https://avatars2.githubusercontent.com/u/5519340?v=4?s=100" width="100px;" alt="Germs2004"/><br /><sub><b>Germs2004</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Germs2004" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/lukwil"><img src="https://avatars1.githubusercontent.com/u/30203234?v=4?s=100" width="100px;" alt="Lukas Willburger"/><br /><sub><b>Lukas Willburger</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=lukwil" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://achfrag.net"><img src="https://avatars1.githubusercontent.com/u/5753622?v=4?s=100" width="100px;" alt="Jan Kristof Nidzwetzki"/><br /><sub><b>Jan Kristof Nidzwetzki</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=jnidzwetzki" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.lukaselsner.de"><img src="https://avatars0.githubusercontent.com/u/1413542?v=4?s=100" width="100px;" alt="lukas"/><br /><sub><b>lukas</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mindrunner" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://codingcoffee.dev"><img src="https://avatars3.githubusercontent.com/u/13611153?v=4?s=100" width="100px;" alt="Ameya Shenoy"/><br /><sub><b>Ameya Shenoy</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=codingCoffee" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/raymondelooff"><img src="https://avatars0.githubusercontent.com/u/9716806?v=4?s=100" width="100px;" alt="Raymon de Looff"/><br /><sub><b>Raymon de Looff</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=raymondelooff" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://codemonkeylabs.com"><img src="https://avatars2.githubusercontent.com/u/704034?v=4?s=100" width="100px;" alt="John Clayton"/><br /><sub><b>John Clayton</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=jsclayton" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Germs2004"><img src="https://avatars2.githubusercontent.com/u/5519340?v=4?s=100" width="100px;" alt="Germs2004"/><br /><sub><b>Germs2004</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Germs2004" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/lukwil"><img src="https://avatars1.githubusercontent.com/u/30203234?v=4?s=100" width="100px;" alt="Lukas Willburger"/><br /><sub><b>Lukas Willburger</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=lukwil" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/auanasgheps"><img src="https://avatars2.githubusercontent.com/u/20586878?v=4?s=100" width="100px;" alt="Oliver Cervera"/><br /><sub><b>Oliver Cervera</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=auanasgheps" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/victorcmoura"><img src="https://avatars1.githubusercontent.com/u/26290053?v=4?s=100" width="100px;" alt="Victor Moura"/><br /><sub><b>Victor Moura</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=victorcmoura" title="Tests">⚠️</a> <a href="https://github.com/containrrr/watchtower/commits?author=victorcmoura" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=victorcmoura" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/mbrandau"><img src="https://avatars3.githubusercontent.com/u/12972798?v=4?s=100" width="100px;" alt="Maximilian Brandau"/><br /><sub><b>Maximilian Brandau</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mbrandau" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=mbrandau" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/aneisch"><img src="https://avatars1.githubusercontent.com/u/6991461?v=4?s=100" width="100px;" alt="Andrew"/><br /><sub><b>Andrew</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=aneisch" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/sixcorners"><img src="https://avatars0.githubusercontent.com/u/585501?v=4?s=100" width="100px;" alt="sixcorners"/><br /><sub><b>sixcorners</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=sixcorners" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://piksel.se"><img src="https://avatars2.githubusercontent.com/u/807383?v=4?s=100" width="100px;" alt="nils måsén"/><br /><sub><b>nils måsén</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=piksel" title="Documentation">📖</a> <a href="https://github.com/containrrr/watchtower/commits?author=piksel" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://arnested.dk"><img src="https://avatars2.githubusercontent.com/u/190005?v=4?s=100" width="100px;" alt="Arne Jørgensen"/><br /><sub><b>Arne Jørgensen</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=arnested" title="Tests">⚠️</a> <a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3Aarnested" title="Reviewed Pull Requests">👀</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/auanasgheps"><img src="https://avatars2.githubusercontent.com/u/20586878?v=4?s=100" width="100px;" alt="Oliver Cervera"/><br /><sub><b>Oliver Cervera</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=auanasgheps" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/victorcmoura"><img src="https://avatars1.githubusercontent.com/u/26290053?v=4?s=100" width="100px;" alt="Victor Moura"/><br /><sub><b>Victor Moura</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=victorcmoura" title="Tests">⚠️</a> <a href="https://github.com/containrrr/watchtower/commits?author=victorcmoura" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=victorcmoura" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mbrandau"><img src="https://avatars3.githubusercontent.com/u/12972798?v=4?s=100" width="100px;" alt="Maximilian Brandau"/><br /><sub><b>Maximilian Brandau</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mbrandau" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=mbrandau" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/aneisch"><img src="https://avatars1.githubusercontent.com/u/6991461?v=4?s=100" width="100px;" alt="Andrew"/><br /><sub><b>Andrew</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=aneisch" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/sixcorners"><img src="https://avatars0.githubusercontent.com/u/585501?v=4?s=100" width="100px;" alt="sixcorners"/><br /><sub><b>sixcorners</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=sixcorners" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://piksel.se"><img src="https://avatars2.githubusercontent.com/u/807383?v=4?s=100" width="100px;" alt="nils måsén"/><br /><sub><b>nils måsén</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=piksel" title="Documentation">📖</a> <a href="https://github.com/containrrr/watchtower/commits?author=piksel" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://arnested.dk"><img src="https://avatars2.githubusercontent.com/u/190005?v=4?s=100" width="100px;" alt="Arne Jørgensen"/><br /><sub><b>Arne Jørgensen</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=arnested" title="Tests">⚠️</a> <a href="https://github.com/containrrr/watchtower/pulls?q=is%3Apr+reviewed-by%3Aarnested" title="Reviewed Pull Requests">👀</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/patski123"><img src="https://avatars1.githubusercontent.com/u/19295295?v=4?s=100" width="100px;" alt="PatSki123"/><br /><sub><b>PatSki123</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=patski123" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://rubyroidlabs.com/"><img src="https://avatars2.githubusercontent.com/u/624999?v=4?s=100" width="100px;" alt="Valentine Zavadsky"/><br /><sub><b>Valentine Zavadsky</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Saicheg" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=Saicheg" title="Documentation">📖</a> <a href="https://github.com/containrrr/watchtower/commits?author=Saicheg" title="Tests">⚠️</a></td>
|
||||
<td align="center"><a href="https://github.com/bopoh24"><img src="https://avatars2.githubusercontent.com/u/4086631?v=4?s=100" width="100px;" alt="Alexander Voronin"/><br /><sub><b>Alexander Voronin</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=bopoh24" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/issues?q=author%3Abopoh24" title="Bug reports">🐛</a></td>
|
||||
<td align="center"><a href="http://www.teqneers.de"><img src="https://avatars0.githubusercontent.com/u/788989?v=4?s=100" width="100px;" alt="Oliver Mueller"/><br /><sub><b>Oliver Mueller</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=ogmueller" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/tammert"><img src="https://avatars0.githubusercontent.com/u/8885250?v=4?s=100" width="100px;" alt="Sebastiaan Tammer"/><br /><sub><b>Sebastiaan Tammer</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=tammert" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Miosame"><img src="https://avatars1.githubusercontent.com/u/8201077?v=4?s=100" width="100px;" alt="miosame"/><br /><sub><b>miosame</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=miosame" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://mtz.gr"><img src="https://avatars3.githubusercontent.com/u/590246?v=4?s=100" width="100px;" alt="Andrew Metzger"/><br /><sub><b>Andrew Metzger</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/issues?q=author%3Aandrewjmetzger" title="Bug reports">🐛</a> <a href="#example-andrewjmetzger" title="Examples">💡</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/patski123"><img src="https://avatars1.githubusercontent.com/u/19295295?v=4?s=100" width="100px;" alt="PatSki123"/><br /><sub><b>PatSki123</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=patski123" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://rubyroidlabs.com/"><img src="https://avatars2.githubusercontent.com/u/624999?v=4?s=100" width="100px;" alt="Valentine Zavadsky"/><br /><sub><b>Valentine Zavadsky</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Saicheg" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=Saicheg" title="Documentation">📖</a> <a href="https://github.com/containrrr/watchtower/commits?author=Saicheg" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bopoh24"><img src="https://avatars2.githubusercontent.com/u/4086631?v=4?s=100" width="100px;" alt="Alexander Voronin"/><br /><sub><b>Alexander Voronin</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=bopoh24" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/issues?q=author%3Abopoh24" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.teqneers.de"><img src="https://avatars0.githubusercontent.com/u/788989?v=4?s=100" width="100px;" alt="Oliver Mueller"/><br /><sub><b>Oliver Mueller</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=ogmueller" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tammert"><img src="https://avatars0.githubusercontent.com/u/8885250?v=4?s=100" width="100px;" alt="Sebastiaan Tammer"/><br /><sub><b>Sebastiaan Tammer</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=tammert" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Miosame"><img src="https://avatars1.githubusercontent.com/u/8201077?v=4?s=100" width="100px;" alt="miosame"/><br /><sub><b>miosame</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=miosame" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://mtz.gr"><img src="https://avatars3.githubusercontent.com/u/590246?v=4?s=100" width="100px;" alt="Andrew Metzger"/><br /><sub><b>Andrew Metzger</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/issues?q=author%3Aandrewjmetzger" title="Bug reports">🐛</a> <a href="#example-andrewjmetzger" title="Examples">💡</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/pgrimaud"><img src="https://avatars1.githubusercontent.com/u/1866496?v=4?s=100" width="100px;" alt="Pierre Grimaud"/><br /><sub><b>Pierre Grimaud</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=pgrimaud" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/mattdoran"><img src="https://avatars0.githubusercontent.com/u/577779?v=4?s=100" width="100px;" alt="Matt Doran"/><br /><sub><b>Matt Doran</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mattdoran" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/MihailITPlace"><img src="https://avatars2.githubusercontent.com/u/28401551?v=4?s=100" width="100px;" alt="MihailITPlace"/><br /><sub><b>MihailITPlace</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=MihailITPlace" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/bugficks"><img src="https://avatars1.githubusercontent.com/u/2992895?v=4?s=100" width="100px;" alt="bugficks"/><br /><sub><b>bugficks</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=bugficks" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=bugficks" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/MichaelSp"><img src="https://avatars0.githubusercontent.com/u/448282?v=4?s=100" width="100px;" alt="Michael"/><br /><sub><b>Michael</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=MichaelSp" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/jokay"><img src="https://avatars0.githubusercontent.com/u/18613935?v=4?s=100" width="100px;" alt="D. Domig"/><br /><sub><b>D. Domig</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=jokay" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://maxwells-daemon.io"><img src="https://avatars1.githubusercontent.com/u/260084?v=4?s=100" width="100px;" alt="Ben Osheroff"/><br /><sub><b>Ben Osheroff</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=osheroff" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pgrimaud"><img src="https://avatars1.githubusercontent.com/u/1866496?v=4?s=100" width="100px;" alt="Pierre Grimaud"/><br /><sub><b>Pierre Grimaud</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=pgrimaud" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mattdoran"><img src="https://avatars0.githubusercontent.com/u/577779?v=4?s=100" width="100px;" alt="Matt Doran"/><br /><sub><b>Matt Doran</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=mattdoran" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MihailITPlace"><img src="https://avatars2.githubusercontent.com/u/28401551?v=4?s=100" width="100px;" alt="MihailITPlace"/><br /><sub><b>MihailITPlace</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=MihailITPlace" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bugficks"><img src="https://avatars1.githubusercontent.com/u/2992895?v=4?s=100" width="100px;" alt="bugficks"/><br /><sub><b>bugficks</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=bugficks" title="Code">💻</a> <a href="https://github.com/containrrr/watchtower/commits?author=bugficks" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/MichaelSp"><img src="https://avatars0.githubusercontent.com/u/448282?v=4?s=100" width="100px;" alt="Michael"/><br /><sub><b>Michael</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=MichaelSp" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jokay"><img src="https://avatars0.githubusercontent.com/u/18613935?v=4?s=100" width="100px;" alt="D. Domig"/><br /><sub><b>D. Domig</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=jokay" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://maxwells-daemon.io"><img src="https://avatars1.githubusercontent.com/u/260084?v=4?s=100" width="100px;" alt="Ben Osheroff"/><br /><sub><b>Ben Osheroff</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=osheroff" title="Code">💻</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/dhet"><img src="https://avatars3.githubusercontent.com/u/2668621?v=4?s=100" width="100px;" alt="David H."/><br /><sub><b>David H.</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=dhet" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://www.gridgeo.com"><img src="https://avatars1.githubusercontent.com/u/671887?v=4?s=100" width="100px;" alt="Chander Ganesan"/><br /><sub><b>Chander Ganesan</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=chander" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/yrien30"><img src="https://avatars1.githubusercontent.com/u/26816162?v=4?s=100" width="100px;" alt="yrien30"/><br /><sub><b>yrien30</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=yrien30" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/ksurl"><img src="https://avatars1.githubusercontent.com/u/1371562?v=4?s=100" width="100px;" alt="ksurl"/><br /><sub><b>ksurl</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=ksurl" title="Documentation">📖</a> <a href="https://github.com/containrrr/watchtower/commits?author=ksurl" title="Code">💻</a> <a href="#infra-ksurl" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
<td align="center"><a href="https://github.com/rg9400"><img src="https://avatars2.githubusercontent.com/u/39887349?v=4?s=100" width="100px;" alt="rg9400"/><br /><sub><b>rg9400</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=rg9400" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/tkalus"><img src="https://avatars2.githubusercontent.com/u/287181?v=4?s=100" width="100px;" alt="Turtle Kalus"/><br /><sub><b>Turtle Kalus</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=tkalus" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/SrihariThalla"><img src="https://avatars1.githubusercontent.com/u/7479937?v=4?s=100" width="100px;" alt="Srihari Thalla"/><br /><sub><b>Srihari Thalla</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=SrihariThalla" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/dhet"><img src="https://avatars3.githubusercontent.com/u/2668621?v=4?s=100" width="100px;" alt="David H."/><br /><sub><b>David H.</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=dhet" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://www.gridgeo.com"><img src="https://avatars1.githubusercontent.com/u/671887?v=4?s=100" width="100px;" alt="Chander Ganesan"/><br /><sub><b>Chander Ganesan</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=chander" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yrien30"><img src="https://avatars1.githubusercontent.com/u/26816162?v=4?s=100" width="100px;" alt="yrien30"/><br /><sub><b>yrien30</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=yrien30" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ksurl"><img src="https://avatars1.githubusercontent.com/u/1371562?v=4?s=100" width="100px;" alt="ksurl"/><br /><sub><b>ksurl</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=ksurl" title="Documentation">📖</a> <a href="https://github.com/containrrr/watchtower/commits?author=ksurl" title="Code">💻</a> <a href="#infra-ksurl" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/rg9400"><img src="https://avatars2.githubusercontent.com/u/39887349?v=4?s=100" width="100px;" alt="rg9400"/><br /><sub><b>rg9400</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=rg9400" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tkalus"><img src="https://avatars2.githubusercontent.com/u/287181?v=4?s=100" width="100px;" alt="Turtle Kalus"/><br /><sub><b>Turtle Kalus</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=tkalus" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/SrihariThalla"><img src="https://avatars1.githubusercontent.com/u/7479937?v=4?s=100" width="100px;" alt="Srihari Thalla"/><br /><sub><b>Srihari Thalla</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=SrihariThalla" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://nymous.io"><img src="https://avatars1.githubusercontent.com/u/4216559?v=4?s=100" width="100px;" alt="Thomas Gaudin"/><br /><sub><b>Thomas Gaudin</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=nymous" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://indigo.re/"><img src="https://avatars.githubusercontent.com/u/2804645?v=4?s=100" width="100px;" alt="hydrargyrum"/><br /><sub><b>hydrargyrum</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=hydrargyrum" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://reinout.vanrees.org"><img src="https://avatars.githubusercontent.com/u/121433?v=4?s=100" width="100px;" alt="Reinout van Rees"/><br /><sub><b>Reinout van Rees</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=reinout" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/DasSkelett"><img src="https://avatars.githubusercontent.com/u/28812678?v=4?s=100" width="100px;" alt="DasSkelett"/><br /><sub><b>DasSkelett</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=DasSkelett" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/zenjabba"><img src="https://avatars.githubusercontent.com/u/679864?v=4?s=100" width="100px;" alt="zenjabba"/><br /><sub><b>zenjabba</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=zenjabba" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://quan.io"><img src="https://avatars.githubusercontent.com/u/3526705?v=4?s=100" width="100px;" alt="Dan Quan"/><br /><sub><b>Dan Quan</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=djquan" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/modem7"><img src="https://avatars.githubusercontent.com/u/4349962?v=4?s=100" width="100px;" alt="modem7"/><br /><sub><b>modem7</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=modem7" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://nymous.io"><img src="https://avatars1.githubusercontent.com/u/4216559?v=4?s=100" width="100px;" alt="Thomas Gaudin"/><br /><sub><b>Thomas Gaudin</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=nymous" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://indigo.re/"><img src="https://avatars.githubusercontent.com/u/2804645?v=4?s=100" width="100px;" alt="hydrargyrum"/><br /><sub><b>hydrargyrum</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=hydrargyrum" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://reinout.vanrees.org"><img src="https://avatars.githubusercontent.com/u/121433?v=4?s=100" width="100px;" alt="Reinout van Rees"/><br /><sub><b>Reinout van Rees</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=reinout" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/DasSkelett"><img src="https://avatars.githubusercontent.com/u/28812678?v=4?s=100" width="100px;" alt="DasSkelett"/><br /><sub><b>DasSkelett</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=DasSkelett" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zenjabba"><img src="https://avatars.githubusercontent.com/u/679864?v=4?s=100" width="100px;" alt="zenjabba"/><br /><sub><b>zenjabba</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=zenjabba" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://quan.io"><img src="https://avatars.githubusercontent.com/u/3526705?v=4?s=100" width="100px;" alt="Dan Quan"/><br /><sub><b>Dan Quan</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=djquan" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/modem7"><img src="https://avatars.githubusercontent.com/u/4349962?v=4?s=100" width="100px;" alt="modem7"/><br /><sub><b>modem7</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=modem7" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/hypnoglow"><img src="https://avatars.githubusercontent.com/u/4853075?v=4?s=100" width="100px;" alt="Igor Zibarev"/><br /><sub><b>Igor Zibarev</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=hypnoglow" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/patricegautier"><img src="https://avatars.githubusercontent.com/u/38435239?v=4?s=100" width="100px;" alt="Patrice"/><br /><sub><b>Patrice</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=patricegautier" title="Code">💻</a></td>
|
||||
<td align="center"><a href="http://jamesw.link/me"><img src="https://avatars.githubusercontent.com/u/8067792?v=4?s=100" width="100px;" alt="James White"/><br /><sub><b>James White</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=jamesmacwhite" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://ko-fi.com/foxite"><img src="https://avatars.githubusercontent.com/u/20421657?v=4?s=100" width="100px;" alt="Dirk Kok"/><br /><sub><b>Dirk Kok</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Foxite" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/EDIflyer"><img src="https://avatars.githubusercontent.com/u/13610277?v=4?s=100" width="100px;" alt="EDIflyer"/><br /><sub><b>EDIflyer</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=EDIflyer" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://github.com/jauderho"><img src="https://avatars.githubusercontent.com/u/13562?v=4?s=100" width="100px;" alt="Jauder Ho"/><br /><sub><b>Jauder Ho</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=jauderho" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://tamal.vercel.app/"><img src="https://avatars.githubusercontent.com/u/72851613?v=4?s=100" width="100px;" alt="Tamal Das "/><br /><sub><b>Tamal Das </b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=IAmTamal" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hypnoglow"><img src="https://avatars.githubusercontent.com/u/4853075?v=4?s=100" width="100px;" alt="Igor Zibarev"/><br /><sub><b>Igor Zibarev</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=hypnoglow" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/patricegautier"><img src="https://avatars.githubusercontent.com/u/38435239?v=4?s=100" width="100px;" alt="Patrice"/><br /><sub><b>Patrice</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=patricegautier" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://jamesw.link/me"><img src="https://avatars.githubusercontent.com/u/8067792?v=4?s=100" width="100px;" alt="James White"/><br /><sub><b>James White</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=jamesmacwhite" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://ko-fi.com/foxite"><img src="https://avatars.githubusercontent.com/u/20421657?v=4?s=100" width="100px;" alt="Dirk Kok"/><br /><sub><b>Dirk Kok</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=Foxite" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/EDIflyer"><img src="https://avatars.githubusercontent.com/u/13610277?v=4?s=100" width="100px;" alt="EDIflyer"/><br /><sub><b>EDIflyer</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=EDIflyer" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/jauderho"><img src="https://avatars.githubusercontent.com/u/13562?v=4?s=100" width="100px;" alt="Jauder Ho"/><br /><sub><b>Jauder Ho</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=jauderho" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://tamal.vercel.app/"><img src="https://avatars.githubusercontent.com/u/72851613?v=4?s=100" width="100px;" alt="Tamal Das "/><br /><sub><b>Tamal Das </b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=IAmTamal" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/testwill"><img src="https://avatars.githubusercontent.com/u/8717479?v=4?s=100" width="100px;" alt="guangwu"/><br /><sub><b>guangwu</b></sub></a><br /><a href="https://github.com/containrrr/watchtower/commits?author=testwill" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
|
|
25
cmd/root.go
25
cmd/root.go
|
@ -1,6 +1,7 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
|
@ -39,6 +40,7 @@ var (
|
|||
lifecycleHooks bool
|
||||
rollingRestart bool
|
||||
scope string
|
||||
labelPrecedence bool
|
||||
)
|
||||
|
||||
var rootCmd = NewRootCommand()
|
||||
|
@ -77,23 +79,8 @@ func Execute() {
|
|||
func PreRun(cmd *cobra.Command, _ []string) {
|
||||
f := cmd.PersistentFlags()
|
||||
flags.ProcessFlagAliases(f)
|
||||
|
||||
if enabled, _ := f.GetBool("no-color"); enabled {
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
DisableColors: true,
|
||||
})
|
||||
} else {
|
||||
// enable logrus built-in support for https://bixense.com/clicolors/
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
EnvironmentOverrideColors: true,
|
||||
})
|
||||
}
|
||||
|
||||
rawLogLevel, _ := f.GetString(`log-level`)
|
||||
if logLevel, err := log.ParseLevel(rawLogLevel); err != nil {
|
||||
log.Fatalf("Invalid log level: %s", err.Error())
|
||||
} else {
|
||||
log.SetLevel(logLevel)
|
||||
if err := flags.SetupLogging(f); err != nil {
|
||||
log.Fatalf("Failed to initialize logging: %s", err.Error())
|
||||
}
|
||||
|
||||
scheduleSpec, _ = f.GetString("schedule")
|
||||
|
@ -109,6 +96,7 @@ func PreRun(cmd *cobra.Command, _ []string) {
|
|||
lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks")
|
||||
rollingRestart, _ = f.GetBool("rolling-restart")
|
||||
scope, _ = f.GetString("scope")
|
||||
labelPrecedence, _ = f.GetBool("label-take-precedence")
|
||||
|
||||
if scope != "" {
|
||||
log.Debugf(`Using scope %q`, scope)
|
||||
|
@ -209,7 +197,7 @@ func Run(c *cobra.Command, names []string) {
|
|||
httpAPI.RegisterHandler(metricsHandler.Path, metricsHandler.Handle)
|
||||
}
|
||||
|
||||
if err := httpAPI.Start(enableUpdateAPI && !unblockHTTPAPI); err != nil && err != http.ErrServerClosed {
|
||||
if err := httpAPI.Start(enableUpdateAPI && !unblockHTTPAPI); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Error("failed to start API", err)
|
||||
}
|
||||
|
||||
|
@ -376,6 +364,7 @@ func runUpdatesWithNotifications(filter t.Filter) *metrics.Metric {
|
|||
MonitorOnly: monitorOnly,
|
||||
LifecycleHooks: lifecycleHooks,
|
||||
RollingRestart: rollingRestart,
|
||||
LabelPrecedence: labelPrecedence,
|
||||
}
|
||||
result, err := actions.Update(client, updateParams)
|
||||
if err != nil {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM --platform=$BUILDPLATFORM alpine:3.18.2 as alpine
|
||||
FROM --platform=$BUILDPLATFORM alpine:3.18.3 as alpine
|
||||
|
||||
RUN apk add --no-cache \
|
||||
ca-certificates \
|
||||
|
|
|
@ -27,6 +27,33 @@ In the example above, watchtower will execute an upgrade attempt on the containe
|
|||
|
||||
When no arguments are specified, watchtower will monitor all running containers.
|
||||
|
||||
## Secrets/Files
|
||||
|
||||
Some arguments can also reference a file, in which case the contents of the file are used as the value.
|
||||
This can be used to avoid putting secrets in the configuration file or command line.
|
||||
|
||||
The following arguments are currently supported (including their corresponding `WATCHTOWER_` environment variables):
|
||||
- `notification-url`
|
||||
- `notification-email-server-password`
|
||||
- `notification-slack-hook-url`
|
||||
- `notification-msteams-hook`
|
||||
- `notification-gotify-token`
|
||||
- `http-api-token`
|
||||
|
||||
### Example docker-compose usage
|
||||
```yaml
|
||||
secrets:
|
||||
access_token:
|
||||
file: access_token
|
||||
|
||||
services:
|
||||
watchtower:
|
||||
secrets:
|
||||
- access_token
|
||||
environment:
|
||||
- WATCHTOWER_HTTP_API_TOKEN=/run/secrets/access_token
|
||||
```
|
||||
|
||||
## Help
|
||||
Shows documentation about the supported flags.
|
||||
|
||||
|
@ -107,6 +134,17 @@ Environment Variable: WATCHTOWER_LOG_LEVEL
|
|||
Default: info
|
||||
```
|
||||
|
||||
## Logging format
|
||||
|
||||
Sets what logging format to use for console output.
|
||||
|
||||
```text
|
||||
Argument: --log-format, -l
|
||||
Environment Variable: WATCHTOWER_LOG_FORMAT
|
||||
Possible values: Auto, LogFmt, Pretty or JSON
|
||||
Default: Auto
|
||||
```
|
||||
|
||||
## ANSI colors
|
||||
Disable ANSI color escape codes in log output.
|
||||
|
||||
|
@ -151,7 +189,7 @@ Environment Variable: WATCHTOWER_INCLUDE_RESTARTING
|
|||
Will also include created and exited containers.
|
||||
|
||||
```text
|
||||
Argument: --include-stopped
|
||||
Argument: --include-stopped, -S
|
||||
Environment Variable: WATCHTOWER_INCLUDE_STOPPED
|
||||
Type: Boolean
|
||||
Default: false
|
||||
|
@ -178,7 +216,7 @@ Environment Variable: WATCHTOWER_POLL_INTERVAL
|
|||
```
|
||||
|
||||
## Filter by enable label
|
||||
Update containers that have a `com.centurylinklabs.watchtower.enable` label set to true.
|
||||
Monitor and update containers that have a `com.centurylinklabs.watchtower.enable` label set to true.
|
||||
|
||||
```text
|
||||
Argument: --label-enable
|
||||
|
@ -188,7 +226,7 @@ Environment Variable: WATCHTOWER_LABEL_ENABLE
|
|||
```
|
||||
|
||||
## Filter by disable label
|
||||
__Do not__ update containers that have `com.centurylinklabs.watchtower.enable` label set to false and
|
||||
__Do not__ Monitor and update containers that have `com.centurylinklabs.watchtower.enable` label set to false and
|
||||
no `--label-enable` argument is passed. Note that only one or the other (targeting by enable label) can be
|
||||
used at the same time to target containers.
|
||||
|
||||
|
@ -211,6 +249,19 @@ Environment Variable: WATCHTOWER_MONITOR_ONLY
|
|||
|
||||
Note that monitor-only can also be specified on a per-container basis with the `com.centurylinklabs.watchtower.monitor-only` label set on those containers.
|
||||
|
||||
See [With label taking precedence over arguments](#With-label-taking-precedence-over-arguments) for behavior when both argument and label are set
|
||||
|
||||
## With label taking precedence over arguments
|
||||
|
||||
By default, arguments will take precedence over labels. This means that if you set `WATCHTOWER_MONITOR_ONLY` to true or use `--monitor-only`, a container with `com.centurylinklabs.watchtower.monitor-only` set to false will not be updated. If you set `WATCHTOWER_LABEL_TAKE_PRECEDENCE` to true or use `--label-take-precedence`, then the container will also be updated. This also apply to the no pull option. if you set `WATCHTOWER_NO_PULL` to true or use `--no-pull`, a container with `com.centurylinklabs.watchtower.no-pull` set to false will not pull the new image. If you set `WATCHTOWER_LABEL_TAKE_PRECEDENCE` to true or use `--label-take-precedence`, then the container will pull image
|
||||
|
||||
```text
|
||||
Argument: --label-take-precedence
|
||||
Environment Variable: WATCHTOWER_LABEL_TAKE_PRECEDENCE
|
||||
Type: Boolean
|
||||
Default: false
|
||||
```
|
||||
|
||||
## Without restarting containers
|
||||
Do not restart containers after updating. This option can be useful when the start of the containers
|
||||
is managed by an external system such as systemd.
|
||||
|
@ -237,6 +288,8 @@ Environment Variable: WATCHTOWER_NO_PULL
|
|||
Note that no-pull can also be specified on a per-container basis with the
|
||||
`com.centurylinklabs.watchtower.no-pull` label set on those containers.
|
||||
|
||||
See [With label taking precedence over arguments](#With-label-taking-precedence-over-arguments) for behavior when both argument and label are set
|
||||
|
||||
## Without sending a startup message
|
||||
Do not send a message after watchtower started. Otherwise there will be an info-level notification.
|
||||
|
||||
|
@ -251,7 +304,7 @@ Environment Variable: WATCHTOWER_NO_STARTUP_MESSAGE
|
|||
Run an update attempt against a container name list one time immediately and exit.
|
||||
|
||||
```text
|
||||
Argument: --run-once
|
||||
Argument: --run-once, -R
|
||||
Environment Variable: WATCHTOWER_RUN_ONCE
|
||||
Type: Boolean
|
||||
Default: false
|
||||
|
@ -270,6 +323,7 @@ Environment Variable: WATCHTOWER_HTTP_API_UPDATE
|
|||
|
||||
## HTTP API Token
|
||||
Sets an authentication token to HTTP API requests.
|
||||
Can also reference a file, in which case the contents of the file are used.
|
||||
|
||||
```text
|
||||
Argument: --http-api-token
|
||||
|
@ -375,3 +429,21 @@ Returns a success exit code to enable usage with docker `HEALTHCHECK`. This chec
|
|||
```text
|
||||
Argument: --health-check
|
||||
```
|
||||
|
||||
## Programatic Output (porcelain)
|
||||
|
||||
Writes the session results to STDOUT using a stable, machine-readable format (indicated by the argument VERSION).
|
||||
|
||||
Alias for:
|
||||
|
||||
```text
|
||||
--notification-url logger://
|
||||
--notification-log-stdout
|
||||
--notification-report
|
||||
--notification-template porcelain.VERSION.summary-no-log
|
||||
|
||||
Argument: --porcelain, -P
|
||||
Environment Variable: WATCHTOWER_PORCELAIN
|
||||
Possible values: v1
|
||||
Default: -
|
||||
```
|
||||
|
|
|
@ -11,5 +11,5 @@ CONTAINER ID IMAGE STATUS PORTS
|
|||
6cc4d2a9d1a5 containrrr/watchtower Up 15 minutes watchtower
|
||||
```
|
||||
|
||||
Every few minutes watchtower will pull the latest _centurylink/wetty-cli_ image and compare it to the one that was used to run the "wetty" container. If it sees that the image has changed it will stop/remove the "wetty" container and then restart it using the new image and the same `docker run` options that were used to start the container initially (in this case, that would include the `-p 8080:3000` port mapping).
|
||||
Every day watchtower will pull the latest _centurylink/wetty-cli_ image and compare it to the one that was used to run the "wetty" container. If it sees that the image has changed it will stop/remove the "wetty" container and then restart it using the new image and the same `docker run` options that were used to start the container initially (in this case, that would include the `-p 8080:3000` port mapping).
|
||||
|
||||
|
|
|
@ -18,10 +18,11 @@ system, [logrus](http://github.com/sirupsen/logrus).
|
|||
|
||||
- `--notifications-level` (env. `WATCHTOWER_NOTIFICATIONS_LEVEL`): Controls the log level which is used for the notifications. If omitted, the default log level is `info`. Possible values are: `panic`, `fatal`, `error`, `warn`, `info`, `debug` or `trace`.
|
||||
- `--notifications-hostname` (env. `WATCHTOWER_NOTIFICATIONS_HOSTNAME`): Custom hostname specified in subject/title. Useful to override the operating system hostname.
|
||||
- `--notifications-delay` (env. `WATCHTOWER_NOTIFICATION_DELAY`): Delay before sending notifications expressed in seconds.
|
||||
- `--notifications-delay` (env. `WATCHTOWER_NOTIFICATIONS_DELAY`): Delay before sending notifications expressed in seconds.
|
||||
- Watchtower will post a notification every time it is started. This behavior [can be changed](https://containrrr.github.io/watchtower/arguments/#without_sending_a_startup_message) with an argument.
|
||||
- `notification-title-tag` (env. `WATCHTOWER_NOTIFICATION_TITLE_TAG`): Prefix to include in the title. Useful when running multiple watchtowers.
|
||||
- `notification-skip-title` (env. `WATCHTOWER_NOTIFICATION_SKIP_TITLE`): Do not pass the title param to notifications. This will not pass a dynamic title override to notification services. If no title is configured for the service, it will remove the title all together.
|
||||
- `--notification-title-tag` (env. `WATCHTOWER_NOTIFICATION_TITLE_TAG`): Prefix to include in the title. Useful when running multiple watchtowers.
|
||||
- `--notification-skip-title` (env. `WATCHTOWER_NOTIFICATION_SKIP_TITLE`): Do not pass the title param to notifications. This will not pass a dynamic title override to notification services. If no title is configured for the service, it will remove the title all together.
|
||||
- `--notification-log-stdout` (env. `WATCHTOWER_NOTIFICATION_LOG_STDOUT`): Enable output from `logger://` shoutrrr service to stdout.
|
||||
|
||||
## [shoutrrr](https://github.com/containrrr/shoutrrr) notifications
|
||||
|
||||
|
@ -30,7 +31,7 @@ To send notifications via shoutrrr, the following command-line options, or their
|
|||
- `--notification-url` (env. `WATCHTOWER_NOTIFICATION_URL`): The shoutrrr service URL to be used. This option can also reference a file, in which case the contents of the file are used.
|
||||
|
||||
|
||||
Go to [containrrr.dev/shoutrrr/v0.7/services/overview](https://containrrr.dev/shoutrrr/v0.6/services/overview) to
|
||||
Go to [containrrr.dev/shoutrrr/v0.8/services/overview](https://containrrr.dev/shoutrrr/v0.8/services/overview) to
|
||||
learn more about the different service URLs you can use. You can define multiple services by space separating the
|
||||
URLs. (See example below)
|
||||
|
||||
|
|
|
@ -124,7 +124,7 @@ in a volume that may be mounted onto your watchtower container.
|
|||
|
||||
1. Create the Dockerfile (contents below):
|
||||
```Dockerfile
|
||||
FROM golang:1.16
|
||||
FROM golang:1.17
|
||||
|
||||
ENV GO111MODULE off
|
||||
ENV CGO_ENABLED 0
|
||||
|
|
16
go.mod
16
go.mod
|
@ -3,10 +3,10 @@ module github.com/containrrr/watchtower
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/containrrr/shoutrrr v0.7.1
|
||||
github.com/docker/cli v24.0.5+incompatible
|
||||
github.com/containrrr/shoutrrr v0.8.0
|
||||
github.com/docker/cli v24.0.6+incompatible
|
||||
github.com/docker/distribution v2.8.2+incompatible
|
||||
github.com/docker/docker v24.0.5+incompatible
|
||||
github.com/docker/docker v24.0.6+incompatible
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/gomega v1.27.10
|
||||
|
@ -17,7 +17,7 @@ require (
|
|||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.16.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/net v0.15.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -28,7 +28,7 @@ require (
|
|||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.1 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
|
@ -37,7 +37,7 @@ require (
|
|||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
|
@ -56,8 +56,8 @@ require (
|
|||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
golang.org/x/text v0.13.0
|
||||
golang.org/x/time v0.1.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
|
|
|
@ -86,7 +86,7 @@ func (client MockClient) ExecuteCommand(_ t.ContainerID, command string, _ int)
|
|||
}
|
||||
|
||||
// IsContainerStale is true if not explicitly stated in TestData for the mock client
|
||||
func (client MockClient) IsContainerStale(cont t.Container) (bool, t.ImageID, error) {
|
||||
func (client MockClient) IsContainerStale(cont t.Container, params t.UpdateParams) (bool, t.ImageID, error) {
|
||||
stale, found := client.TestData.Staleness[cont.Name()]
|
||||
if !found {
|
||||
stale = true
|
||||
|
|
|
@ -33,8 +33,8 @@ func Update(client container.Client, params types.UpdateParams) (types.Report, e
|
|||
staleCheckFailed := 0
|
||||
|
||||
for i, targetContainer := range containers {
|
||||
stale, newestImage, err := client.IsContainerStale(targetContainer)
|
||||
shouldUpdate := stale && !params.NoRestart && !params.MonitorOnly && !targetContainer.IsMonitorOnly()
|
||||
stale, newestImage, err := client.IsContainerStale(targetContainer, params)
|
||||
shouldUpdate := stale && !params.NoRestart && !targetContainer.IsMonitorOnly(params)
|
||||
if err == nil && shouldUpdate {
|
||||
// Check to make sure we have all the necessary information for recreating the container
|
||||
err = targetContainer.VerifyConfiguration()
|
||||
|
@ -72,14 +72,12 @@ func Update(client container.Client, params types.UpdateParams) (types.Report, e
|
|||
UpdateImplicitRestart(containers)
|
||||
|
||||
var containersToUpdate []types.Container
|
||||
if !params.MonitorOnly {
|
||||
for _, c := range containers {
|
||||
if !c.IsMonitorOnly() {
|
||||
if !c.IsMonitorOnly(params) {
|
||||
containersToUpdate = append(containersToUpdate, c)
|
||||
progress.MarkForUpdate(c.ID())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if params.RollingRestart {
|
||||
progress.UpdateFailed(performRollingRestart(containersToUpdate, client, params))
|
||||
|
|
|
@ -178,13 +178,85 @@ var _ = Describe("the update action", func() {
|
|||
false,
|
||||
false,
|
||||
)
|
||||
_, err := actions.Update(client, types.UpdateParams{MonitorOnly: true})
|
||||
_, err := actions.Update(client, types.UpdateParams{Cleanup: true, MonitorOnly: true})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(0))
|
||||
})
|
||||
When("watchtower has been instructed to have label take precedence", func() {
|
||||
It("it should update containers when monitor only is set to false", func() {
|
||||
client := CreateMockClient(
|
||||
&TestData{
|
||||
//NameOfContainerToKeep: "test-container-02",
|
||||
Containers: []types.Container{
|
||||
CreateMockContainerWithConfig(
|
||||
"test-container-02",
|
||||
"test-container-02",
|
||||
"fake-image2:latest",
|
||||
false,
|
||||
false,
|
||||
time.Now(),
|
||||
&dockerContainer.Config{
|
||||
Labels: map[string]string{
|
||||
"com.centurylinklabs.watchtower.monitor-only": "false",
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
_, err := actions.Update(client, types.UpdateParams{Cleanup: true, MonitorOnly: true, LabelPrecedence: true})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(1))
|
||||
})
|
||||
It("it should update not containers when monitor only is set to true", func() {
|
||||
client := CreateMockClient(
|
||||
&TestData{
|
||||
//NameOfContainerToKeep: "test-container-02",
|
||||
Containers: []types.Container{
|
||||
CreateMockContainerWithConfig(
|
||||
"test-container-02",
|
||||
"test-container-02",
|
||||
"fake-image2:latest",
|
||||
false,
|
||||
false,
|
||||
time.Now(),
|
||||
&dockerContainer.Config{
|
||||
Labels: map[string]string{
|
||||
"com.centurylinklabs.watchtower.monitor-only": "true",
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
_, err := actions.Update(client, types.UpdateParams{Cleanup: true, MonitorOnly: true, LabelPrecedence: true})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(0))
|
||||
})
|
||||
It("it should update not containers when monitor only is not set", func() {
|
||||
client := CreateMockClient(
|
||||
&TestData{
|
||||
Containers: []types.Container{
|
||||
CreateMockContainer(
|
||||
"test-container-01",
|
||||
"test-container-01",
|
||||
"fake-image:latest",
|
||||
time.Now()),
|
||||
},
|
||||
},
|
||||
false,
|
||||
false,
|
||||
)
|
||||
_, err := actions.Update(client, types.UpdateParams{Cleanup: true, MonitorOnly: true, LabelPrecedence: true})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(0))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
When("watchtower has been instructed to run lifecycle hooks", func() {
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -24,9 +23,9 @@ var defaultInterval = int((time.Hour * 24).Seconds())
|
|||
// RegisterDockerFlags that are used directly by the docker api client
|
||||
func RegisterDockerFlags(rootCmd *cobra.Command) {
|
||||
flags := rootCmd.PersistentFlags()
|
||||
flags.StringP("host", "H", viper.GetString("DOCKER_HOST"), "daemon socket to connect to")
|
||||
flags.BoolP("tlsverify", "v", viper.GetBool("DOCKER_TLS_VERIFY"), "use TLS and verify the remote")
|
||||
flags.StringP("api-version", "a", viper.GetString("DOCKER_API_VERSION"), "api version to use by docker client")
|
||||
flags.StringP("host", "H", envString("DOCKER_HOST"), "daemon socket to connect to")
|
||||
flags.BoolP("tlsverify", "v", envBool("DOCKER_TLS_VERIFY"), "use TLS and verify the remote")
|
||||
flags.StringP("api-version", "a", envString("DOCKER_API_VERSION"), "api version to use by docker client")
|
||||
}
|
||||
|
||||
// RegisterSystemFlags that are used by watchtower to modify the program flow
|
||||
|
@ -35,132 +34,138 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
|
|||
flags.IntP(
|
||||
"interval",
|
||||
"i",
|
||||
viper.GetInt("WATCHTOWER_POLL_INTERVAL"),
|
||||
envInt("WATCHTOWER_POLL_INTERVAL"),
|
||||
"Poll interval (in seconds)")
|
||||
|
||||
flags.StringP(
|
||||
"schedule",
|
||||
"s",
|
||||
viper.GetString("WATCHTOWER_SCHEDULE"),
|
||||
envString("WATCHTOWER_SCHEDULE"),
|
||||
"The cron expression which defines when to update")
|
||||
|
||||
flags.DurationP(
|
||||
"stop-timeout",
|
||||
"t",
|
||||
viper.GetDuration("WATCHTOWER_TIMEOUT"),
|
||||
envDuration("WATCHTOWER_TIMEOUT"),
|
||||
"Timeout before a container is forcefully stopped")
|
||||
|
||||
flags.BoolP(
|
||||
"no-pull",
|
||||
"",
|
||||
viper.GetBool("WATCHTOWER_NO_PULL"),
|
||||
envBool("WATCHTOWER_NO_PULL"),
|
||||
"Do not pull any new images")
|
||||
|
||||
flags.BoolP(
|
||||
"no-restart",
|
||||
"",
|
||||
viper.GetBool("WATCHTOWER_NO_RESTART"),
|
||||
envBool("WATCHTOWER_NO_RESTART"),
|
||||
"Do not restart any containers")
|
||||
|
||||
flags.BoolP(
|
||||
"no-startup-message",
|
||||
"",
|
||||
viper.GetBool("WATCHTOWER_NO_STARTUP_MESSAGE"),
|
||||
envBool("WATCHTOWER_NO_STARTUP_MESSAGE"),
|
||||
"Prevents watchtower from sending a startup message")
|
||||
|
||||
flags.BoolP(
|
||||
"cleanup",
|
||||
"c",
|
||||
viper.GetBool("WATCHTOWER_CLEANUP"),
|
||||
envBool("WATCHTOWER_CLEANUP"),
|
||||
"Remove previously used images after updating")
|
||||
|
||||
flags.BoolP(
|
||||
"remove-volumes",
|
||||
"",
|
||||
viper.GetBool("WATCHTOWER_REMOVE_VOLUMES"),
|
||||
envBool("WATCHTOWER_REMOVE_VOLUMES"),
|
||||
"Remove attached volumes before updating")
|
||||
|
||||
flags.BoolP(
|
||||
"label-enable",
|
||||
"e",
|
||||
viper.GetBool("WATCHTOWER_LABEL_ENABLE"),
|
||||
envBool("WATCHTOWER_LABEL_ENABLE"),
|
||||
"Watch containers where the com.centurylinklabs.watchtower.enable label is true")
|
||||
|
||||
flags.StringP(
|
||||
"log-format",
|
||||
"l",
|
||||
viper.GetString("WATCHTOWER_LOG_FORMAT"),
|
||||
"Sets what logging format to use for console output. Possible values: Auto, LogFmt, Pretty, JSON")
|
||||
|
||||
flags.BoolP(
|
||||
"debug",
|
||||
"d",
|
||||
viper.GetBool("WATCHTOWER_DEBUG"),
|
||||
envBool("WATCHTOWER_DEBUG"),
|
||||
"Enable debug mode with verbose logging")
|
||||
|
||||
flags.BoolP(
|
||||
"trace",
|
||||
"",
|
||||
viper.GetBool("WATCHTOWER_TRACE"),
|
||||
envBool("WATCHTOWER_TRACE"),
|
||||
"Enable trace mode with very verbose logging - caution, exposes credentials")
|
||||
|
||||
flags.BoolP(
|
||||
"monitor-only",
|
||||
"m",
|
||||
viper.GetBool("WATCHTOWER_MONITOR_ONLY"),
|
||||
envBool("WATCHTOWER_MONITOR_ONLY"),
|
||||
"Will only monitor for new images, not update the containers")
|
||||
|
||||
flags.BoolP(
|
||||
"run-once",
|
||||
"R",
|
||||
viper.GetBool("WATCHTOWER_RUN_ONCE"),
|
||||
envBool("WATCHTOWER_RUN_ONCE"),
|
||||
"Run once now and exit")
|
||||
|
||||
flags.BoolP(
|
||||
"include-restarting",
|
||||
"",
|
||||
viper.GetBool("WATCHTOWER_INCLUDE_RESTARTING"),
|
||||
envBool("WATCHTOWER_INCLUDE_RESTARTING"),
|
||||
"Will also include restarting containers")
|
||||
|
||||
flags.BoolP(
|
||||
"include-stopped",
|
||||
"S",
|
||||
viper.GetBool("WATCHTOWER_INCLUDE_STOPPED"),
|
||||
envBool("WATCHTOWER_INCLUDE_STOPPED"),
|
||||
"Will also include created and exited containers")
|
||||
|
||||
flags.BoolP(
|
||||
"revive-stopped",
|
||||
"",
|
||||
viper.GetBool("WATCHTOWER_REVIVE_STOPPED"),
|
||||
envBool("WATCHTOWER_REVIVE_STOPPED"),
|
||||
"Will also start stopped containers that were updated, if include-stopped is active")
|
||||
|
||||
flags.BoolP(
|
||||
"enable-lifecycle-hooks",
|
||||
"",
|
||||
viper.GetBool("WATCHTOWER_LIFECYCLE_HOOKS"),
|
||||
envBool("WATCHTOWER_LIFECYCLE_HOOKS"),
|
||||
"Enable the execution of commands triggered by pre- and post-update lifecycle hooks")
|
||||
|
||||
flags.BoolP(
|
||||
"rolling-restart",
|
||||
"",
|
||||
viper.GetBool("WATCHTOWER_ROLLING_RESTART"),
|
||||
envBool("WATCHTOWER_ROLLING_RESTART"),
|
||||
"Restart containers one at a time")
|
||||
|
||||
flags.BoolP(
|
||||
"http-api-update",
|
||||
"",
|
||||
viper.GetBool("WATCHTOWER_HTTP_API_UPDATE"),
|
||||
envBool("WATCHTOWER_HTTP_API_UPDATE"),
|
||||
"Runs Watchtower in HTTP API mode, so that image updates must to be triggered by a request")
|
||||
flags.BoolP(
|
||||
"http-api-metrics",
|
||||
"",
|
||||
viper.GetBool("WATCHTOWER_HTTP_API_METRICS"),
|
||||
envBool("WATCHTOWER_HTTP_API_METRICS"),
|
||||
"Runs Watchtower with the Prometheus metrics API enabled")
|
||||
|
||||
flags.StringP(
|
||||
"http-api-token",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_HTTP_API_TOKEN"),
|
||||
envString("WATCHTOWER_HTTP_API_TOKEN"),
|
||||
"Sets an authentication token to HTTP API requests.")
|
||||
|
||||
flags.BoolP(
|
||||
"http-api-periodic-polls",
|
||||
"",
|
||||
viper.GetBool("WATCHTOWER_HTTP_API_PERIODIC_POLLS"),
|
||||
envBool("WATCHTOWER_HTTP_API_PERIODIC_POLLS"),
|
||||
"Also run periodic updates (specified with --interval and --schedule) if HTTP API is enabled")
|
||||
|
||||
// https://no-color.org/
|
||||
|
@ -173,18 +178,18 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
|
|||
flags.StringP(
|
||||
"scope",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_SCOPE"),
|
||||
envString("WATCHTOWER_SCOPE"),
|
||||
"Defines a monitoring scope for the Watchtower instance.")
|
||||
|
||||
flags.StringP(
|
||||
"porcelain",
|
||||
"P",
|
||||
viper.GetString("WATCHTOWER_PORCELAIN"),
|
||||
envString("WATCHTOWER_PORCELAIN"),
|
||||
`Write session results to stdout using a stable versioned format. Supported values: "v1"`)
|
||||
|
||||
flags.String(
|
||||
"log-level",
|
||||
viper.GetString("WATCHTOWER_LOG_LEVEL"),
|
||||
envString("WATCHTOWER_LOG_LEVEL"),
|
||||
"The maximum log level that will be written to STDERR. Possible values: panic, fatal, error, warn, info, debug or trace")
|
||||
|
||||
flags.BoolP(
|
||||
|
@ -192,6 +197,12 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
|
|||
"",
|
||||
false,
|
||||
"Do health check and exit")
|
||||
|
||||
flags.BoolP(
|
||||
"label-take-precedence",
|
||||
"",
|
||||
envBool("WATCHTOWER_LABEL_TAKE_PRECEDENCE"),
|
||||
"Label applied to containers take precedence over arguments")
|
||||
}
|
||||
|
||||
// RegisterNotificationFlags that are used by watchtower to send notifications
|
||||
|
@ -201,177 +212,202 @@ func RegisterNotificationFlags(rootCmd *cobra.Command) {
|
|||
flags.StringSliceP(
|
||||
"notifications",
|
||||
"n",
|
||||
viper.GetStringSlice("WATCHTOWER_NOTIFICATIONS"),
|
||||
envStringSlice("WATCHTOWER_NOTIFICATIONS"),
|
||||
" Notification types to send (valid: email, slack, msteams, gotify, shoutrrr)")
|
||||
|
||||
flags.String(
|
||||
"notifications-level",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATIONS_LEVEL"),
|
||||
envString("WATCHTOWER_NOTIFICATIONS_LEVEL"),
|
||||
"The log level used for sending notifications. Possible values: panic, fatal, error, warn, info or debug")
|
||||
|
||||
flags.IntP(
|
||||
"notifications-delay",
|
||||
"",
|
||||
viper.GetInt("WATCHTOWER_NOTIFICATIONS_DELAY"),
|
||||
envInt("WATCHTOWER_NOTIFICATIONS_DELAY"),
|
||||
"Delay before sending notifications, expressed in seconds")
|
||||
|
||||
flags.StringP(
|
||||
"notifications-hostname",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATIONS_HOSTNAME"),
|
||||
envString("WATCHTOWER_NOTIFICATIONS_HOSTNAME"),
|
||||
"Custom hostname for notification titles")
|
||||
|
||||
flags.StringP(
|
||||
"notification-email-from",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_FROM"),
|
||||
envString("WATCHTOWER_NOTIFICATION_EMAIL_FROM"),
|
||||
"Address to send notification emails from")
|
||||
|
||||
flags.StringP(
|
||||
"notification-email-to",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_TO"),
|
||||
envString("WATCHTOWER_NOTIFICATION_EMAIL_TO"),
|
||||
"Address to send notification emails to")
|
||||
|
||||
flags.IntP(
|
||||
"notification-email-delay",
|
||||
"",
|
||||
viper.GetInt("WATCHTOWER_NOTIFICATION_EMAIL_DELAY"),
|
||||
envInt("WATCHTOWER_NOTIFICATION_EMAIL_DELAY"),
|
||||
"Delay before sending notifications, expressed in seconds")
|
||||
|
||||
flags.StringP(
|
||||
"notification-email-server",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER"),
|
||||
envString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER"),
|
||||
"SMTP server to send notification emails through")
|
||||
|
||||
flags.IntP(
|
||||
"notification-email-server-port",
|
||||
"",
|
||||
viper.GetInt("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT"),
|
||||
envInt("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT"),
|
||||
"SMTP server port to send notification emails through")
|
||||
|
||||
flags.BoolP(
|
||||
"notification-email-server-tls-skip-verify",
|
||||
"",
|
||||
viper.GetBool("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY"),
|
||||
envBool("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_TLS_SKIP_VERIFY"),
|
||||
`Controls whether watchtower verifies the SMTP server's certificate chain and host name.
|
||||
Should only be used for testing.`)
|
||||
|
||||
flags.StringP(
|
||||
"notification-email-server-user",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER"),
|
||||
envString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER"),
|
||||
"SMTP server user for sending notifications")
|
||||
|
||||
flags.StringP(
|
||||
"notification-email-server-password",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD"),
|
||||
envString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD"),
|
||||
"SMTP server password for sending notifications")
|
||||
|
||||
flags.StringP(
|
||||
"notification-email-subjecttag",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG"),
|
||||
envString("WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG"),
|
||||
"Subject prefix tag for notifications via mail")
|
||||
|
||||
flags.StringP(
|
||||
"notification-slack-hook-url",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL"),
|
||||
envString("WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL"),
|
||||
"The Slack Hook URL to send notifications to")
|
||||
|
||||
flags.StringP(
|
||||
"notification-slack-identifier",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER"),
|
||||
envString("WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER"),
|
||||
"A string which will be used to identify the messages coming from this watchtower instance")
|
||||
|
||||
flags.StringP(
|
||||
"notification-slack-channel",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_CHANNEL"),
|
||||
envString("WATCHTOWER_NOTIFICATION_SLACK_CHANNEL"),
|
||||
"A string which overrides the webhook's default channel. Example: #my-custom-channel")
|
||||
|
||||
flags.StringP(
|
||||
"notification-slack-icon-emoji",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI"),
|
||||
envString("WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI"),
|
||||
"An emoji code string to use in place of the default icon")
|
||||
|
||||
flags.StringP(
|
||||
"notification-slack-icon-url",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_SLACK_ICON_URL"),
|
||||
envString("WATCHTOWER_NOTIFICATION_SLACK_ICON_URL"),
|
||||
"An icon image URL string to use in place of the default icon")
|
||||
|
||||
flags.StringP(
|
||||
"notification-msteams-hook",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL"),
|
||||
envString("WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL"),
|
||||
"The MSTeams WebHook URL to send notifications to")
|
||||
|
||||
flags.BoolP(
|
||||
"notification-msteams-data",
|
||||
"",
|
||||
viper.GetBool("WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA"),
|
||||
envBool("WATCHTOWER_NOTIFICATION_MSTEAMS_USE_LOG_DATA"),
|
||||
"The MSTeams notifier will try to extract log entry fields as MSTeams message facts")
|
||||
|
||||
flags.StringP(
|
||||
"notification-gotify-url",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_GOTIFY_URL"),
|
||||
envString("WATCHTOWER_NOTIFICATION_GOTIFY_URL"),
|
||||
"The Gotify URL to send notifications to")
|
||||
|
||||
flags.StringP(
|
||||
"notification-gotify-token",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN"),
|
||||
envString("WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN"),
|
||||
"The Gotify Application required to query the Gotify API")
|
||||
|
||||
flags.BoolP(
|
||||
"notification-gotify-tls-skip-verify",
|
||||
"",
|
||||
viper.GetBool("WATCHTOWER_NOTIFICATION_GOTIFY_TLS_SKIP_VERIFY"),
|
||||
envBool("WATCHTOWER_NOTIFICATION_GOTIFY_TLS_SKIP_VERIFY"),
|
||||
`Controls whether watchtower verifies the Gotify server's certificate chain and host name.
|
||||
Should only be used for testing.`)
|
||||
|
||||
flags.String(
|
||||
"notification-template",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_TEMPLATE"),
|
||||
envString("WATCHTOWER_NOTIFICATION_TEMPLATE"),
|
||||
"The shoutrrr text/template for the messages")
|
||||
|
||||
flags.StringArray(
|
||||
"notification-url",
|
||||
viper.GetStringSlice("WATCHTOWER_NOTIFICATION_URL"),
|
||||
envStringSlice("WATCHTOWER_NOTIFICATION_URL"),
|
||||
"The shoutrrr URL to send notifications to")
|
||||
|
||||
flags.Bool("notification-report",
|
||||
viper.GetBool("WATCHTOWER_NOTIFICATION_REPORT"),
|
||||
envBool("WATCHTOWER_NOTIFICATION_REPORT"),
|
||||
"Use the session report as the notification template data")
|
||||
|
||||
flags.StringP(
|
||||
"notification-title-tag",
|
||||
"",
|
||||
viper.GetString("WATCHTOWER_NOTIFICATION_TITLE_TAG"),
|
||||
envString("WATCHTOWER_NOTIFICATION_TITLE_TAG"),
|
||||
"Title prefix tag for notifications")
|
||||
|
||||
flags.Bool("notification-skip-title",
|
||||
viper.GetBool("WATCHTOWER_NOTIFICATION_SKIP_TITLE"),
|
||||
envBool("WATCHTOWER_NOTIFICATION_SKIP_TITLE"),
|
||||
"Do not pass the title param to notifications")
|
||||
|
||||
flags.String(
|
||||
"warn-on-head-failure",
|
||||
viper.GetString("WATCHTOWER_WARN_ON_HEAD_FAILURE"),
|
||||
envString("WATCHTOWER_WARN_ON_HEAD_FAILURE"),
|
||||
"When to warn about HEAD pull requests failing. Possible values: always, auto or never")
|
||||
|
||||
flags.Bool(
|
||||
"notification-log-stdout",
|
||||
viper.GetBool("WATCHTOWER_NOTIFICATION_LOG_STDOUT"),
|
||||
envBool("WATCHTOWER_NOTIFICATION_LOG_STDOUT"),
|
||||
"Write notification logs to stdout instead of logging (to stderr)")
|
||||
}
|
||||
|
||||
func envString(key string) string {
|
||||
viper.MustBindEnv(key)
|
||||
return viper.GetString(key)
|
||||
}
|
||||
|
||||
func envStringSlice(key string) []string {
|
||||
viper.MustBindEnv(key)
|
||||
return viper.GetStringSlice(key)
|
||||
}
|
||||
|
||||
func envInt(key string) int {
|
||||
viper.MustBindEnv(key)
|
||||
return viper.GetInt(key)
|
||||
}
|
||||
|
||||
func envBool(key string) bool {
|
||||
viper.MustBindEnv(key)
|
||||
return viper.GetBool(key)
|
||||
}
|
||||
|
||||
func envDuration(key string) time.Duration {
|
||||
viper.MustBindEnv(key)
|
||||
return viper.GetDuration(key)
|
||||
}
|
||||
|
||||
// SetDefaults provides default values for environment variables
|
||||
func SetDefaults() {
|
||||
viper.AutomaticEnv()
|
||||
|
@ -385,6 +421,7 @@ func SetDefaults() {
|
|||
viper.SetDefault("WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG", "")
|
||||
viper.SetDefault("WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER", "watchtower")
|
||||
viper.SetDefault("WATCHTOWER_LOG_LEVEL", "info")
|
||||
viper.SetDefault("WATCHTOWER_LOG_FORMAT", "auto")
|
||||
}
|
||||
|
||||
// EnvConfig translates the command-line options into environment variables
|
||||
|
@ -473,14 +510,17 @@ func GetSecretsFromFiles(rootCmd *cobra.Command) {
|
|||
"notification-msteams-hook",
|
||||
"notification-gotify-token",
|
||||
"notification-url",
|
||||
"http-api-token",
|
||||
}
|
||||
for _, secret := range secrets {
|
||||
getSecretFromFile(flags, secret)
|
||||
if err := getSecretFromFile(flags, secret); err != nil {
|
||||
log.Fatalf("failed to get secret from flag %v: %s", secret, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getSecretFromFile will check if the flag contains a reference to a file; if it does, replaces the value of the flag with the contents of the file.
|
||||
func getSecretFromFile(flags *pflag.FlagSet, secret string) {
|
||||
func getSecretFromFile(flags *pflag.FlagSet, secret string) error {
|
||||
flag := flags.Lookup(secret)
|
||||
if sliceValue, ok := flag.Value.(pflag.SliceValue); ok {
|
||||
oldValues := sliceValue.GetSlice()
|
||||
|
@ -489,7 +529,7 @@ func getSecretFromFile(flags *pflag.FlagSet, secret string) {
|
|||
if value != "" && isFile(value) {
|
||||
file, err := os.Open(value)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
|
@ -499,25 +539,26 @@ func getSecretFromFile(flags *pflag.FlagSet, secret string) {
|
|||
}
|
||||
values = append(values, line)
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
values = append(values, value)
|
||||
}
|
||||
}
|
||||
sliceValue.Replace(values)
|
||||
return
|
||||
return sliceValue.Replace(values)
|
||||
}
|
||||
|
||||
value := flag.Value.String()
|
||||
if value != "" && isFile(value) {
|
||||
file, err := ioutil.ReadFile(value)
|
||||
content, err := os.ReadFile(value)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = flags.Set(secret, strings.TrimSpace(string(file)))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
return flags.Set(secret, strings.TrimSpace(string(content)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isFile(s string) bool {
|
||||
|
@ -570,19 +611,59 @@ func ProcessFlagAliases(flags *pflag.FlagSet) {
|
|||
// update schedule flag to match interval if it's set, or to the default if none of them are
|
||||
if intervalChanged || !scheduleChanged {
|
||||
interval, _ := flags.GetInt(`interval`)
|
||||
flags.Set(`schedule`, fmt.Sprintf(`@every %ds`, interval))
|
||||
_ = flags.Set(`schedule`, fmt.Sprintf(`@every %ds`, interval))
|
||||
}
|
||||
|
||||
if flagIsEnabled(flags, `debug`) {
|
||||
flags.Set(`log-level`, `debug`)
|
||||
_ = flags.Set(`log-level`, `debug`)
|
||||
}
|
||||
|
||||
if flagIsEnabled(flags, `trace`) {
|
||||
flags.Set(`log-level`, `trace`)
|
||||
_ = flags.Set(`log-level`, `trace`)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// SetupLogging reads only the flags that is needed to set up logging and applies them to the global logger
|
||||
func SetupLogging(f *pflag.FlagSet) error {
|
||||
logFormat, _ := f.GetString(`log-format`)
|
||||
noColor, _ := f.GetBool("no-color")
|
||||
|
||||
switch strings.ToLower(logFormat) {
|
||||
case "auto":
|
||||
// This will either use the "pretty" or "logfmt" format, based on whether the standard out is connected to a TTY
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
DisableColors: noColor,
|
||||
// enable logrus built-in support for https://bixense.com/clicolors/
|
||||
EnvironmentOverrideColors: true,
|
||||
})
|
||||
case "json":
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
case "logfmt":
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
DisableColors: true,
|
||||
FullTimestamp: true,
|
||||
})
|
||||
case "pretty":
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
// "Pretty" format combined with `--no-color` will only change the timestamp to the time since start
|
||||
ForceColors: !noColor,
|
||||
FullTimestamp: false,
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("invalid log format: %s", logFormat)
|
||||
}
|
||||
|
||||
rawLogLevel, _ := f.GetString(`log-level`)
|
||||
if logLevel, err := log.ParseLevel(rawLogLevel); err != nil {
|
||||
return fmt.Errorf("invalid log level: %e", err)
|
||||
} else {
|
||||
log.SetLevel(logLevel)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func flagIsEnabled(flags *pflag.FlagSet, name string) bool {
|
||||
value, err := flags.GetBool(name)
|
||||
if err != nil {
|
||||
|
@ -599,7 +680,7 @@ func appendFlagValue(flags *pflag.FlagSet, name string, values ...string) error
|
|||
|
||||
if flagValues, ok := flag.Value.(pflag.SliceValue); ok {
|
||||
for _, value := range values {
|
||||
flagValues.Append(value)
|
||||
_ = flagValues.Append(value)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf(`the value for flag %q is not a slice value`, name)
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
package flags
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEnvConfig_Defaults(t *testing.T) {
|
||||
// Unset testing environments own variables, since those are not what is under test
|
||||
os.Unsetenv("DOCKER_TLS_VERIFY")
|
||||
os.Unsetenv("DOCKER_HOST")
|
||||
_ = os.Unsetenv("DOCKER_TLS_VERIFY")
|
||||
_ = os.Unsetenv("DOCKER_HOST")
|
||||
|
||||
cmd := new(cobra.Command)
|
||||
SetDefaults()
|
||||
|
@ -48,10 +50,7 @@ func TestEnvConfig_Custom(t *testing.T) {
|
|||
|
||||
func TestGetSecretsFromFilesWithString(t *testing.T) {
|
||||
value := "supersecretstring"
|
||||
|
||||
err := os.Setenv("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD", value)
|
||||
require.NoError(t, err)
|
||||
defer os.Unsetenv("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD")
|
||||
t.Setenv("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD", value)
|
||||
|
||||
testGetSecretsFromFiles(t, "notification-email-server-password", value)
|
||||
}
|
||||
|
@ -60,18 +59,15 @@ func TestGetSecretsFromFilesWithFile(t *testing.T) {
|
|||
value := "megasecretstring"
|
||||
|
||||
// Create the temporary file which will contain a secret.
|
||||
file, err := ioutil.TempFile(os.TempDir(), "watchtower-")
|
||||
file, err := os.CreateTemp(t.TempDir(), "watchtower-")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(file.Name()) // Make sure to remove the temporary file later.
|
||||
|
||||
// Write the secret to the temporary file.
|
||||
secret := []byte(value)
|
||||
_, err = file.Write(secret)
|
||||
_, err = file.Write([]byte(value))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, file.Close())
|
||||
|
||||
err = os.Setenv("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD", file.Name())
|
||||
require.NoError(t, err)
|
||||
defer os.Unsetenv("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD")
|
||||
t.Setenv("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD", file.Name())
|
||||
|
||||
testGetSecretsFromFiles(t, "notification-email-server-password", value)
|
||||
}
|
||||
|
@ -80,16 +76,15 @@ func TestGetSliceSecretsFromFiles(t *testing.T) {
|
|||
values := []string{"entry2", "", "entry3"}
|
||||
|
||||
// Create the temporary file which will contain a secret.
|
||||
file, err := ioutil.TempFile(os.TempDir(), "watchtower-")
|
||||
file, err := os.CreateTemp(t.TempDir(), "watchtower-")
|
||||
require.NoError(t, err)
|
||||
defer os.Remove(file.Name()) // Make sure to remove the temporary file later.
|
||||
|
||||
// Write the secret to the temporary file.
|
||||
for _, value := range values {
|
||||
_, err = file.WriteString("\n" + value)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
file.Close()
|
||||
require.NoError(t, file.Close())
|
||||
|
||||
testGetSecretsFromFiles(t, "notification-url", `[entry1,entry2,entry3]`,
|
||||
`--notification-url`, "entry1",
|
||||
|
@ -99,6 +94,7 @@ func TestGetSliceSecretsFromFiles(t *testing.T) {
|
|||
func testGetSecretsFromFiles(t *testing.T, flagName string, expected string, args ...string) {
|
||||
cmd := new(cobra.Command)
|
||||
SetDefaults()
|
||||
RegisterSystemFlags(cmd)
|
||||
RegisterNotificationFlags(cmd)
|
||||
require.NoError(t, cmd.ParseFlags(args))
|
||||
GetSecretsFromFiles(cmd)
|
||||
|
@ -166,9 +162,7 @@ func TestProcessFlagAliases(t *testing.T) {
|
|||
|
||||
func TestProcessFlagAliasesLogLevelFromEnvironment(t *testing.T) {
|
||||
cmd := new(cobra.Command)
|
||||
err := os.Setenv("WATCHTOWER_DEBUG", `true`)
|
||||
require.NoError(t, err)
|
||||
defer os.Unsetenv("WATCHTOWER_DEBUG")
|
||||
t.Setenv("WATCHTOWER_DEBUG", `true`)
|
||||
|
||||
SetDefaults()
|
||||
RegisterDockerFlags(cmd)
|
||||
|
@ -183,6 +177,57 @@ func TestProcessFlagAliasesLogLevelFromEnvironment(t *testing.T) {
|
|||
assert.Equal(t, `debug`, logLevel)
|
||||
}
|
||||
|
||||
func TestLogFormatFlag(t *testing.T) {
|
||||
cmd := new(cobra.Command)
|
||||
|
||||
SetDefaults()
|
||||
RegisterDockerFlags(cmd)
|
||||
RegisterSystemFlags(cmd)
|
||||
|
||||
// Ensure the default value is Auto
|
||||
require.NoError(t, cmd.ParseFlags([]string{}))
|
||||
require.NoError(t, SetupLogging(cmd.Flags()))
|
||||
assert.IsType(t, &logrus.TextFormatter{}, logrus.StandardLogger().Formatter)
|
||||
|
||||
// Test JSON format
|
||||
require.NoError(t, cmd.ParseFlags([]string{`--log-format`, `JSON`}))
|
||||
require.NoError(t, SetupLogging(cmd.Flags()))
|
||||
assert.IsType(t, &logrus.JSONFormatter{}, logrus.StandardLogger().Formatter)
|
||||
|
||||
// Test Pretty format
|
||||
require.NoError(t, cmd.ParseFlags([]string{`--log-format`, `pretty`}))
|
||||
require.NoError(t, SetupLogging(cmd.Flags()))
|
||||
assert.IsType(t, &logrus.TextFormatter{}, logrus.StandardLogger().Formatter)
|
||||
textFormatter, ok := (logrus.StandardLogger().Formatter).(*logrus.TextFormatter)
|
||||
assert.True(t, ok)
|
||||
assert.True(t, textFormatter.ForceColors)
|
||||
assert.False(t, textFormatter.FullTimestamp)
|
||||
|
||||
// Test LogFmt format
|
||||
require.NoError(t, cmd.ParseFlags([]string{`--log-format`, `logfmt`}))
|
||||
require.NoError(t, SetupLogging(cmd.Flags()))
|
||||
textFormatter, ok = (logrus.StandardLogger().Formatter).(*logrus.TextFormatter)
|
||||
assert.True(t, ok)
|
||||
assert.True(t, textFormatter.DisableColors)
|
||||
assert.True(t, textFormatter.FullTimestamp)
|
||||
|
||||
// Test invalid format
|
||||
require.NoError(t, cmd.ParseFlags([]string{`--log-format`, `cowsay`}))
|
||||
require.Error(t, SetupLogging(cmd.Flags()))
|
||||
}
|
||||
|
||||
func TestLogLevelFlag(t *testing.T) {
|
||||
cmd := new(cobra.Command)
|
||||
|
||||
SetDefaults()
|
||||
RegisterDockerFlags(cmd)
|
||||
RegisterSystemFlags(cmd)
|
||||
|
||||
// Test invalid format
|
||||
require.NoError(t, cmd.ParseFlags([]string{`--log-level`, `gossip`}))
|
||||
require.Error(t, SetupLogging(cmd.Flags()))
|
||||
}
|
||||
|
||||
func TestProcessFlagAliasesSchedAndInterval(t *testing.T) {
|
||||
logrus.StandardLogger().ExitFunc = func(_ int) { panic(`FATAL`) }
|
||||
cmd := new(cobra.Command)
|
||||
|
@ -202,9 +247,7 @@ func TestProcessFlagAliasesSchedAndInterval(t *testing.T) {
|
|||
func TestProcessFlagAliasesScheduleFromEnvironment(t *testing.T) {
|
||||
cmd := new(cobra.Command)
|
||||
|
||||
err := os.Setenv("WATCHTOWER_SCHEDULE", `@hourly`)
|
||||
require.NoError(t, err)
|
||||
defer os.Unsetenv("WATCHTOWER_SCHEDULE")
|
||||
t.Setenv("WATCHTOWER_SCHEDULE", `@hourly`)
|
||||
|
||||
SetDefaults()
|
||||
RegisterDockerFlags(cmd)
|
||||
|
@ -234,3 +277,63 @@ func TestProcessFlagAliasesInvalidPorcelaineVersion(t *testing.T) {
|
|||
ProcessFlagAliases(flags)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFlagsArePrecentInDocumentation(t *testing.T) {
|
||||
|
||||
// Legacy notifcations are ignored, since they are (soft) deprecated
|
||||
ignoredEnvs := map[string]string{
|
||||
"WATCHTOWER_NOTIFICATION_SLACK_ICON_EMOJI": "legacy",
|
||||
"WATCHTOWER_NOTIFICATION_SLACK_ICON_URL": "legacy",
|
||||
}
|
||||
|
||||
ignoredFlags := map[string]string{
|
||||
"notification-gotify-url": "legacy",
|
||||
"notification-slack-icon-emoji": "legacy",
|
||||
"notification-slack-icon-url": "legacy",
|
||||
}
|
||||
|
||||
cmd := new(cobra.Command)
|
||||
SetDefaults()
|
||||
RegisterDockerFlags(cmd)
|
||||
RegisterSystemFlags(cmd)
|
||||
RegisterNotificationFlags(cmd)
|
||||
|
||||
flags := cmd.PersistentFlags()
|
||||
|
||||
docFiles := []string{
|
||||
"../../docs/arguments.md",
|
||||
"../../docs/lifecycle-hooks.md",
|
||||
"../../docs/notifications.md",
|
||||
}
|
||||
allDocs := ""
|
||||
for _, f := range docFiles {
|
||||
bytes, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not load docs file %q: %v", f, err)
|
||||
}
|
||||
allDocs += string(bytes)
|
||||
}
|
||||
|
||||
flags.VisitAll(func(f *pflag.Flag) {
|
||||
if !strings.Contains(allDocs, "--"+f.Name) {
|
||||
if _, found := ignoredFlags[f.Name]; !found {
|
||||
t.Logf("Docs does not mention flag long name %q", f.Name)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
if !strings.Contains(allDocs, "-"+f.Shorthand) {
|
||||
t.Logf("Docs does not mention flag shorthand %q (%q)", f.Shorthand, f.Name)
|
||||
t.Fail()
|
||||
}
|
||||
})
|
||||
|
||||
for _, key := range viper.AllKeys() {
|
||||
envKey := strings.ToUpper(key)
|
||||
if !strings.Contains(allDocs, envKey) {
|
||||
if _, found := ignoredEnvs[envKey]; !found {
|
||||
t.Logf("Docs does not mention environment variable %q", envKey)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
BIN
oryxBuildBinary
Executable file
BIN
oryxBuildBinary
Executable file
Binary file not shown.
|
@ -30,7 +30,7 @@ type Client interface {
|
|||
StopContainer(t.Container, time.Duration) error
|
||||
StartContainer(t.Container) (t.ContainerID, error)
|
||||
RenameContainer(t.Container, string) error
|
||||
IsContainerStale(t.Container) (stale bool, latestImage t.ImageID, err error)
|
||||
IsContainerStale(t.Container, t.UpdateParams) (stale bool, latestImage t.ImageID, err error)
|
||||
ExecuteCommand(containerID t.ContainerID, command string, timeout int) (SkipUpdate bool, err error)
|
||||
RemoveImageByID(t.ImageID) error
|
||||
WarnOnHeadPullFailed(container t.Container) bool
|
||||
|
@ -230,9 +230,18 @@ func (client dockerClient) GetNetworkConfig(c t.Container) *network.NetworkingCo
|
|||
}
|
||||
|
||||
for _, ep := range config.EndpointsConfig {
|
||||
// This keeps accumulating across upgrades with no apparent added value
|
||||
// so throwing the information away to prevent overflows.
|
||||
ep.Aliases = nil
|
||||
aliases := make([]string, 0, len(ep.Aliases))
|
||||
cidAlias := c.ID().ShortID()
|
||||
|
||||
// Remove the old container ID alias from the network aliases, as it would accumulate across updates otherwise
|
||||
for _, alias := range ep.Aliases {
|
||||
if alias == cidAlias {
|
||||
continue
|
||||
}
|
||||
aliases = append(aliases, alias)
|
||||
}
|
||||
|
||||
ep.Aliases = aliases
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
@ -308,10 +317,10 @@ func (client dockerClient) RenameContainer(c t.Container, newName string) error
|
|||
return client.api.ContainerRename(bg, string(c.ID()), newName)
|
||||
}
|
||||
|
||||
func (client dockerClient) IsContainerStale(container t.Container) (stale bool, latestImage t.ImageID, err error) {
|
||||
func (client dockerClient) IsContainerStale(container t.Container, params t.UpdateParams) (stale bool, latestImage t.ImageID, err error) {
|
||||
ctx := context.Background()
|
||||
|
||||
if !client.PullImages || container.IsNoPull() {
|
||||
if container.IsNoPull(params) {
|
||||
log.Debugf("Skipping image pull.")
|
||||
} else if err := client.PullImage(ctx, container); err != nil {
|
||||
return false, container.SafeImageID(), err
|
||||
|
|
|
@ -317,19 +317,20 @@ var _ = Describe("the client", func() {
|
|||
})
|
||||
Describe(`GetNetworkConfig`, func() {
|
||||
When(`providing a container with network aliases`, func() {
|
||||
It(`should purge the aliases`, func() {
|
||||
aliases := []string{"One", "Two"}
|
||||
It(`should omit the container ID alias`, func() {
|
||||
client := dockerClient{
|
||||
api: docker,
|
||||
ClientOptions: ClientOptions{PullImages: false, IncludeRestarting: false},
|
||||
}
|
||||
container := MockContainer(WithImageName("docker.io/prefix/imagename:latest"))
|
||||
|
||||
aliases := []string{"One", "Two", container.ID().ShortID(), "Four"}
|
||||
endpoints := map[string]*network.EndpointSettings{
|
||||
`test`: {Aliases: aliases},
|
||||
}
|
||||
container.containerInfo.NetworkSettings = &types.NetworkSettings{Networks: endpoints}
|
||||
Expect(container.ContainerInfo().NetworkSettings.Networks[`test`].Aliases).To(Equal(aliases))
|
||||
Expect(client.GetNetworkConfig(container).EndpointsConfig[`test`].Aliases).To(BeEmpty())
|
||||
Expect(client.GetNetworkConfig(container).EndpointsConfig[`test`].Aliases).To(Equal([]string{"One", "Two", "Four"}))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containrrr/watchtower/internal/util"
|
||||
wt "github.com/containrrr/watchtower/pkg/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
dockercontainer "github.com/docker/docker/api/types/container"
|
||||
|
@ -129,36 +131,31 @@ func (c Container) Enabled() (bool, bool) {
|
|||
return parsedBool, true
|
||||
}
|
||||
|
||||
// IsMonitorOnly returns the value of the monitor-only label. If the label
|
||||
// is not set then false is returned.
|
||||
func (c Container) IsMonitorOnly() bool {
|
||||
rawBool, ok := c.getLabelValue(monitorOnlyLabel)
|
||||
if !ok {
|
||||
return false
|
||||
// IsMonitorOnly returns whether the container should only be monitored based on values of
|
||||
// the monitor-only label, the monitor-only argument and the label-take-precedence argument.
|
||||
func (c Container) IsMonitorOnly(params wt.UpdateParams) bool {
|
||||
return c.getContainerOrGlobalBool(params.MonitorOnly, monitorOnlyLabel, params.LabelPrecedence)
|
||||
}
|
||||
|
||||
parsedBool, err := strconv.ParseBool(rawBool)
|
||||
if err != nil {
|
||||
return false
|
||||
// IsNoPull returns whether the image should be pulled based on values of
|
||||
// the no-pull label, the no-pull argument and the label-take-precedence argument.
|
||||
func (c Container) IsNoPull(params wt.UpdateParams) bool {
|
||||
return c.getContainerOrGlobalBool(params.NoPull, noPullLabel, params.LabelPrecedence)
|
||||
}
|
||||
|
||||
return parsedBool
|
||||
func (c Container) getContainerOrGlobalBool(globalVal bool, label string, contPrecedence bool) bool {
|
||||
if contVal, err := c.getBoolLabelValue(label); err != nil {
|
||||
if !errors.Is(err, errorLabelNotFound) {
|
||||
logrus.WithField("error", err).WithField("label", label).Warn("Failed to parse label value")
|
||||
}
|
||||
|
||||
// IsNoPull returns the value of the no-pull label. If the label is not set
|
||||
// then false is returned.
|
||||
func (c Container) IsNoPull() bool {
|
||||
rawBool, ok := c.getLabelValue(noPullLabel)
|
||||
if !ok {
|
||||
return false
|
||||
return globalVal
|
||||
} else {
|
||||
if contPrecedence {
|
||||
return contVal
|
||||
} else {
|
||||
return contVal || globalVal
|
||||
}
|
||||
|
||||
parsedBool, err := strconv.ParseBool(rawBool)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return parsedBool
|
||||
}
|
||||
|
||||
// Scope returns the value of the scope UID label and if the label
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"github.com/containrrr/watchtower/pkg/types"
|
||||
"github.com/docker/go-connections/nat"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
@ -215,12 +216,13 @@ var _ = Describe("the container", func() {
|
|||
})
|
||||
|
||||
When("checking no-pull label", func() {
|
||||
When("no-pull argument is not set", func() {
|
||||
When("no-pull label is true", func() {
|
||||
c := MockContainer(WithLabels(map[string]string{
|
||||
"com.centurylinklabs.watchtower.no-pull": "true",
|
||||
}))
|
||||
It("should return true", func() {
|
||||
Expect(c.IsNoPull()).To(Equal(true))
|
||||
Expect(c.IsNoPull(types.UpdateParams{})).To(Equal(true))
|
||||
})
|
||||
})
|
||||
When("no-pull label is false", func() {
|
||||
|
@ -228,7 +230,7 @@ var _ = Describe("the container", func() {
|
|||
"com.centurylinklabs.watchtower.no-pull": "false",
|
||||
}))
|
||||
It("should return false", func() {
|
||||
Expect(c.IsNoPull()).To(Equal(false))
|
||||
Expect(c.IsNoPull(types.UpdateParams{})).To(Equal(false))
|
||||
})
|
||||
})
|
||||
When("no-pull label is set to an invalid value", func() {
|
||||
|
@ -236,13 +238,50 @@ var _ = Describe("the container", func() {
|
|||
"com.centurylinklabs.watchtower.no-pull": "maybe",
|
||||
}))
|
||||
It("should return false", func() {
|
||||
Expect(c.IsNoPull()).To(Equal(false))
|
||||
Expect(c.IsNoPull(types.UpdateParams{})).To(Equal(false))
|
||||
})
|
||||
})
|
||||
When("no-pull label is unset", func() {
|
||||
c = MockContainer(WithLabels(map[string]string{}))
|
||||
It("should return false", func() {
|
||||
Expect(c.IsNoPull()).To(Equal(false))
|
||||
Expect(c.IsNoPull(types.UpdateParams{})).To(Equal(false))
|
||||
})
|
||||
})
|
||||
})
|
||||
When("no-pull argument is set to true", func() {
|
||||
When("no-pull label is true", func() {
|
||||
c := MockContainer(WithLabels(map[string]string{
|
||||
"com.centurylinklabs.watchtower.no-pull": "true",
|
||||
}))
|
||||
It("should return true", func() {
|
||||
Expect(c.IsNoPull(types.UpdateParams{NoPull: true})).To(Equal(true))
|
||||
})
|
||||
})
|
||||
When("no-pull label is false", func() {
|
||||
c := MockContainer(WithLabels(map[string]string{
|
||||
"com.centurylinklabs.watchtower.no-pull": "false",
|
||||
}))
|
||||
It("should return true", func() {
|
||||
Expect(c.IsNoPull(types.UpdateParams{NoPull: true})).To(Equal(true))
|
||||
})
|
||||
})
|
||||
When("label-take-precedence argument is set to true", func() {
|
||||
When("no-pull label is true", func() {
|
||||
c := MockContainer(WithLabels(map[string]string{
|
||||
"com.centurylinklabs.watchtower.no-pull": "true",
|
||||
}))
|
||||
It("should return true", func() {
|
||||
Expect(c.IsNoPull(types.UpdateParams{LabelPrecedence: true, NoPull: true})).To(Equal(true))
|
||||
})
|
||||
})
|
||||
When("no-pull label is false", func() {
|
||||
c := MockContainer(WithLabels(map[string]string{
|
||||
"com.centurylinklabs.watchtower.no-pull": "false",
|
||||
}))
|
||||
It("should return false", func() {
|
||||
Expect(c.IsNoPull(types.UpdateParams{LabelPrecedence: true, NoPull: true})).To(Equal(false))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -5,3 +5,4 @@ import "errors"
|
|||
var errorNoImageInfo = errors.New("no available image info")
|
||||
var errorNoContainerInfo = errors.New("no available container info")
|
||||
var errorInvalidConfig = errors.New("container configuration missing or invalid")
|
||||
var errorLabelNotFound = errors.New("label was not found in container")
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package container
|
||||
|
||||
import "strconv"
|
||||
|
||||
const (
|
||||
watchtowerLabel = "com.centurylinklabs.watchtower"
|
||||
signalLabel = "com.centurylinklabs.watchtower.stop-signal"
|
||||
|
@ -55,3 +57,11 @@ func (c Container) getLabelValue(label string) (string, bool) {
|
|||
val, ok := c.containerInfo.Config.Labels[label]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
func (c Container) getBoolLabelValue(label string) (bool, error) {
|
||||
if strVal, ok := c.containerInfo.Config.Labels[label]; ok {
|
||||
value, err := strconv.ParseBool(strVal)
|
||||
return value, err
|
||||
}
|
||||
return false, errorLabelNotFound
|
||||
}
|
||||
|
|
|
@ -110,6 +110,7 @@ func createNotifier(urls []string, level log.Level, tplString string, legacy boo
|
|||
legacyTemplate: legacy,
|
||||
data: data,
|
||||
params: params,
|
||||
delay: delay,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ var _ = Describe("the auth module", func() {
|
|||
Expect(getScopeFromImageAuthURL("ghcr.io/containrrr/watchtower")).To(Equal("containrrr/watchtower"))
|
||||
})
|
||||
})
|
||||
It("should not crash when an empty field is recieved", func() {
|
||||
It("should not crash when an empty field is received", func() {
|
||||
input := `bearer realm="https://ghcr.io/token",service="ghcr.io",scope="repository:user/image:pull",`
|
||||
imageRef, err := ref.ParseNormalizedNamed("containrrr/watchtower")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
@ -120,7 +120,7 @@ var _ = Describe("the auth module", func() {
|
|||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res).NotTo(BeNil())
|
||||
})
|
||||
It("should not crash when a field without a value is recieved", func() {
|
||||
It("should not crash when a field without a value is received", func() {
|
||||
input := `bearer realm="https://ghcr.io/token",service="ghcr.io",scope="repository:user/image:pull",valuelesskey`
|
||||
imageRef, err := ref.ParseNormalizedNamed("containrrr/watchtower")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
|
|
@ -52,7 +52,7 @@ type Container interface {
|
|||
SafeImageID() ImageID
|
||||
ImageName() string
|
||||
Enabled() (bool, bool)
|
||||
IsMonitorOnly() bool
|
||||
IsMonitorOnly(UpdateParams) bool
|
||||
Scope() (string, bool)
|
||||
Links() []string
|
||||
ToRestart() bool
|
||||
|
@ -67,7 +67,7 @@ type Container interface {
|
|||
VerifyConfiguration() error
|
||||
SetStale(bool)
|
||||
IsStale() bool
|
||||
IsNoPull() bool
|
||||
IsNoPull(UpdateParams) bool
|
||||
SetLinkedToRestarting(bool)
|
||||
IsLinkedToRestarting() bool
|
||||
PreUpdateTimeout() int
|
||||
|
|
|
@ -11,6 +11,8 @@ type UpdateParams struct {
|
|||
NoRestart bool
|
||||
Timeout time.Duration
|
||||
MonitorOnly bool
|
||||
NoPull bool
|
||||
LifecycleHooks bool
|
||||
RollingRestart bool
|
||||
LabelPrecedence bool
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue