mirror of
https://github.com/containrrr/watchtower.git
synced 2025-09-22 05:40:50 +02:00
Merge branch 'main' into feat/json-logging
This commit is contained in:
commit
4ba4565d5b
43 changed files with 2040 additions and 1010 deletions
|
@ -849,6 +849,15 @@
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"doc"
|
"doc"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "testwill",
|
||||||
|
"name": "guangwu",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/8717479?v=4",
|
||||||
|
"profile": "https://github.com/testwill",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
@ -857,5 +866,6 @@
|
||||||
"repoType": "github",
|
"repoType": "github",
|
||||||
"repoHost": "https://github.com",
|
"repoHost": "https://github.com",
|
||||||
"commitConvention": "none",
|
"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:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# We must fetch at least the immediate parents so that if this is
|
# We must fetch at least the immediate parents so that if this is
|
||||||
# a pull request then we can checkout the head.
|
# 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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup python
|
- 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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
|
@ -37,7 +37,7 @@ jobs:
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
|
@ -56,7 +56,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
|
@ -64,7 +64,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
go-version: 1.18.x
|
go-version: 1.18.x
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 #v3
|
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 #v3
|
||||||
with:
|
with:
|
||||||
version: v0.155.0
|
version: v0.155.0
|
||||||
args: --snapshot --skip-publish --debug
|
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:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
|
@ -20,7 +20,7 @@ jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
|
@ -37,7 +37,7 @@ jobs:
|
||||||
- test
|
- test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Publish to Docker Hub
|
- name: Publish to Docker Hub
|
||||||
uses: jerray/publish-docker-action@87d84711629b0dc9f6bb127b568413cc92a2088e #master@2022-10-14
|
uses: jerray/publish-docker-action@87d84711629b0dc9f6bb127b568413cc92a2088e #master@2022-10-14
|
||||||
with:
|
with:
|
||||||
|
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
|
@ -15,7 +15,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
|
@ -40,7 +40,7 @@ jobs:
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
|
@ -62,7 +62,7 @@ jobs:
|
||||||
TAG: ${{ github.event.release.tag_name }}
|
TAG: ${{ github.event.release.tag_name }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
|
@ -81,7 +81,7 @@ jobs:
|
||||||
password: ${{ secrets.BOT_GHCR_PAT }}
|
password: ${{ secrets.BOT_GHCR_PAT }}
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 #v3
|
uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 #v3
|
||||||
with:
|
with:
|
||||||
version: v0.155.0
|
version: v0.155.0
|
||||||
args: --debug
|
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>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<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" 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"><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://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" 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"><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/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" 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"><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://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="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>
|
||||||
<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" 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"><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://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" 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"><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="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" 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"><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://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="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>
|
||||||
<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" 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"><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://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" 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"><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://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" 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"><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://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://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>
|
||||||
<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" 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"><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://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" 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"><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://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" 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"><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://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://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>
|
||||||
<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" 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"><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="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" 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"><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/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" 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"><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://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://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>
|
||||||
<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" 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"><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="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" 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"><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://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" 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"><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://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://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>
|
||||||
<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" 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"><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://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" 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"><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="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" 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"><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/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://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>
|
||||||
<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" 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"><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/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" 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"><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/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" 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"><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://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://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>
|
||||||
<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" 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"><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://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" 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"><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="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" 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"><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://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://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>
|
||||||
<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" 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"><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/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" 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"><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/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" 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"><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://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://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>
|
||||||
<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" 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"><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="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" 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"><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/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" 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"><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/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/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>
|
||||||
<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" 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"><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://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" 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"><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/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" 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"><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://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://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>
|
||||||
<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" 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"><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="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" 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"><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://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" 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"><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://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://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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
|
||||||
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<!-- markdownlint-restore -->
|
<!-- markdownlint-restore -->
|
||||||
|
|
39
cmd/root.go
39
cmd/root.go
|
@ -29,17 +29,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
client container.Client
|
client container.Client
|
||||||
scheduleSpec string
|
scheduleSpec string
|
||||||
cleanup bool
|
cleanup bool
|
||||||
noRestart bool
|
noRestart bool
|
||||||
monitorOnly bool
|
monitorOnly bool
|
||||||
enableLabel bool
|
enableLabel bool
|
||||||
notifier t.Notifier
|
notifier t.Notifier
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
lifecycleHooks bool
|
lifecycleHooks bool
|
||||||
rollingRestart bool
|
rollingRestart bool
|
||||||
scope string
|
scope string
|
||||||
|
labelPrecedence bool
|
||||||
)
|
)
|
||||||
|
|
||||||
var rootCmd = NewRootCommand()
|
var rootCmd = NewRootCommand()
|
||||||
|
@ -95,6 +96,7 @@ func PreRun(cmd *cobra.Command, _ []string) {
|
||||||
lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks")
|
lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks")
|
||||||
rollingRestart, _ = f.GetBool("rolling-restart")
|
rollingRestart, _ = f.GetBool("rolling-restart")
|
||||||
scope, _ = f.GetString("scope")
|
scope, _ = f.GetString("scope")
|
||||||
|
labelPrecedence, _ = f.GetBool("label-take-precedence")
|
||||||
|
|
||||||
if scope != "" {
|
if scope != "" {
|
||||||
log.Debugf(`Using scope %q`, scope)
|
log.Debugf(`Using scope %q`, scope)
|
||||||
|
@ -345,13 +347,14 @@ func runUpgradesOnSchedule(c *cobra.Command, filter t.Filter, filtering string,
|
||||||
func runUpdatesWithNotifications(filter t.Filter) *metrics.Metric {
|
func runUpdatesWithNotifications(filter t.Filter) *metrics.Metric {
|
||||||
notifier.StartNotification()
|
notifier.StartNotification()
|
||||||
updateParams := t.UpdateParams{
|
updateParams := t.UpdateParams{
|
||||||
Filter: filter,
|
Filter: filter,
|
||||||
Cleanup: cleanup,
|
Cleanup: cleanup,
|
||||||
NoRestart: noRestart,
|
NoRestart: noRestart,
|
||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
MonitorOnly: monitorOnly,
|
MonitorOnly: monitorOnly,
|
||||||
LifecycleHooks: lifecycleHooks,
|
LifecycleHooks: lifecycleHooks,
|
||||||
RollingRestart: rollingRestart,
|
RollingRestart: rollingRestart,
|
||||||
|
LabelPrecedence: labelPrecedence,
|
||||||
}
|
}
|
||||||
result, err := actions.Update(client, updateParams)
|
result, err := actions.Update(client, updateParams)
|
||||||
if err != nil {
|
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 \
|
RUN apk add --no-cache \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
|
|
17
dockerfiles/container-networking/docker-compose.yml
Normal file
17
dockerfiles/container-networking/docker-compose.yml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
services:
|
||||||
|
producer:
|
||||||
|
image: qmcgaw/gluetun:v3.35.0
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
environment:
|
||||||
|
- VPN_SERVICE_PROVIDER=${VPN_SERVICE_PROVIDER}
|
||||||
|
- OPENVPN_USER=${OPENVPN_USER}
|
||||||
|
- OPENVPN_PASSWORD=${OPENVPN_PASSWORD}
|
||||||
|
- SERVER_COUNTRIES=${SERVER_COUNTRIES}
|
||||||
|
consumer:
|
||||||
|
depends_on:
|
||||||
|
- producer
|
||||||
|
image: nginx:1.25.1
|
||||||
|
network_mode: "service:producer"
|
||||||
|
labels:
|
||||||
|
- "com.centurylinklabs.watchtower.depends-on=/wt-contnet-producer-1"
|
|
@ -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.
|
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
|
## Help
|
||||||
Shows documentation about the supported flags.
|
Shows documentation about the supported flags.
|
||||||
|
|
||||||
|
@ -162,7 +189,7 @@ Environment Variable: WATCHTOWER_INCLUDE_RESTARTING
|
||||||
Will also include created and exited containers.
|
Will also include created and exited containers.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Argument: --include-stopped
|
Argument: --include-stopped, -S
|
||||||
Environment Variable: WATCHTOWER_INCLUDE_STOPPED
|
Environment Variable: WATCHTOWER_INCLUDE_STOPPED
|
||||||
Type: Boolean
|
Type: Boolean
|
||||||
Default: false
|
Default: false
|
||||||
|
@ -189,7 +216,7 @@ Environment Variable: WATCHTOWER_POLL_INTERVAL
|
||||||
```
|
```
|
||||||
|
|
||||||
## Filter by enable label
|
## 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
|
```text
|
||||||
Argument: --label-enable
|
Argument: --label-enable
|
||||||
|
@ -199,7 +226,7 @@ Environment Variable: WATCHTOWER_LABEL_ENABLE
|
||||||
```
|
```
|
||||||
|
|
||||||
## Filter by disable label
|
## 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
|
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.
|
used at the same time to target containers.
|
||||||
|
|
||||||
|
@ -222,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.
|
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
|
## Without restarting containers
|
||||||
Do not restart containers after updating. This option can be useful when the start of the 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.
|
is managed by an external system such as systemd.
|
||||||
|
@ -248,6 +288,8 @@ Environment Variable: WATCHTOWER_NO_PULL
|
||||||
Note that no-pull can also be specified on a per-container basis with the
|
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.
|
`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
|
## Without sending a startup message
|
||||||
Do not send a message after watchtower started. Otherwise there will be an info-level notification.
|
Do not send a message after watchtower started. Otherwise there will be an info-level notification.
|
||||||
|
|
||||||
|
@ -262,7 +304,7 @@ Environment Variable: WATCHTOWER_NO_STARTUP_MESSAGE
|
||||||
Run an update attempt against a container name list one time immediately and exit.
|
Run an update attempt against a container name list one time immediately and exit.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Argument: --run-once
|
Argument: --run-once, -R
|
||||||
Environment Variable: WATCHTOWER_RUN_ONCE
|
Environment Variable: WATCHTOWER_RUN_ONCE
|
||||||
Type: Boolean
|
Type: Boolean
|
||||||
Default: false
|
Default: false
|
||||||
|
@ -281,6 +323,7 @@ Environment Variable: WATCHTOWER_HTTP_API_UPDATE
|
||||||
|
|
||||||
## HTTP API Token
|
## HTTP API Token
|
||||||
Sets an authentication token to HTTP API requests.
|
Sets an authentication token to HTTP API requests.
|
||||||
|
Can also reference a file, in which case the contents of the file are used.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Argument: --http-api-token
|
Argument: --http-api-token
|
||||||
|
@ -375,3 +418,21 @@ Environment Variable: WATCHTOWER_WARN_ON_HEAD_FAILURE
|
||||||
Possible values: always, auto, never
|
Possible values: always, auto, never
|
||||||
Default: auto
|
Default: auto
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 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
|
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).
|
||||||
|
|
||||||
|
|
|
@ -2,4 +2,6 @@ Watchtower will detect if there are links between any of the running containers
|
||||||
|
|
||||||
For example, imagine you were running a _mysql_ container and a _wordpress_ container which had been linked to the _mysql_ container. If watchtower were to detect that the _mysql_ container required an update, it would first shut down the linked _wordpress_ container followed by the _mysql_ container. When restarting the containers it would handle _mysql_ first and then _wordpress_ to ensure that the link continued to work.
|
For example, imagine you were running a _mysql_ container and a _wordpress_ container which had been linked to the _mysql_ container. If watchtower were to detect that the _mysql_ container required an update, it would first shut down the linked _wordpress_ container followed by the _mysql_ container. When restarting the containers it would handle _mysql_ first and then _wordpress_ to ensure that the link continued to work.
|
||||||
|
|
||||||
If you want to override existing links you can use special `com.centurylinklabs.watchtower.depends-on` label with dependent container names, separated by a comma.
|
If you want to override existing links, or if you are not using links, you can use special `com.centurylinklabs.watchtower.depends-on` label with dependent container names, separated by a comma.
|
||||||
|
|
||||||
|
When you have a depending container that is using `network_mode: service:container` then watchtower will treat that container as an implicit link.
|
||||||
|
|
|
@ -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-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-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.
|
- 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-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-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
|
## [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.
|
- `--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
|
learn more about the different service URLs you can use. You can define multiple services by space separating the
|
||||||
URLs. (See example below)
|
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):
|
1. Create the Dockerfile (contents below):
|
||||||
```Dockerfile
|
```Dockerfile
|
||||||
FROM golang:1.16
|
FROM golang:1.17
|
||||||
|
|
||||||
ENV GO111MODULE off
|
ENV GO111MODULE off
|
||||||
ENV CGO_ENABLED 0
|
ENV CGO_ENABLED 0
|
||||||
|
|
16
go.mod
16
go.mod
|
@ -3,10 +3,10 @@ module github.com/containrrr/watchtower
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/containrrr/shoutrrr v0.7.1
|
github.com/containrrr/shoutrrr v0.8.0
|
||||||
github.com/docker/cli v24.0.5+incompatible
|
github.com/docker/cli v24.0.6+incompatible
|
||||||
github.com/docker/distribution v2.8.2+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/docker/go-connections v0.4.0
|
||||||
github.com/onsi/ginkgo v1.16.5
|
github.com/onsi/ginkgo v1.16.5
|
||||||
github.com/onsi/gomega v1.27.10
|
github.com/onsi/gomega v1.27.10
|
||||||
|
@ -17,7 +17,7 @@ require (
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/spf13/viper v1.16.0
|
github.com/spf13/viper v1.16.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
golang.org/x/net v0.12.0
|
golang.org/x/net v0.15.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -28,7 +28,7 @@ require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/docker/docker-credential-helpers v0.6.1 // indirect
|
github.com/docker/docker-credential-helpers v0.6.1 // indirect
|
||||||
github.com/docker/go-units v0.4.0 // 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/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
@ -37,7 +37,7 @@ require (
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // 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/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // 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/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/stretchr/objx v0.5.0 // indirect
|
github.com/stretchr/objx v0.5.0 // indirect
|
||||||
github.com/subosito/gotenv v1.4.2 // indirect
|
github.com/subosito/gotenv v1.4.2 // indirect
|
||||||
golang.org/x/sys v0.10.0 // indirect
|
golang.org/x/sys v0.12.0 // indirect
|
||||||
golang.org/x/text v0.11.0
|
golang.org/x/text v0.13.0
|
||||||
golang.org/x/time v0.1.0 // indirect
|
golang.org/x/time v0.1.0 // indirect
|
||||||
google.golang.org/protobuf v1.30.0 // indirect
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.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
|
// 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()]
|
stale, found := client.TestData.Staleness[cont.Name()]
|
||||||
if !found {
|
if !found {
|
||||||
stale = true
|
stale = true
|
||||||
|
|
|
@ -33,8 +33,8 @@ func Update(client container.Client, params types.UpdateParams) (types.Report, e
|
||||||
staleCheckFailed := 0
|
staleCheckFailed := 0
|
||||||
|
|
||||||
for i, targetContainer := range containers {
|
for i, targetContainer := range containers {
|
||||||
stale, newestImage, err := client.IsContainerStale(targetContainer)
|
stale, newestImage, err := client.IsContainerStale(targetContainer, params)
|
||||||
shouldUpdate := stale && !params.NoRestart && !params.MonitorOnly && !targetContainer.IsMonitorOnly()
|
shouldUpdate := stale && !params.NoRestart && !targetContainer.IsMonitorOnly(params)
|
||||||
if err == nil && shouldUpdate {
|
if err == nil && shouldUpdate {
|
||||||
// Check to make sure we have all the necessary information for recreating the container
|
// Check to make sure we have all the necessary information for recreating the container
|
||||||
err = targetContainer.VerifyConfiguration()
|
err = targetContainer.VerifyConfiguration()
|
||||||
|
@ -72,12 +72,10 @@ func Update(client container.Client, params types.UpdateParams) (types.Report, e
|
||||||
UpdateImplicitRestart(containers)
|
UpdateImplicitRestart(containers)
|
||||||
|
|
||||||
var containersToUpdate []types.Container
|
var containersToUpdate []types.Container
|
||||||
if !params.MonitorOnly {
|
for _, c := range containers {
|
||||||
for _, c := range containers {
|
if !c.IsMonitorOnly(params) {
|
||||||
if !c.IsMonitorOnly() {
|
containersToUpdate = append(containersToUpdate, c)
|
||||||
containersToUpdate = append(containersToUpdate, c)
|
progress.MarkForUpdate(c.ID())
|
||||||
progress.MarkForUpdate(c.ID())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -178,12 +178,84 @@ var _ = Describe("the update action", func() {
|
||||||
false,
|
false,
|
||||||
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(err).NotTo(HaveOccurred())
|
||||||
Expect(client.TestData.TriedToRemoveImageCount).To(Equal(0))
|
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() {
|
When("watchtower has been instructed to run lifecycle hooks", func() {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -24,9 +23,9 @@ var defaultInterval = int((time.Hour * 24).Seconds())
|
||||||
// RegisterDockerFlags that are used directly by the docker api client
|
// RegisterDockerFlags that are used directly by the docker api client
|
||||||
func RegisterDockerFlags(rootCmd *cobra.Command) {
|
func RegisterDockerFlags(rootCmd *cobra.Command) {
|
||||||
flags := rootCmd.PersistentFlags()
|
flags := rootCmd.PersistentFlags()
|
||||||
flags.StringP("host", "H", viper.GetString("DOCKER_HOST"), "daemon socket to connect to")
|
flags.StringP("host", "H", envString("DOCKER_HOST"), "daemon socket to connect to")
|
||||||
flags.BoolP("tlsverify", "v", viper.GetBool("DOCKER_TLS_VERIFY"), "use TLS and verify the remote")
|
flags.BoolP("tlsverify", "v", envBool("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("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
|
// RegisterSystemFlags that are used by watchtower to modify the program flow
|
||||||
|
@ -35,55 +34,55 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
|
||||||
flags.IntP(
|
flags.IntP(
|
||||||
"interval",
|
"interval",
|
||||||
"i",
|
"i",
|
||||||
viper.GetInt("WATCHTOWER_POLL_INTERVAL"),
|
envInt("WATCHTOWER_POLL_INTERVAL"),
|
||||||
"Poll interval (in seconds)")
|
"Poll interval (in seconds)")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"schedule",
|
"schedule",
|
||||||
"s",
|
"s",
|
||||||
viper.GetString("WATCHTOWER_SCHEDULE"),
|
envString("WATCHTOWER_SCHEDULE"),
|
||||||
"The cron expression which defines when to update")
|
"The cron expression which defines when to update")
|
||||||
|
|
||||||
flags.DurationP(
|
flags.DurationP(
|
||||||
"stop-timeout",
|
"stop-timeout",
|
||||||
"t",
|
"t",
|
||||||
viper.GetDuration("WATCHTOWER_TIMEOUT"),
|
envDuration("WATCHTOWER_TIMEOUT"),
|
||||||
"Timeout before a container is forcefully stopped")
|
"Timeout before a container is forcefully stopped")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"no-pull",
|
"no-pull",
|
||||||
"",
|
"",
|
||||||
viper.GetBool("WATCHTOWER_NO_PULL"),
|
envBool("WATCHTOWER_NO_PULL"),
|
||||||
"Do not pull any new images")
|
"Do not pull any new images")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"no-restart",
|
"no-restart",
|
||||||
"",
|
"",
|
||||||
viper.GetBool("WATCHTOWER_NO_RESTART"),
|
envBool("WATCHTOWER_NO_RESTART"),
|
||||||
"Do not restart any containers")
|
"Do not restart any containers")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"no-startup-message",
|
"no-startup-message",
|
||||||
"",
|
"",
|
||||||
viper.GetBool("WATCHTOWER_NO_STARTUP_MESSAGE"),
|
envBool("WATCHTOWER_NO_STARTUP_MESSAGE"),
|
||||||
"Prevents watchtower from sending a startup message")
|
"Prevents watchtower from sending a startup message")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"cleanup",
|
"cleanup",
|
||||||
"c",
|
"c",
|
||||||
viper.GetBool("WATCHTOWER_CLEANUP"),
|
envBool("WATCHTOWER_CLEANUP"),
|
||||||
"Remove previously used images after updating")
|
"Remove previously used images after updating")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"remove-volumes",
|
"remove-volumes",
|
||||||
"",
|
"",
|
||||||
viper.GetBool("WATCHTOWER_REMOVE_VOLUMES"),
|
envBool("WATCHTOWER_REMOVE_VOLUMES"),
|
||||||
"Remove attached volumes before updating")
|
"Remove attached volumes before updating")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"label-enable",
|
"label-enable",
|
||||||
"e",
|
"e",
|
||||||
viper.GetBool("WATCHTOWER_LABEL_ENABLE"),
|
envBool("WATCHTOWER_LABEL_ENABLE"),
|
||||||
"Watch containers where the com.centurylinklabs.watchtower.enable label is true")
|
"Watch containers where the com.centurylinklabs.watchtower.enable label is true")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
|
@ -95,78 +94,78 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"debug",
|
"debug",
|
||||||
"d",
|
"d",
|
||||||
viper.GetBool("WATCHTOWER_DEBUG"),
|
envBool("WATCHTOWER_DEBUG"),
|
||||||
"Enable debug mode with verbose logging")
|
"Enable debug mode with verbose logging")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"trace",
|
"trace",
|
||||||
"",
|
"",
|
||||||
viper.GetBool("WATCHTOWER_TRACE"),
|
envBool("WATCHTOWER_TRACE"),
|
||||||
"Enable trace mode with very verbose logging - caution, exposes credentials")
|
"Enable trace mode with very verbose logging - caution, exposes credentials")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"monitor-only",
|
"monitor-only",
|
||||||
"m",
|
"m",
|
||||||
viper.GetBool("WATCHTOWER_MONITOR_ONLY"),
|
envBool("WATCHTOWER_MONITOR_ONLY"),
|
||||||
"Will only monitor for new images, not update the containers")
|
"Will only monitor for new images, not update the containers")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"run-once",
|
"run-once",
|
||||||
"R",
|
"R",
|
||||||
viper.GetBool("WATCHTOWER_RUN_ONCE"),
|
envBool("WATCHTOWER_RUN_ONCE"),
|
||||||
"Run once now and exit")
|
"Run once now and exit")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"include-restarting",
|
"include-restarting",
|
||||||
"",
|
"",
|
||||||
viper.GetBool("WATCHTOWER_INCLUDE_RESTARTING"),
|
envBool("WATCHTOWER_INCLUDE_RESTARTING"),
|
||||||
"Will also include restarting containers")
|
"Will also include restarting containers")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"include-stopped",
|
"include-stopped",
|
||||||
"S",
|
"S",
|
||||||
viper.GetBool("WATCHTOWER_INCLUDE_STOPPED"),
|
envBool("WATCHTOWER_INCLUDE_STOPPED"),
|
||||||
"Will also include created and exited containers")
|
"Will also include created and exited containers")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"revive-stopped",
|
"revive-stopped",
|
||||||
"",
|
"",
|
||||||
viper.GetBool("WATCHTOWER_REVIVE_STOPPED"),
|
envBool("WATCHTOWER_REVIVE_STOPPED"),
|
||||||
"Will also start stopped containers that were updated, if include-stopped is active")
|
"Will also start stopped containers that were updated, if include-stopped is active")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"enable-lifecycle-hooks",
|
"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")
|
"Enable the execution of commands triggered by pre- and post-update lifecycle hooks")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"rolling-restart",
|
"rolling-restart",
|
||||||
"",
|
"",
|
||||||
viper.GetBool("WATCHTOWER_ROLLING_RESTART"),
|
envBool("WATCHTOWER_ROLLING_RESTART"),
|
||||||
"Restart containers one at a time")
|
"Restart containers one at a time")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"http-api-update",
|
"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")
|
"Runs Watchtower in HTTP API mode, so that image updates must to be triggered by a request")
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"http-api-metrics",
|
"http-api-metrics",
|
||||||
"",
|
"",
|
||||||
viper.GetBool("WATCHTOWER_HTTP_API_METRICS"),
|
envBool("WATCHTOWER_HTTP_API_METRICS"),
|
||||||
"Runs Watchtower with the Prometheus metrics API enabled")
|
"Runs Watchtower with the Prometheus metrics API enabled")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"http-api-token",
|
"http-api-token",
|
||||||
"",
|
"",
|
||||||
viper.GetString("WATCHTOWER_HTTP_API_TOKEN"),
|
envString("WATCHTOWER_HTTP_API_TOKEN"),
|
||||||
"Sets an authentication token to HTTP API requests.")
|
"Sets an authentication token to HTTP API requests.")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"http-api-periodic-polls",
|
"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")
|
"Also run periodic updates (specified with --interval and --schedule) if HTTP API is enabled")
|
||||||
|
|
||||||
// https://no-color.org/
|
// https://no-color.org/
|
||||||
|
@ -179,19 +178,25 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"scope",
|
"scope",
|
||||||
"",
|
"",
|
||||||
viper.GetString("WATCHTOWER_SCOPE"),
|
envString("WATCHTOWER_SCOPE"),
|
||||||
"Defines a monitoring scope for the Watchtower instance.")
|
"Defines a monitoring scope for the Watchtower instance.")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"porcelain",
|
"porcelain",
|
||||||
"P",
|
"P",
|
||||||
viper.GetString("WATCHTOWER_PORCELAIN"),
|
envString("WATCHTOWER_PORCELAIN"),
|
||||||
`Write session results to stdout using a stable versioned format. Supported values: "v1"`)
|
`Write session results to stdout using a stable versioned format. Supported values: "v1"`)
|
||||||
|
|
||||||
flags.String(
|
flags.String(
|
||||||
"log-level",
|
"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")
|
"The maximum log level that will be written to STDERR. Possible values: panic, fatal, error, warn, info, debug or trace")
|
||||||
|
|
||||||
|
flags.BoolP(
|
||||||
|
"label-take-precedence",
|
||||||
|
"",
|
||||||
|
viper.GetBool("WATCHTOWER_LABEL_TAKE_PRECEDENCE"),
|
||||||
|
"Label applied to containers take precedence over arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterNotificationFlags that are used by watchtower to send notifications
|
// RegisterNotificationFlags that are used by watchtower to send notifications
|
||||||
|
@ -201,177 +206,202 @@ func RegisterNotificationFlags(rootCmd *cobra.Command) {
|
||||||
flags.StringSliceP(
|
flags.StringSliceP(
|
||||||
"notifications",
|
"notifications",
|
||||||
"n",
|
"n",
|
||||||
viper.GetStringSlice("WATCHTOWER_NOTIFICATIONS"),
|
envStringSlice("WATCHTOWER_NOTIFICATIONS"),
|
||||||
" Notification types to send (valid: email, slack, msteams, gotify, shoutrrr)")
|
" Notification types to send (valid: email, slack, msteams, gotify, shoutrrr)")
|
||||||
|
|
||||||
flags.String(
|
flags.String(
|
||||||
"notifications-level",
|
"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")
|
"The log level used for sending notifications. Possible values: panic, fatal, error, warn, info or debug")
|
||||||
|
|
||||||
flags.IntP(
|
flags.IntP(
|
||||||
"notifications-delay",
|
"notifications-delay",
|
||||||
"",
|
"",
|
||||||
viper.GetInt("WATCHTOWER_NOTIFICATIONS_DELAY"),
|
envInt("WATCHTOWER_NOTIFICATIONS_DELAY"),
|
||||||
"Delay before sending notifications, expressed in seconds")
|
"Delay before sending notifications, expressed in seconds")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"notifications-hostname",
|
"notifications-hostname",
|
||||||
"",
|
"",
|
||||||
viper.GetString("WATCHTOWER_NOTIFICATIONS_HOSTNAME"),
|
envString("WATCHTOWER_NOTIFICATIONS_HOSTNAME"),
|
||||||
"Custom hostname for notification titles")
|
"Custom hostname for notification titles")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"notification-email-from",
|
"notification-email-from",
|
||||||
"",
|
"",
|
||||||
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_FROM"),
|
envString("WATCHTOWER_NOTIFICATION_EMAIL_FROM"),
|
||||||
"Address to send notification emails from")
|
"Address to send notification emails from")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"notification-email-to",
|
"notification-email-to",
|
||||||
"",
|
"",
|
||||||
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_TO"),
|
envString("WATCHTOWER_NOTIFICATION_EMAIL_TO"),
|
||||||
"Address to send notification emails to")
|
"Address to send notification emails to")
|
||||||
|
|
||||||
flags.IntP(
|
flags.IntP(
|
||||||
"notification-email-delay",
|
"notification-email-delay",
|
||||||
"",
|
"",
|
||||||
viper.GetInt("WATCHTOWER_NOTIFICATION_EMAIL_DELAY"),
|
envInt("WATCHTOWER_NOTIFICATION_EMAIL_DELAY"),
|
||||||
"Delay before sending notifications, expressed in seconds")
|
"Delay before sending notifications, expressed in seconds")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"notification-email-server",
|
"notification-email-server",
|
||||||
"",
|
"",
|
||||||
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER"),
|
envString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER"),
|
||||||
"SMTP server to send notification emails through")
|
"SMTP server to send notification emails through")
|
||||||
|
|
||||||
flags.IntP(
|
flags.IntP(
|
||||||
"notification-email-server-port",
|
"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")
|
"SMTP server port to send notification emails through")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"notification-email-server-tls-skip-verify",
|
"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.
|
`Controls whether watchtower verifies the SMTP server's certificate chain and host name.
|
||||||
Should only be used for testing.`)
|
Should only be used for testing.`)
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"notification-email-server-user",
|
"notification-email-server-user",
|
||||||
"",
|
"",
|
||||||
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER"),
|
envString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER"),
|
||||||
"SMTP server user for sending notifications")
|
"SMTP server user for sending notifications")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"notification-email-server-password",
|
"notification-email-server-password",
|
||||||
"",
|
"",
|
||||||
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD"),
|
envString("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD"),
|
||||||
"SMTP server password for sending notifications")
|
"SMTP server password for sending notifications")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"notification-email-subjecttag",
|
"notification-email-subjecttag",
|
||||||
"",
|
"",
|
||||||
viper.GetString("WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG"),
|
envString("WATCHTOWER_NOTIFICATION_EMAIL_SUBJECTTAG"),
|
||||||
"Subject prefix tag for notifications via mail")
|
"Subject prefix tag for notifications via mail")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"notification-slack-hook-url",
|
"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")
|
"The Slack Hook URL to send notifications to")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"notification-slack-identifier",
|
"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")
|
"A string which will be used to identify the messages coming from this watchtower instance")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"notification-slack-channel",
|
"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")
|
"A string which overrides the webhook's default channel. Example: #my-custom-channel")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"notification-slack-icon-emoji",
|
"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")
|
"An emoji code string to use in place of the default icon")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"notification-slack-icon-url",
|
"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")
|
"An icon image URL string to use in place of the default icon")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"notification-msteams-hook",
|
"notification-msteams-hook",
|
||||||
"",
|
"",
|
||||||
viper.GetString("WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL"),
|
envString("WATCHTOWER_NOTIFICATION_MSTEAMS_HOOK_URL"),
|
||||||
"The MSTeams WebHook URL to send notifications to")
|
"The MSTeams WebHook URL to send notifications to")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"notification-msteams-data",
|
"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")
|
"The MSTeams notifier will try to extract log entry fields as MSTeams message facts")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"notification-gotify-url",
|
"notification-gotify-url",
|
||||||
"",
|
"",
|
||||||
viper.GetString("WATCHTOWER_NOTIFICATION_GOTIFY_URL"),
|
envString("WATCHTOWER_NOTIFICATION_GOTIFY_URL"),
|
||||||
"The Gotify URL to send notifications to")
|
"The Gotify URL to send notifications to")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"notification-gotify-token",
|
"notification-gotify-token",
|
||||||
"",
|
"",
|
||||||
viper.GetString("WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN"),
|
envString("WATCHTOWER_NOTIFICATION_GOTIFY_TOKEN"),
|
||||||
"The Gotify Application required to query the Gotify API")
|
"The Gotify Application required to query the Gotify API")
|
||||||
|
|
||||||
flags.BoolP(
|
flags.BoolP(
|
||||||
"notification-gotify-tls-skip-verify",
|
"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.
|
`Controls whether watchtower verifies the Gotify server's certificate chain and host name.
|
||||||
Should only be used for testing.`)
|
Should only be used for testing.`)
|
||||||
|
|
||||||
flags.String(
|
flags.String(
|
||||||
"notification-template",
|
"notification-template",
|
||||||
viper.GetString("WATCHTOWER_NOTIFICATION_TEMPLATE"),
|
envString("WATCHTOWER_NOTIFICATION_TEMPLATE"),
|
||||||
"The shoutrrr text/template for the messages")
|
"The shoutrrr text/template for the messages")
|
||||||
|
|
||||||
flags.StringArray(
|
flags.StringArray(
|
||||||
"notification-url",
|
"notification-url",
|
||||||
viper.GetStringSlice("WATCHTOWER_NOTIFICATION_URL"),
|
envStringSlice("WATCHTOWER_NOTIFICATION_URL"),
|
||||||
"The shoutrrr URL to send notifications to")
|
"The shoutrrr URL to send notifications to")
|
||||||
|
|
||||||
flags.Bool("notification-report",
|
flags.Bool("notification-report",
|
||||||
viper.GetBool("WATCHTOWER_NOTIFICATION_REPORT"),
|
envBool("WATCHTOWER_NOTIFICATION_REPORT"),
|
||||||
"Use the session report as the notification template data")
|
"Use the session report as the notification template data")
|
||||||
|
|
||||||
flags.StringP(
|
flags.StringP(
|
||||||
"notification-title-tag",
|
"notification-title-tag",
|
||||||
"",
|
"",
|
||||||
viper.GetString("WATCHTOWER_NOTIFICATION_TITLE_TAG"),
|
envString("WATCHTOWER_NOTIFICATION_TITLE_TAG"),
|
||||||
"Title prefix tag for notifications")
|
"Title prefix tag for notifications")
|
||||||
|
|
||||||
flags.Bool("notification-skip-title",
|
flags.Bool("notification-skip-title",
|
||||||
viper.GetBool("WATCHTOWER_NOTIFICATION_SKIP_TITLE"),
|
envBool("WATCHTOWER_NOTIFICATION_SKIP_TITLE"),
|
||||||
"Do not pass the title param to notifications")
|
"Do not pass the title param to notifications")
|
||||||
|
|
||||||
flags.String(
|
flags.String(
|
||||||
"warn-on-head-failure",
|
"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")
|
"When to warn about HEAD pull requests failing. Possible values: always, auto or never")
|
||||||
|
|
||||||
flags.Bool(
|
flags.Bool(
|
||||||
"notification-log-stdout",
|
"notification-log-stdout",
|
||||||
viper.GetBool("WATCHTOWER_NOTIFICATION_LOG_STDOUT"),
|
envBool("WATCHTOWER_NOTIFICATION_LOG_STDOUT"),
|
||||||
"Write notification logs to stdout instead of logging (to stderr)")
|
"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
|
// SetDefaults provides default values for environment variables
|
||||||
func SetDefaults() {
|
func SetDefaults() {
|
||||||
viper.AutomaticEnv()
|
viper.AutomaticEnv()
|
||||||
|
@ -474,14 +504,17 @@ func GetSecretsFromFiles(rootCmd *cobra.Command) {
|
||||||
"notification-msteams-hook",
|
"notification-msteams-hook",
|
||||||
"notification-gotify-token",
|
"notification-gotify-token",
|
||||||
"notification-url",
|
"notification-url",
|
||||||
|
"http-api-token",
|
||||||
}
|
}
|
||||||
for _, secret := range secrets {
|
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.
|
// 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)
|
flag := flags.Lookup(secret)
|
||||||
if sliceValue, ok := flag.Value.(pflag.SliceValue); ok {
|
if sliceValue, ok := flag.Value.(pflag.SliceValue); ok {
|
||||||
oldValues := sliceValue.GetSlice()
|
oldValues := sliceValue.GetSlice()
|
||||||
|
@ -490,7 +523,7 @@ func getSecretFromFile(flags *pflag.FlagSet, secret string) {
|
||||||
if value != "" && isFile(value) {
|
if value != "" && isFile(value) {
|
||||||
file, err := os.Open(value)
|
file, err := os.Open(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
|
@ -500,25 +533,26 @@ func getSecretFromFile(flags *pflag.FlagSet, secret string) {
|
||||||
}
|
}
|
||||||
values = append(values, line)
|
values = append(values, line)
|
||||||
}
|
}
|
||||||
|
if err := file.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
values = append(values, value)
|
values = append(values, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sliceValue.Replace(values)
|
return sliceValue.Replace(values)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
value := flag.Value.String()
|
value := flag.Value.String()
|
||||||
if value != "" && isFile(value) {
|
if value != "" && isFile(value) {
|
||||||
file, err := ioutil.ReadFile(value)
|
content, err := os.ReadFile(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
|
||||||
err = flags.Set(secret, strings.TrimSpace(string(file)))
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
}
|
||||||
|
return flags.Set(secret, strings.TrimSpace(string(content)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isFile(s string) bool {
|
func isFile(s string) bool {
|
||||||
|
@ -571,15 +605,15 @@ func ProcessFlagAliases(flags *pflag.FlagSet) {
|
||||||
// update schedule flag to match interval if it's set, or to the default if none of them are
|
// update schedule flag to match interval if it's set, or to the default if none of them are
|
||||||
if intervalChanged || !scheduleChanged {
|
if intervalChanged || !scheduleChanged {
|
||||||
interval, _ := flags.GetInt(`interval`)
|
interval, _ := flags.GetInt(`interval`)
|
||||||
flags.Set(`schedule`, fmt.Sprintf(`@every %ds`, interval))
|
_ = flags.Set(`schedule`, fmt.Sprintf(`@every %ds`, interval))
|
||||||
}
|
}
|
||||||
|
|
||||||
if flagIsEnabled(flags, `debug`) {
|
if flagIsEnabled(flags, `debug`) {
|
||||||
flags.Set(`log-level`, `debug`)
|
_ = flags.Set(`log-level`, `debug`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if flagIsEnabled(flags, `trace`) {
|
if flagIsEnabled(flags, `trace`) {
|
||||||
flags.Set(`log-level`, `trace`)
|
_ = flags.Set(`log-level`, `trace`)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -640,7 +674,7 @@ func appendFlagValue(flags *pflag.FlagSet, name string, values ...string) error
|
||||||
|
|
||||||
if flagValues, ok := flag.Value.(pflag.SliceValue); ok {
|
if flagValues, ok := flag.Value.(pflag.SliceValue); ok {
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
flagValues.Append(value)
|
_ = flagValues.Append(value)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf(`the value for flag %q is not a slice value`, name)
|
return fmt.Errorf(`the value for flag %q is not a slice value`, name)
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
package flags
|
package flags
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEnvConfig_Defaults(t *testing.T) {
|
func TestEnvConfig_Defaults(t *testing.T) {
|
||||||
// Unset testing environments own variables, since those are not what is under test
|
// Unset testing environments own variables, since those are not what is under test
|
||||||
os.Unsetenv("DOCKER_TLS_VERIFY")
|
_ = os.Unsetenv("DOCKER_TLS_VERIFY")
|
||||||
os.Unsetenv("DOCKER_HOST")
|
_ = os.Unsetenv("DOCKER_HOST")
|
||||||
|
|
||||||
cmd := new(cobra.Command)
|
cmd := new(cobra.Command)
|
||||||
SetDefaults()
|
SetDefaults()
|
||||||
|
@ -48,10 +50,7 @@ func TestEnvConfig_Custom(t *testing.T) {
|
||||||
|
|
||||||
func TestGetSecretsFromFilesWithString(t *testing.T) {
|
func TestGetSecretsFromFilesWithString(t *testing.T) {
|
||||||
value := "supersecretstring"
|
value := "supersecretstring"
|
||||||
|
t.Setenv("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD", value)
|
||||||
err := os.Setenv("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD", value)
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer os.Unsetenv("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD")
|
|
||||||
|
|
||||||
testGetSecretsFromFiles(t, "notification-email-server-password", value)
|
testGetSecretsFromFiles(t, "notification-email-server-password", value)
|
||||||
}
|
}
|
||||||
|
@ -60,18 +59,15 @@ func TestGetSecretsFromFilesWithFile(t *testing.T) {
|
||||||
value := "megasecretstring"
|
value := "megasecretstring"
|
||||||
|
|
||||||
// Create the temporary file which will contain a secret.
|
// 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)
|
require.NoError(t, err)
|
||||||
defer os.Remove(file.Name()) // Make sure to remove the temporary file later.
|
|
||||||
|
|
||||||
// Write the secret to the temporary file.
|
// Write the secret to the temporary file.
|
||||||
secret := []byte(value)
|
_, err = file.Write([]byte(value))
|
||||||
_, err = file.Write(secret)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, file.Close())
|
||||||
|
|
||||||
err = os.Setenv("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD", file.Name())
|
t.Setenv("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD", file.Name())
|
||||||
require.NoError(t, err)
|
|
||||||
defer os.Unsetenv("WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD")
|
|
||||||
|
|
||||||
testGetSecretsFromFiles(t, "notification-email-server-password", value)
|
testGetSecretsFromFiles(t, "notification-email-server-password", value)
|
||||||
}
|
}
|
||||||
|
@ -80,16 +76,15 @@ func TestGetSliceSecretsFromFiles(t *testing.T) {
|
||||||
values := []string{"entry2", "", "entry3"}
|
values := []string{"entry2", "", "entry3"}
|
||||||
|
|
||||||
// Create the temporary file which will contain a secret.
|
// 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)
|
require.NoError(t, err)
|
||||||
defer os.Remove(file.Name()) // Make sure to remove the temporary file later.
|
|
||||||
|
|
||||||
// Write the secret to the temporary file.
|
// Write the secret to the temporary file.
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
_, err = file.WriteString("\n" + value)
|
_, err = file.WriteString("\n" + value)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
file.Close()
|
require.NoError(t, file.Close())
|
||||||
|
|
||||||
testGetSecretsFromFiles(t, "notification-url", `[entry1,entry2,entry3]`,
|
testGetSecretsFromFiles(t, "notification-url", `[entry1,entry2,entry3]`,
|
||||||
`--notification-url`, "entry1",
|
`--notification-url`, "entry1",
|
||||||
|
@ -99,6 +94,7 @@ func TestGetSliceSecretsFromFiles(t *testing.T) {
|
||||||
func testGetSecretsFromFiles(t *testing.T, flagName string, expected string, args ...string) {
|
func testGetSecretsFromFiles(t *testing.T, flagName string, expected string, args ...string) {
|
||||||
cmd := new(cobra.Command)
|
cmd := new(cobra.Command)
|
||||||
SetDefaults()
|
SetDefaults()
|
||||||
|
RegisterSystemFlags(cmd)
|
||||||
RegisterNotificationFlags(cmd)
|
RegisterNotificationFlags(cmd)
|
||||||
require.NoError(t, cmd.ParseFlags(args))
|
require.NoError(t, cmd.ParseFlags(args))
|
||||||
GetSecretsFromFiles(cmd)
|
GetSecretsFromFiles(cmd)
|
||||||
|
@ -166,9 +162,7 @@ func TestProcessFlagAliases(t *testing.T) {
|
||||||
|
|
||||||
func TestProcessFlagAliasesLogLevelFromEnvironment(t *testing.T) {
|
func TestProcessFlagAliasesLogLevelFromEnvironment(t *testing.T) {
|
||||||
cmd := new(cobra.Command)
|
cmd := new(cobra.Command)
|
||||||
err := os.Setenv("WATCHTOWER_DEBUG", `true`)
|
t.Setenv("WATCHTOWER_DEBUG", `true`)
|
||||||
require.NoError(t, err)
|
|
||||||
defer os.Unsetenv("WATCHTOWER_DEBUG")
|
|
||||||
|
|
||||||
SetDefaults()
|
SetDefaults()
|
||||||
RegisterDockerFlags(cmd)
|
RegisterDockerFlags(cmd)
|
||||||
|
@ -253,9 +247,7 @@ func TestProcessFlagAliasesSchedAndInterval(t *testing.T) {
|
||||||
func TestProcessFlagAliasesScheduleFromEnvironment(t *testing.T) {
|
func TestProcessFlagAliasesScheduleFromEnvironment(t *testing.T) {
|
||||||
cmd := new(cobra.Command)
|
cmd := new(cobra.Command)
|
||||||
|
|
||||||
err := os.Setenv("WATCHTOWER_SCHEDULE", `@hourly`)
|
t.Setenv("WATCHTOWER_SCHEDULE", `@hourly`)
|
||||||
require.NoError(t, err)
|
|
||||||
defer os.Unsetenv("WATCHTOWER_SCHEDULE")
|
|
||||||
|
|
||||||
SetDefaults()
|
SetDefaults()
|
||||||
RegisterDockerFlags(cmd)
|
RegisterDockerFlags(cmd)
|
||||||
|
@ -285,3 +277,63 @@ func TestProcessFlagAliasesInvalidPorcelaineVersion(t *testing.T) {
|
||||||
ProcessFlagAliases(flags)
|
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
|
StopContainer(t.Container, time.Duration) error
|
||||||
StartContainer(t.Container) (t.ContainerID, error)
|
StartContainer(t.Container) (t.ContainerID, error)
|
||||||
RenameContainer(t.Container, string) 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)
|
ExecuteCommand(containerID t.ContainerID, command string, timeout int) (SkipUpdate bool, err error)
|
||||||
RemoveImageByID(t.ImageID) error
|
RemoveImageByID(t.ImageID) error
|
||||||
WarnOnHeadPullFailed(container t.Container) bool
|
WarnOnHeadPullFailed(container t.Container) bool
|
||||||
|
@ -157,6 +157,22 @@ func (client dockerClient) GetContainer(containerID t.ContainerID) (t.Container,
|
||||||
return &Container{}, err
|
return &Container{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
netType, netContainerId, found := strings.Cut(string(containerInfo.HostConfig.NetworkMode), ":")
|
||||||
|
if found && netType == "container" {
|
||||||
|
parentContainer, err := client.api.ContainerInspect(bg, netContainerId)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(map[string]interface{}{
|
||||||
|
"container": containerInfo.Name,
|
||||||
|
"error": err,
|
||||||
|
"network-container": netContainerId,
|
||||||
|
}).Warnf("Unable to resolve network container: %v", err)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Replace the container ID with a container name to allow it to reference the re-created network container
|
||||||
|
containerInfo.HostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", parentContainer.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
imageInfo, _, err := client.api.ImageInspectWithRaw(bg, containerInfo.Image)
|
imageInfo, _, err := client.api.ImageInspectWithRaw(bg, containerInfo.Image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to retrieve container image info: %v", err)
|
log.Warnf("Failed to retrieve container image info: %v", err)
|
||||||
|
@ -208,11 +224,34 @@ func (client dockerClient) StopContainer(c t.Container, timeout time.Duration) e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (client dockerClient) GetNetworkConfig(c t.Container) *network.NetworkingConfig {
|
||||||
|
config := &network.NetworkingConfig{
|
||||||
|
EndpointsConfig: c.ContainerInfo().NetworkSettings.Networks,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ep := range config.EndpointsConfig {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
func (client dockerClient) StartContainer(c t.Container) (t.ContainerID, error) {
|
func (client dockerClient) StartContainer(c t.Container) (t.ContainerID, error) {
|
||||||
bg := context.Background()
|
bg := context.Background()
|
||||||
config := c.GetCreateConfig()
|
config := c.GetCreateConfig()
|
||||||
hostConfig := c.GetCreateHostConfig()
|
hostConfig := c.GetCreateHostConfig()
|
||||||
networkConfig := &network.NetworkingConfig{EndpointsConfig: c.ContainerInfo().NetworkSettings.Networks}
|
networkConfig := client.GetNetworkConfig(c)
|
||||||
|
|
||||||
// simpleNetworkConfig is a networkConfig with only 1 network.
|
// simpleNetworkConfig is a networkConfig with only 1 network.
|
||||||
// see: https://github.com/docker/docker/issues/29265
|
// see: https://github.com/docker/docker/issues/29265
|
||||||
simpleNetworkConfig := func() *network.NetworkingConfig {
|
simpleNetworkConfig := func() *network.NetworkingConfig {
|
||||||
|
@ -228,6 +267,7 @@ func (client dockerClient) StartContainer(c t.Container) (t.ContainerID, error)
|
||||||
name := c.Name()
|
name := c.Name()
|
||||||
|
|
||||||
log.Infof("Creating %s", name)
|
log.Infof("Creating %s", name)
|
||||||
|
|
||||||
createdContainer, err := client.api.ContainerCreate(bg, config, hostConfig, simpleNetworkConfig, nil, name)
|
createdContainer, err := client.api.ContainerCreate(bg, config, hostConfig, simpleNetworkConfig, nil, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -277,10 +317,10 @@ func (client dockerClient) RenameContainer(c t.Container, newName string) error
|
||||||
return client.api.ContainerRename(bg, string(c.ID()), newName)
|
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()
|
ctx := context.Background()
|
||||||
|
|
||||||
if !client.PullImages || container.IsNoPull() {
|
if container.IsNoPull(params) {
|
||||||
log.Debugf("Skipping image pull.")
|
log.Debugf("Skipping image pull.")
|
||||||
} else if err := client.PullImage(ctx, container); err != nil {
|
} else if err := client.PullImage(ctx, container); err != nil {
|
||||||
return false, container.SafeImageID(), err
|
return false, container.SafeImageID(), err
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/docker/docker/api/types/network"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containrrr/watchtower/internal/util"
|
"github.com/containrrr/watchtower/internal/util"
|
||||||
|
@ -140,7 +141,7 @@ var _ = Describe("the client", func() {
|
||||||
When("no filter is provided", func() {
|
When("no filter is provided", func() {
|
||||||
It("should return all available containers", func() {
|
It("should return all available containers", func() {
|
||||||
mockServer.AppendHandlers(mocks.ListContainersHandler("running"))
|
mockServer.AppendHandlers(mocks.ListContainersHandler("running"))
|
||||||
mockServer.AppendHandlers(mocks.GetContainerHandlers("watchtower", "running")...)
|
mockServer.AppendHandlers(mocks.GetContainerHandlers(&mocks.Watchtower, &mocks.Running)...)
|
||||||
client := dockerClient{
|
client := dockerClient{
|
||||||
api: docker,
|
api: docker,
|
||||||
ClientOptions: ClientOptions{PullImages: false},
|
ClientOptions: ClientOptions{PullImages: false},
|
||||||
|
@ -153,7 +154,7 @@ var _ = Describe("the client", func() {
|
||||||
When("a filter matching nothing", func() {
|
When("a filter matching nothing", func() {
|
||||||
It("should return an empty array", func() {
|
It("should return an empty array", func() {
|
||||||
mockServer.AppendHandlers(mocks.ListContainersHandler("running"))
|
mockServer.AppendHandlers(mocks.ListContainersHandler("running"))
|
||||||
mockServer.AppendHandlers(mocks.GetContainerHandlers("watchtower", "running")...)
|
mockServer.AppendHandlers(mocks.GetContainerHandlers(&mocks.Watchtower, &mocks.Running)...)
|
||||||
filter := filters.FilterByNames([]string{"lollercoaster"}, filters.NoFilter)
|
filter := filters.FilterByNames([]string{"lollercoaster"}, filters.NoFilter)
|
||||||
client := dockerClient{
|
client := dockerClient{
|
||||||
api: docker,
|
api: docker,
|
||||||
|
@ -167,7 +168,7 @@ var _ = Describe("the client", func() {
|
||||||
When("a watchtower filter is provided", func() {
|
When("a watchtower filter is provided", func() {
|
||||||
It("should return only the watchtower container", func() {
|
It("should return only the watchtower container", func() {
|
||||||
mockServer.AppendHandlers(mocks.ListContainersHandler("running"))
|
mockServer.AppendHandlers(mocks.ListContainersHandler("running"))
|
||||||
mockServer.AppendHandlers(mocks.GetContainerHandlers("watchtower", "running")...)
|
mockServer.AppendHandlers(mocks.GetContainerHandlers(&mocks.Watchtower, &mocks.Running)...)
|
||||||
client := dockerClient{
|
client := dockerClient{
|
||||||
api: docker,
|
api: docker,
|
||||||
ClientOptions: ClientOptions{PullImages: false},
|
ClientOptions: ClientOptions{PullImages: false},
|
||||||
|
@ -180,7 +181,7 @@ var _ = Describe("the client", func() {
|
||||||
When(`include stopped is enabled`, func() {
|
When(`include stopped is enabled`, func() {
|
||||||
It("should return both stopped and running containers", func() {
|
It("should return both stopped and running containers", func() {
|
||||||
mockServer.AppendHandlers(mocks.ListContainersHandler("running", "exited", "created"))
|
mockServer.AppendHandlers(mocks.ListContainersHandler("running", "exited", "created"))
|
||||||
mockServer.AppendHandlers(mocks.GetContainerHandlers("stopped", "watchtower", "running")...)
|
mockServer.AppendHandlers(mocks.GetContainerHandlers(&mocks.Stopped, &mocks.Watchtower, &mocks.Running)...)
|
||||||
client := dockerClient{
|
client := dockerClient{
|
||||||
api: docker,
|
api: docker,
|
||||||
ClientOptions: ClientOptions{PullImages: false, IncludeStopped: true},
|
ClientOptions: ClientOptions{PullImages: false, IncludeStopped: true},
|
||||||
|
@ -193,7 +194,7 @@ var _ = Describe("the client", func() {
|
||||||
When(`include restarting is enabled`, func() {
|
When(`include restarting is enabled`, func() {
|
||||||
It("should return both restarting and running containers", func() {
|
It("should return both restarting and running containers", func() {
|
||||||
mockServer.AppendHandlers(mocks.ListContainersHandler("running", "restarting"))
|
mockServer.AppendHandlers(mocks.ListContainersHandler("running", "restarting"))
|
||||||
mockServer.AppendHandlers(mocks.GetContainerHandlers("watchtower", "running", "restarting")...)
|
mockServer.AppendHandlers(mocks.GetContainerHandlers(&mocks.Watchtower, &mocks.Running, &mocks.Restarting)...)
|
||||||
client := dockerClient{
|
client := dockerClient{
|
||||||
api: docker,
|
api: docker,
|
||||||
ClientOptions: ClientOptions{PullImages: false, IncludeRestarting: true},
|
ClientOptions: ClientOptions{PullImages: false, IncludeRestarting: true},
|
||||||
|
@ -206,7 +207,7 @@ var _ = Describe("the client", func() {
|
||||||
When(`include restarting is disabled`, func() {
|
When(`include restarting is disabled`, func() {
|
||||||
It("should not return restarting containers", func() {
|
It("should not return restarting containers", func() {
|
||||||
mockServer.AppendHandlers(mocks.ListContainersHandler("running"))
|
mockServer.AppendHandlers(mocks.ListContainersHandler("running"))
|
||||||
mockServer.AppendHandlers(mocks.GetContainerHandlers("watchtower", "running")...)
|
mockServer.AppendHandlers(mocks.GetContainerHandlers(&mocks.Watchtower, &mocks.Running)...)
|
||||||
client := dockerClient{
|
client := dockerClient{
|
||||||
api: docker,
|
api: docker,
|
||||||
ClientOptions: ClientOptions{PullImages: false, IncludeRestarting: false},
|
ClientOptions: ClientOptions{PullImages: false, IncludeRestarting: false},
|
||||||
|
@ -216,6 +217,36 @@ var _ = Describe("the client", func() {
|
||||||
Expect(containers).NotTo(ContainElement(havingRestartingState(true)))
|
Expect(containers).NotTo(ContainElement(havingRestartingState(true)))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
When(`a container uses container network mode`, func() {
|
||||||
|
When(`the network container can be resolved`, func() {
|
||||||
|
It("should return the container name instead of the ID", func() {
|
||||||
|
consumerContainerRef := mocks.NetConsumerOK
|
||||||
|
mockServer.AppendHandlers(mocks.GetContainerHandlers(&consumerContainerRef)...)
|
||||||
|
client := dockerClient{
|
||||||
|
api: docker,
|
||||||
|
ClientOptions: ClientOptions{PullImages: false},
|
||||||
|
}
|
||||||
|
container, err := client.GetContainer(consumerContainerRef.ContainerID())
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
networkMode := container.ContainerInfo().HostConfig.NetworkMode
|
||||||
|
Expect(networkMode.ConnectedContainer()).To(Equal(mocks.NetSupplierContainerName))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
When(`the network container cannot be resolved`, func() {
|
||||||
|
It("should still return the container ID", func() {
|
||||||
|
consumerContainerRef := mocks.NetConsumerInvalidSupplier
|
||||||
|
mockServer.AppendHandlers(mocks.GetContainerHandlers(&consumerContainerRef)...)
|
||||||
|
client := dockerClient{
|
||||||
|
api: docker,
|
||||||
|
ClientOptions: ClientOptions{PullImages: false},
|
||||||
|
}
|
||||||
|
container, err := client.GetContainer(consumerContainerRef.ContainerID())
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
networkMode := container.ContainerInfo().HostConfig.NetworkMode
|
||||||
|
Expect(networkMode.ConnectedContainer()).To(Equal(mocks.NetSupplierNotFoundID))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
Describe(`ExecuteCommand`, func() {
|
Describe(`ExecuteCommand`, func() {
|
||||||
When(`logging`, func() {
|
When(`logging`, func() {
|
||||||
|
@ -284,6 +315,25 @@ var _ = Describe("the client", func() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
Describe(`GetNetworkConfig`, func() {
|
||||||
|
When(`providing a container with network aliases`, func() {
|
||||||
|
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(Equal([]string{"One", "Two", "Four"}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Capture logrus output in buffer
|
// Capture logrus output in buffer
|
||||||
|
|
|
@ -2,12 +2,14 @@
|
||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containrrr/watchtower/internal/util"
|
"github.com/containrrr/watchtower/internal/util"
|
||||||
wt "github.com/containrrr/watchtower/pkg/types"
|
wt "github.com/containrrr/watchtower/pkg/types"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
dockercontainer "github.com/docker/docker/api/types/container"
|
dockercontainer "github.com/docker/docker/api/types/container"
|
||||||
|
@ -129,36 +131,31 @@ func (c Container) Enabled() (bool, bool) {
|
||||||
return parsedBool, true
|
return parsedBool, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsMonitorOnly returns the value of the monitor-only label. If the label
|
// IsMonitorOnly returns whether the container should only be monitored based on values of
|
||||||
// is not set then false is returned.
|
// the monitor-only label, the monitor-only argument and the label-take-precedence argument.
|
||||||
func (c Container) IsMonitorOnly() bool {
|
func (c Container) IsMonitorOnly(params wt.UpdateParams) bool {
|
||||||
rawBool, ok := c.getLabelValue(monitorOnlyLabel)
|
return c.getContainerOrGlobalBool(params.MonitorOnly, monitorOnlyLabel, params.LabelPrecedence)
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedBool, err := strconv.ParseBool(rawBool)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedBool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNoPull returns the value of the no-pull label. If the label is not set
|
// IsNoPull returns whether the image should be pulled based on values of
|
||||||
// then false is returned.
|
// the no-pull label, the no-pull argument and the label-take-precedence argument.
|
||||||
func (c Container) IsNoPull() bool {
|
func (c Container) IsNoPull(params wt.UpdateParams) bool {
|
||||||
rawBool, ok := c.getLabelValue(noPullLabel)
|
return c.getContainerOrGlobalBool(params.NoPull, noPullLabel, params.LabelPrecedence)
|
||||||
if !ok {
|
}
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedBool, err := strconv.ParseBool(rawBool)
|
func (c Container) getContainerOrGlobalBool(globalVal bool, label string, contPrecedence bool) bool {
|
||||||
if err != nil {
|
if contVal, err := c.getBoolLabelValue(label); err != nil {
|
||||||
return false
|
if !errors.Is(err, errorLabelNotFound) {
|
||||||
|
logrus.WithField("error", err).WithField("label", label).Warn("Failed to parse label value")
|
||||||
|
}
|
||||||
|
return globalVal
|
||||||
|
} else {
|
||||||
|
if contPrecedence {
|
||||||
|
return contVal
|
||||||
|
} else {
|
||||||
|
return contVal || globalVal
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsedBool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scope returns the value of the scope UID label and if the label
|
// Scope returns the value of the scope UID label and if the label
|
||||||
|
@ -196,6 +193,13 @@ func (c Container) Links() []string {
|
||||||
name := strings.Split(link, ":")[0]
|
name := strings.Split(link, ":")[0]
|
||||||
links = append(links, name)
|
links = append(links, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the container uses another container for networking, it can be considered an implicit link
|
||||||
|
// since the container would stop working if the network supplier were to be recreated
|
||||||
|
networkMode := c.containerInfo.HostConfig.NetworkMode
|
||||||
|
if networkMode.IsContainer() {
|
||||||
|
links = append(links, networkMode.ConnectedContainer())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return links
|
return links
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/containrrr/watchtower/pkg/types"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
@ -215,34 +216,72 @@ var _ = Describe("the container", func() {
|
||||||
})
|
})
|
||||||
|
|
||||||
When("checking no-pull label", func() {
|
When("checking no-pull label", func() {
|
||||||
When("no-pull label is true", func() {
|
When("no-pull argument is not set", func() {
|
||||||
c := MockContainer(WithLabels(map[string]string{
|
When("no-pull label is true", func() {
|
||||||
"com.centurylinklabs.watchtower.no-pull": "true",
|
c := MockContainer(WithLabels(map[string]string{
|
||||||
}))
|
"com.centurylinklabs.watchtower.no-pull": "true",
|
||||||
It("should return true", func() {
|
}))
|
||||||
Expect(c.IsNoPull()).To(Equal(true))
|
It("should return true", func() {
|
||||||
|
Expect(c.IsNoPull(types.UpdateParams{})).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{})).To(Equal(false))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
When("no-pull label is set to an invalid value", func() {
|
||||||
|
c := MockContainer(WithLabels(map[string]string{
|
||||||
|
"com.centurylinklabs.watchtower.no-pull": "maybe",
|
||||||
|
}))
|
||||||
|
It("should return false", func() {
|
||||||
|
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(types.UpdateParams{})).To(Equal(false))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
When("no-pull label is false", func() {
|
When("no-pull argument is set to true", func() {
|
||||||
c := MockContainer(WithLabels(map[string]string{
|
When("no-pull label is true", func() {
|
||||||
"com.centurylinklabs.watchtower.no-pull": "false",
|
c := MockContainer(WithLabels(map[string]string{
|
||||||
}))
|
"com.centurylinklabs.watchtower.no-pull": "true",
|
||||||
It("should return false", func() {
|
}))
|
||||||
Expect(c.IsNoPull()).To(Equal(false))
|
It("should return true", func() {
|
||||||
|
Expect(c.IsNoPull(types.UpdateParams{NoPull: true})).To(Equal(true))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
When("no-pull label is false", func() {
|
||||||
When("no-pull label is set to an invalid value", func() {
|
c := MockContainer(WithLabels(map[string]string{
|
||||||
c := MockContainer(WithLabels(map[string]string{
|
"com.centurylinklabs.watchtower.no-pull": "false",
|
||||||
"com.centurylinklabs.watchtower.no-pull": "maybe",
|
}))
|
||||||
}))
|
It("should return true", func() {
|
||||||
It("should return false", func() {
|
Expect(c.IsNoPull(types.UpdateParams{NoPull: true})).To(Equal(true))
|
||||||
Expect(c.IsNoPull()).To(Equal(false))
|
})
|
||||||
})
|
})
|
||||||
})
|
When("label-take-precedence argument is set to true", func() {
|
||||||
When("no-pull label is unset", func() {
|
When("no-pull label is true", func() {
|
||||||
c = MockContainer(WithLabels(map[string]string{}))
|
c := MockContainer(WithLabels(map[string]string{
|
||||||
It("should return false", func() {
|
"com.centurylinklabs.watchtower.no-pull": "true",
|
||||||
Expect(c.IsNoPull()).To(Equal(false))
|
}))
|
||||||
|
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 errorNoImageInfo = errors.New("no available image info")
|
||||||
var errorNoContainerInfo = errors.New("no available container info")
|
var errorNoContainerInfo = errors.New("no available container info")
|
||||||
var errorInvalidConfig = errors.New("container configuration missing or invalid")
|
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
|
package container
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
watchtowerLabel = "com.centurylinklabs.watchtower"
|
watchtowerLabel = "com.centurylinklabs.watchtower"
|
||||||
signalLabel = "com.centurylinklabs.watchtower.stop-signal"
|
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]
|
val, ok := c.containerInfo.Config.Labels[label]
|
||||||
return val, ok
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -3,12 +3,15 @@ package mocks
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"github.com/onsi/ginkgo"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
t "github.com/containrrr/watchtower/pkg/types"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
O "github.com/onsi/gomega"
|
O "github.com/onsi/gomega"
|
||||||
|
@ -17,10 +20,9 @@ import (
|
||||||
|
|
||||||
func getMockJSONFile(relPath string) ([]byte, error) {
|
func getMockJSONFile(relPath string) ([]byte, error) {
|
||||||
absPath, _ := filepath.Abs(relPath)
|
absPath, _ := filepath.Abs(relPath)
|
||||||
buf, err := ioutil.ReadFile(absPath)
|
buf, err := os.ReadFile(absPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// logrus.WithError(err).WithField("file", absPath).Error(err)
|
return nil, fmt.Errorf("mock JSON file %q not found: %e", absPath, err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
return buf, nil
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
@ -41,19 +43,22 @@ func respondWithJSONFile(relPath string, statusCode int, optionalHeader ...http.
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContainerHandlers returns the handlers serving lookups for the supplied container mock files
|
// GetContainerHandlers returns the handlers serving lookups for the supplied container mock files
|
||||||
func GetContainerHandlers(containerFiles ...string) []http.HandlerFunc {
|
func GetContainerHandlers(containerRefs ...*ContainerRef) []http.HandlerFunc {
|
||||||
handlers := make([]http.HandlerFunc, 0, len(containerFiles)*2)
|
handlers := make([]http.HandlerFunc, 0, len(containerRefs)*3)
|
||||||
for _, file := range containerFiles {
|
for _, containerRef := range containerRefs {
|
||||||
handlers = append(handlers, getContainerFileHandler(file))
|
handlers = append(handlers, getContainerFileHandler(containerRef))
|
||||||
|
|
||||||
|
// Also append any containers that the container references, if any
|
||||||
|
for _, ref := range containerRef.references {
|
||||||
|
handlers = append(handlers, getContainerFileHandler(ref))
|
||||||
|
}
|
||||||
|
|
||||||
// Also append the image request since that will be called for every container
|
// Also append the image request since that will be called for every container
|
||||||
if file == "running" {
|
handlers = append(handlers, getImageHandler(containerRef.image.id,
|
||||||
// The "running" container is the only one using image02
|
RespondWithJSONFile(containerRef.image.getFileName(), http.StatusOK),
|
||||||
handlers = append(handlers, getImageFileHandler(1))
|
))
|
||||||
} else {
|
|
||||||
handlers = append(handlers, getImageFileHandler(0))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return handlers
|
return handlers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,24 +70,90 @@ func createFilterArgs(statuses []string) filters.Args {
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
var containerFileIds = map[string]string{
|
var defaultImage = imageRef{
|
||||||
"stopped": "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65",
|
// watchtower
|
||||||
"watchtower": "3d88e0e3543281c747d88b27e246578b65ae8964ba86c7cd7522cf84e0978134",
|
id: t.ImageID("sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa"),
|
||||||
"running": "b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008",
|
file: "default",
|
||||||
"restarting": "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b67",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var imageIds = []string{
|
var Watchtower = ContainerRef{
|
||||||
"sha256:4dbc5f9c07028a985e14d1393e849ea07f68804c4293050d5a641b138db72daa",
|
name: "watchtower",
|
||||||
"sha256:19d07168491a3f9e2798a9bed96544e34d57ddc4757a4ac5bb199dea896c87fd",
|
id: "3d88e0e3543281c747d88b27e246578b65ae8964ba86c7cd7522cf84e0978134",
|
||||||
|
image: &defaultImage,
|
||||||
|
}
|
||||||
|
var Stopped = ContainerRef{
|
||||||
|
name: "stopped",
|
||||||
|
id: "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b65",
|
||||||
|
image: &defaultImage,
|
||||||
|
}
|
||||||
|
var Running = ContainerRef{
|
||||||
|
name: "running",
|
||||||
|
id: "b978af0b858aa8855cce46b628817d4ed58e58f2c4f66c9b9c5449134ed4c008",
|
||||||
|
image: &imageRef{
|
||||||
|
// portainer
|
||||||
|
id: t.ImageID("sha256:19d07168491a3f9e2798a9bed96544e34d57ddc4757a4ac5bb199dea896c87fd"),
|
||||||
|
file: "running",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var Restarting = ContainerRef{
|
||||||
|
name: "restarting",
|
||||||
|
id: "ae8964ba86c7cd7522cf84e09781343d88e0e3543281c747d88b27e246578b67",
|
||||||
|
image: &defaultImage,
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContainerFileHandler(file string) http.HandlerFunc {
|
var netSupplierOK = ContainerRef{
|
||||||
id, ok := containerFileIds[file]
|
id: "25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2",
|
||||||
failTestUnless(ok)
|
name: "net_supplier",
|
||||||
|
image: &imageRef{
|
||||||
|
// gluetun
|
||||||
|
id: t.ImageID("sha256:c22b543d33bfdcb9992cbef23961677133cdf09da71d782468ae2517138bad51"),
|
||||||
|
file: "net_producer",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var netSupplierNotFound = ContainerRef{
|
||||||
|
id: NetSupplierNotFoundID,
|
||||||
|
name: netSupplierOK.name,
|
||||||
|
isMissing: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetConsumerOK is used for testing `container` networking mode
|
||||||
|
// returns a container that consumes an existing supplier container
|
||||||
|
var NetConsumerOK = ContainerRef{
|
||||||
|
id: "1f6b79d2aff23244382026c76f4995851322bed5f9c50631620162f6f9aafbd6",
|
||||||
|
name: "net_consumer",
|
||||||
|
image: &imageRef{
|
||||||
|
id: t.ImageID("sha256:904b8cb13b932e23230836850610fa45dce9eb0650d5618c2b1487c2a4f577b8"), // nginx
|
||||||
|
file: "net_consumer",
|
||||||
|
},
|
||||||
|
references: []*ContainerRef{&netSupplierOK},
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetConsumerInvalidSupplier is used for testing `container` networking mode
|
||||||
|
// returns a container that references a supplying container that does not exist
|
||||||
|
var NetConsumerInvalidSupplier = ContainerRef{
|
||||||
|
id: NetConsumerOK.id,
|
||||||
|
name: "net_consumer-missing_supplier",
|
||||||
|
image: NetConsumerOK.image,
|
||||||
|
references: []*ContainerRef{&netSupplierNotFound},
|
||||||
|
}
|
||||||
|
|
||||||
|
const NetSupplierNotFoundID = "badc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc"
|
||||||
|
const NetSupplierContainerName = "/wt-contnet-producer-1"
|
||||||
|
|
||||||
|
func getContainerFileHandler(cr *ContainerRef) http.HandlerFunc {
|
||||||
|
|
||||||
|
if cr.isMissing {
|
||||||
|
return containerNotFoundResponse(string(cr.id))
|
||||||
|
}
|
||||||
|
|
||||||
|
containerFile, err := cr.getContainerFile()
|
||||||
|
if err != nil {
|
||||||
|
ginkgo.Fail(fmt.Sprintf("Failed to get container mock file: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
return getContainerHandler(
|
return getContainerHandler(
|
||||||
id,
|
string(cr.id),
|
||||||
RespondWithJSONFile(fmt.Sprintf("./mocks/data/container_%v.json", file), http.StatusOK),
|
RespondWithJSONFile(containerFile, http.StatusOK),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +175,7 @@ func GetContainerHandler(containerID string, containerInfo *types.ContainerJSON)
|
||||||
|
|
||||||
// GetImageHandler mocks the GET images/{id}/json endpoint
|
// GetImageHandler mocks the GET images/{id}/json endpoint
|
||||||
func GetImageHandler(imageInfo *types.ImageInspect) http.HandlerFunc {
|
func GetImageHandler(imageInfo *types.ImageInspect) http.HandlerFunc {
|
||||||
return getImageHandler(imageInfo.ID, ghttp.RespondWithJSONEncoded(http.StatusOK, imageInfo))
|
return getImageHandler(t.ImageID(imageInfo.ID), ghttp.RespondWithJSONEncoded(http.StatusOK, imageInfo))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListContainersHandler mocks the GET containers/json endpoint, filtering the returned containers based on statuses
|
// ListContainersHandler mocks the GET containers/json endpoint, filtering the returned containers based on statuses
|
||||||
|
@ -138,23 +209,13 @@ func respondWithFilteredContainers(filters filters.Args) http.HandlerFunc {
|
||||||
return ghttp.RespondWithJSONEncoded(http.StatusOK, filteredContainers)
|
return ghttp.RespondWithJSONEncoded(http.StatusOK, filteredContainers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getImageHandler(imageId string, responseHandler http.HandlerFunc) http.HandlerFunc {
|
func getImageHandler(imageId t.ImageID, responseHandler http.HandlerFunc) http.HandlerFunc {
|
||||||
return ghttp.CombineHandlers(
|
return ghttp.CombineHandlers(
|
||||||
ghttp.VerifyRequest("GET", O.HaveSuffix("/images/%s/json", imageId)),
|
ghttp.VerifyRequest("GET", O.HaveSuffix("/images/%s/json", imageId)),
|
||||||
responseHandler,
|
responseHandler,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getImageFileHandler(index int) http.HandlerFunc {
|
|
||||||
return getImageHandler(imageIds[index],
|
|
||||||
RespondWithJSONFile(fmt.Sprintf("./mocks/data/image%02d.json", index+1), http.StatusOK),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func failTestUnless(ok bool) {
|
|
||||||
O.ExpectWithOffset(2, ok).To(O.BeTrue(), "test setup failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// KillContainerHandler mocks the POST containers/{id}/kill endpoint
|
// KillContainerHandler mocks the POST containers/{id}/kill endpoint
|
||||||
func KillContainerHandler(containerID string, found FoundStatus) http.HandlerFunc {
|
func KillContainerHandler(containerID string, found FoundStatus) http.HandlerFunc {
|
||||||
responseHandler := noContentStatusResponse
|
responseHandler := noContentStatusResponse
|
||||||
|
@ -180,7 +241,7 @@ func RemoveContainerHandler(containerID string, found FoundStatus) http.HandlerF
|
||||||
}
|
}
|
||||||
|
|
||||||
func containerNotFoundResponse(containerID string) http.HandlerFunc {
|
func containerNotFoundResponse(containerID string) http.HandlerFunc {
|
||||||
return ghttp.RespondWithJSONEncoded(http.StatusNotFound, struct{ message string }{message: "No such container: " + containerID})
|
return ghttp.RespondWithJSONEncoded(http.StatusNotFound, struct{ message string }{message: "No such container: " + string(containerID)})
|
||||||
}
|
}
|
||||||
|
|
||||||
var noContentStatusResponse = ghttp.RespondWith(http.StatusNoContent, nil)
|
var noContentStatusResponse = ghttp.RespondWith(http.StatusNoContent, nil)
|
||||||
|
|
42
pkg/container/mocks/container_ref.go
Normal file
42
pkg/container/mocks/container_ref.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
t "github.com/containrrr/watchtower/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type imageRef struct {
|
||||||
|
id t.ImageID
|
||||||
|
file string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ir *imageRef) getFileName() string {
|
||||||
|
return fmt.Sprintf("./mocks/data/image_%v.json", ir.file)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContainerRef struct {
|
||||||
|
name string
|
||||||
|
id t.ContainerID
|
||||||
|
image *imageRef
|
||||||
|
file string
|
||||||
|
references []*ContainerRef
|
||||||
|
isMissing bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *ContainerRef) getContainerFile() (containerFile string, err error) {
|
||||||
|
file := cr.file
|
||||||
|
if file == "" {
|
||||||
|
file = cr.name
|
||||||
|
}
|
||||||
|
|
||||||
|
containerFile = fmt.Sprintf("./mocks/data/container_%v.json", file)
|
||||||
|
_, err = os.Stat(containerFile)
|
||||||
|
|
||||||
|
return containerFile, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *ContainerRef) ContainerID() t.ContainerID {
|
||||||
|
return cr.id
|
||||||
|
}
|
|
@ -0,0 +1,205 @@
|
||||||
|
{
|
||||||
|
"Id": "1f6b79d2aff23244382026c76f4995851322bed5f9c50631620162f6f9aafbd6",
|
||||||
|
"Created": "2023-07-25T14:55:14.69155887Z",
|
||||||
|
"Path": "/docker-entrypoint.sh",
|
||||||
|
"Args": [
|
||||||
|
"nginx",
|
||||||
|
"-g",
|
||||||
|
"daemon off;"
|
||||||
|
],
|
||||||
|
"State": {
|
||||||
|
"Status": "running",
|
||||||
|
"Running": true,
|
||||||
|
"Paused": false,
|
||||||
|
"Restarting": false,
|
||||||
|
"OOMKilled": false,
|
||||||
|
"Dead": false,
|
||||||
|
"Pid": 3743,
|
||||||
|
"ExitCode": 0,
|
||||||
|
"Error": "",
|
||||||
|
"StartedAt": "2023-07-25T14:55:15.299654437Z",
|
||||||
|
"FinishedAt": "0001-01-01T00:00:00Z"
|
||||||
|
},
|
||||||
|
"Image": "sha256:904b8cb13b932e23230836850610fa45dce9eb0650d5618c2b1487c2a4f577b8",
|
||||||
|
"ResolvConfPath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/resolv.conf",
|
||||||
|
"HostnamePath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/hostname",
|
||||||
|
"HostsPath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/hosts",
|
||||||
|
"LogPath": "/var/lib/docker/containers/1f6b79d2aff23244382026c76f4995851322bed5f9c50631620162f6f9aafbd6/1f6b79d2aff23244382026c76f4995851322bed5f9c50631620162f6f9aafbd6-json.log",
|
||||||
|
"Name": "/wt-contnet-consumer-1",
|
||||||
|
"RestartCount": 0,
|
||||||
|
"Driver": "overlay2",
|
||||||
|
"Platform": "linux",
|
||||||
|
"MountLabel": "",
|
||||||
|
"ProcessLabel": "",
|
||||||
|
"AppArmorProfile": "",
|
||||||
|
"ExecIDs": null,
|
||||||
|
"HostConfig": {
|
||||||
|
"Binds": null,
|
||||||
|
"ContainerIDFile": "",
|
||||||
|
"LogConfig": {
|
||||||
|
"Type": "json-file",
|
||||||
|
"Config": {}
|
||||||
|
},
|
||||||
|
"NetworkMode": "container:badc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc1dbadc",
|
||||||
|
"PortBindings": {},
|
||||||
|
"RestartPolicy": {
|
||||||
|
"Name": "",
|
||||||
|
"MaximumRetryCount": 0
|
||||||
|
},
|
||||||
|
"AutoRemove": false,
|
||||||
|
"VolumeDriver": "",
|
||||||
|
"VolumesFrom": null,
|
||||||
|
"ConsoleSize": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"CapAdd": null,
|
||||||
|
"CapDrop": null,
|
||||||
|
"CgroupnsMode": "host",
|
||||||
|
"Dns": null,
|
||||||
|
"DnsOptions": null,
|
||||||
|
"DnsSearch": null,
|
||||||
|
"ExtraHosts": [],
|
||||||
|
"GroupAdd": null,
|
||||||
|
"IpcMode": "private",
|
||||||
|
"Cgroup": "",
|
||||||
|
"Links": null,
|
||||||
|
"OomScoreAdj": 0,
|
||||||
|
"PidMode": "",
|
||||||
|
"Privileged": false,
|
||||||
|
"PublishAllPorts": false,
|
||||||
|
"ReadonlyRootfs": false,
|
||||||
|
"SecurityOpt": null,
|
||||||
|
"UTSMode": "",
|
||||||
|
"UsernsMode": "",
|
||||||
|
"ShmSize": 67108864,
|
||||||
|
"Runtime": "runc",
|
||||||
|
"Isolation": "",
|
||||||
|
"CpuShares": 0,
|
||||||
|
"Memory": 0,
|
||||||
|
"NanoCpus": 0,
|
||||||
|
"CgroupParent": "",
|
||||||
|
"BlkioWeight": 0,
|
||||||
|
"BlkioWeightDevice": null,
|
||||||
|
"BlkioDeviceReadBps": null,
|
||||||
|
"BlkioDeviceWriteBps": null,
|
||||||
|
"BlkioDeviceReadIOps": null,
|
||||||
|
"BlkioDeviceWriteIOps": null,
|
||||||
|
"CpuPeriod": 0,
|
||||||
|
"CpuQuota": 0,
|
||||||
|
"CpuRealtimePeriod": 0,
|
||||||
|
"CpuRealtimeRuntime": 0,
|
||||||
|
"CpusetCpus": "",
|
||||||
|
"CpusetMems": "",
|
||||||
|
"Devices": null,
|
||||||
|
"DeviceCgroupRules": null,
|
||||||
|
"DeviceRequests": null,
|
||||||
|
"MemoryReservation": 0,
|
||||||
|
"MemorySwap": 0,
|
||||||
|
"MemorySwappiness": null,
|
||||||
|
"OomKillDisable": false,
|
||||||
|
"PidsLimit": null,
|
||||||
|
"Ulimits": null,
|
||||||
|
"CpuCount": 0,
|
||||||
|
"CpuPercent": 0,
|
||||||
|
"IOMaximumIOps": 0,
|
||||||
|
"IOMaximumBandwidth": 0,
|
||||||
|
"MaskedPaths": [
|
||||||
|
"/proc/asound",
|
||||||
|
"/proc/acpi",
|
||||||
|
"/proc/kcore",
|
||||||
|
"/proc/keys",
|
||||||
|
"/proc/latency_stats",
|
||||||
|
"/proc/timer_list",
|
||||||
|
"/proc/timer_stats",
|
||||||
|
"/proc/sched_debug",
|
||||||
|
"/proc/scsi",
|
||||||
|
"/sys/firmware"
|
||||||
|
],
|
||||||
|
"ReadonlyPaths": [
|
||||||
|
"/proc/bus",
|
||||||
|
"/proc/fs",
|
||||||
|
"/proc/irq",
|
||||||
|
"/proc/sys",
|
||||||
|
"/proc/sysrq-trigger"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"GraphDriver": {
|
||||||
|
"Data": {
|
||||||
|
"LowerDir": "/var/lib/docker/overlay2/05501c86219af9f713c74c129426cf5a17dc5e42f96f7f881f443cab100280e2-init/diff:/var/lib/docker/overlay2/105427179e5628eb7e893d53e21f42f9e76278f8b5665387ecdeed54a7231137/diff:/var/lib/docker/overlay2/09785ba17f27c783ef8b44f369f9aac0ca936000b57abf22b3c54d1e6eb8e27b/diff:/var/lib/docker/overlay2/6f8acd64ae44fd4d14bcb90c105eceba46854aa3985b5b6b317bcc5692cfc286/diff:/var/lib/docker/overlay2/73d41c15edb21c5f12cf53e313f48b5da55283aafc77d35b7bc662241879d7e7/diff:/var/lib/docker/overlay2/d97b55f3d966ae031492369a98e9e00d2bd31e520290fe2034e0a2b1ed77c91e/diff:/var/lib/docker/overlay2/053e9ca65c6b64cb9d98a812ff7488c7e77938b4fb8e0c4d2ad7f8ec235f0f20/diff",
|
||||||
|
"MergedDir": "/var/lib/docker/overlay2/05501c86219af9f713c74c129426cf5a17dc5e42f96f7f881f443cab100280e2/merged",
|
||||||
|
"UpperDir": "/var/lib/docker/overlay2/05501c86219af9f713c74c129426cf5a17dc5e42f96f7f881f443cab100280e2/diff",
|
||||||
|
"WorkDir": "/var/lib/docker/overlay2/05501c86219af9f713c74c129426cf5a17dc5e42f96f7f881f443cab100280e2/work"
|
||||||
|
},
|
||||||
|
"Name": "overlay2"
|
||||||
|
},
|
||||||
|
"Mounts": [],
|
||||||
|
"Config": {
|
||||||
|
"Hostname": "25e75393800b",
|
||||||
|
"Domainname": "",
|
||||||
|
"User": "",
|
||||||
|
"AttachStdin": false,
|
||||||
|
"AttachStdout": true,
|
||||||
|
"AttachStderr": true,
|
||||||
|
"ExposedPorts": {
|
||||||
|
"80/tcp": {}
|
||||||
|
},
|
||||||
|
"Tty": false,
|
||||||
|
"OpenStdin": false,
|
||||||
|
"StdinOnce": false,
|
||||||
|
"Env": [
|
||||||
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||||
|
"NGINX_VERSION=1.23.3",
|
||||||
|
"NJS_VERSION=0.7.9",
|
||||||
|
"PKG_RELEASE=1~bullseye"
|
||||||
|
],
|
||||||
|
"Cmd": [
|
||||||
|
"nginx",
|
||||||
|
"-g",
|
||||||
|
"daemon off;"
|
||||||
|
],
|
||||||
|
"Image": "nginx",
|
||||||
|
"Volumes": null,
|
||||||
|
"WorkingDir": "",
|
||||||
|
"Entrypoint": [
|
||||||
|
"/docker-entrypoint.sh"
|
||||||
|
],
|
||||||
|
"OnBuild": null,
|
||||||
|
"Labels": {
|
||||||
|
"com.docker.compose.config-hash": "8bb0e1c8c61f6d495840ba9133ebfb1e4ffda3e1adb701a011b03951848bb9fa",
|
||||||
|
"com.docker.compose.container-number": "1",
|
||||||
|
"com.docker.compose.depends_on": "producer:service_started:false",
|
||||||
|
"com.docker.compose.image": "sha256:904b8cb13b932e23230836850610fa45dce9eb0650d5618c2b1487c2a4f577b8",
|
||||||
|
"com.docker.compose.oneoff": "False",
|
||||||
|
"com.docker.compose.project": "wt-contnet",
|
||||||
|
"com.docker.compose.project.config_files": "/tmp/wt-contnet/docker-compose.yaml",
|
||||||
|
"com.docker.compose.project.working_dir": "/tmp/wt-contnet",
|
||||||
|
"com.docker.compose.replace": "07bb70608f96f577aa02b9f317500e23e691c94eb099f6fb52301dfb031d0668",
|
||||||
|
"com.docker.compose.service": "consumer",
|
||||||
|
"com.docker.compose.version": "2.19.1",
|
||||||
|
"desktop.docker.io/wsl-distro": "Ubuntu",
|
||||||
|
"maintainer": "NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e"
|
||||||
|
},
|
||||||
|
"StopSignal": "SIGQUIT"
|
||||||
|
},
|
||||||
|
"NetworkSettings": {
|
||||||
|
"Bridge": "",
|
||||||
|
"SandboxID": "",
|
||||||
|
"HairpinMode": false,
|
||||||
|
"LinkLocalIPv6Address": "",
|
||||||
|
"LinkLocalIPv6PrefixLen": 0,
|
||||||
|
"Ports": {},
|
||||||
|
"SandboxKey": "",
|
||||||
|
"SecondaryIPAddresses": null,
|
||||||
|
"SecondaryIPv6Addresses": null,
|
||||||
|
"EndpointID": "",
|
||||||
|
"Gateway": "",
|
||||||
|
"GlobalIPv6Address": "",
|
||||||
|
"GlobalIPv6PrefixLen": 0,
|
||||||
|
"IPAddress": "",
|
||||||
|
"IPPrefixLen": 0,
|
||||||
|
"IPv6Gateway": "",
|
||||||
|
"MacAddress": "",
|
||||||
|
"Networks": {}
|
||||||
|
}
|
||||||
|
}
|
205
pkg/container/mocks/data/container_net_consumer.json
Normal file
205
pkg/container/mocks/data/container_net_consumer.json
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
{
|
||||||
|
"Id": "1f6b79d2aff23244382026c76f4995851322bed5f9c50631620162f6f9aafbd6",
|
||||||
|
"Created": "2023-07-25T14:55:14.69155887Z",
|
||||||
|
"Path": "/docker-entrypoint.sh",
|
||||||
|
"Args": [
|
||||||
|
"nginx",
|
||||||
|
"-g",
|
||||||
|
"daemon off;"
|
||||||
|
],
|
||||||
|
"State": {
|
||||||
|
"Status": "running",
|
||||||
|
"Running": true,
|
||||||
|
"Paused": false,
|
||||||
|
"Restarting": false,
|
||||||
|
"OOMKilled": false,
|
||||||
|
"Dead": false,
|
||||||
|
"Pid": 3743,
|
||||||
|
"ExitCode": 0,
|
||||||
|
"Error": "",
|
||||||
|
"StartedAt": "2023-07-25T14:55:15.299654437Z",
|
||||||
|
"FinishedAt": "0001-01-01T00:00:00Z"
|
||||||
|
},
|
||||||
|
"Image": "sha256:904b8cb13b932e23230836850610fa45dce9eb0650d5618c2b1487c2a4f577b8",
|
||||||
|
"ResolvConfPath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/resolv.conf",
|
||||||
|
"HostnamePath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/hostname",
|
||||||
|
"HostsPath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/hosts",
|
||||||
|
"LogPath": "/var/lib/docker/containers/1f6b79d2aff23244382026c76f4995851322bed5f9c50631620162f6f9aafbd6/1f6b79d2aff23244382026c76f4995851322bed5f9c50631620162f6f9aafbd6-json.log",
|
||||||
|
"Name": "/wt-contnet-consumer-1",
|
||||||
|
"RestartCount": 0,
|
||||||
|
"Driver": "overlay2",
|
||||||
|
"Platform": "linux",
|
||||||
|
"MountLabel": "",
|
||||||
|
"ProcessLabel": "",
|
||||||
|
"AppArmorProfile": "",
|
||||||
|
"ExecIDs": null,
|
||||||
|
"HostConfig": {
|
||||||
|
"Binds": null,
|
||||||
|
"ContainerIDFile": "",
|
||||||
|
"LogConfig": {
|
||||||
|
"Type": "json-file",
|
||||||
|
"Config": {}
|
||||||
|
},
|
||||||
|
"NetworkMode": "container:25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2",
|
||||||
|
"PortBindings": {},
|
||||||
|
"RestartPolicy": {
|
||||||
|
"Name": "",
|
||||||
|
"MaximumRetryCount": 0
|
||||||
|
},
|
||||||
|
"AutoRemove": false,
|
||||||
|
"VolumeDriver": "",
|
||||||
|
"VolumesFrom": null,
|
||||||
|
"ConsoleSize": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"CapAdd": null,
|
||||||
|
"CapDrop": null,
|
||||||
|
"CgroupnsMode": "host",
|
||||||
|
"Dns": null,
|
||||||
|
"DnsOptions": null,
|
||||||
|
"DnsSearch": null,
|
||||||
|
"ExtraHosts": [],
|
||||||
|
"GroupAdd": null,
|
||||||
|
"IpcMode": "private",
|
||||||
|
"Cgroup": "",
|
||||||
|
"Links": null,
|
||||||
|
"OomScoreAdj": 0,
|
||||||
|
"PidMode": "",
|
||||||
|
"Privileged": false,
|
||||||
|
"PublishAllPorts": false,
|
||||||
|
"ReadonlyRootfs": false,
|
||||||
|
"SecurityOpt": null,
|
||||||
|
"UTSMode": "",
|
||||||
|
"UsernsMode": "",
|
||||||
|
"ShmSize": 67108864,
|
||||||
|
"Runtime": "runc",
|
||||||
|
"Isolation": "",
|
||||||
|
"CpuShares": 0,
|
||||||
|
"Memory": 0,
|
||||||
|
"NanoCpus": 0,
|
||||||
|
"CgroupParent": "",
|
||||||
|
"BlkioWeight": 0,
|
||||||
|
"BlkioWeightDevice": null,
|
||||||
|
"BlkioDeviceReadBps": null,
|
||||||
|
"BlkioDeviceWriteBps": null,
|
||||||
|
"BlkioDeviceReadIOps": null,
|
||||||
|
"BlkioDeviceWriteIOps": null,
|
||||||
|
"CpuPeriod": 0,
|
||||||
|
"CpuQuota": 0,
|
||||||
|
"CpuRealtimePeriod": 0,
|
||||||
|
"CpuRealtimeRuntime": 0,
|
||||||
|
"CpusetCpus": "",
|
||||||
|
"CpusetMems": "",
|
||||||
|
"Devices": null,
|
||||||
|
"DeviceCgroupRules": null,
|
||||||
|
"DeviceRequests": null,
|
||||||
|
"MemoryReservation": 0,
|
||||||
|
"MemorySwap": 0,
|
||||||
|
"MemorySwappiness": null,
|
||||||
|
"OomKillDisable": false,
|
||||||
|
"PidsLimit": null,
|
||||||
|
"Ulimits": null,
|
||||||
|
"CpuCount": 0,
|
||||||
|
"CpuPercent": 0,
|
||||||
|
"IOMaximumIOps": 0,
|
||||||
|
"IOMaximumBandwidth": 0,
|
||||||
|
"MaskedPaths": [
|
||||||
|
"/proc/asound",
|
||||||
|
"/proc/acpi",
|
||||||
|
"/proc/kcore",
|
||||||
|
"/proc/keys",
|
||||||
|
"/proc/latency_stats",
|
||||||
|
"/proc/timer_list",
|
||||||
|
"/proc/timer_stats",
|
||||||
|
"/proc/sched_debug",
|
||||||
|
"/proc/scsi",
|
||||||
|
"/sys/firmware"
|
||||||
|
],
|
||||||
|
"ReadonlyPaths": [
|
||||||
|
"/proc/bus",
|
||||||
|
"/proc/fs",
|
||||||
|
"/proc/irq",
|
||||||
|
"/proc/sys",
|
||||||
|
"/proc/sysrq-trigger"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"GraphDriver": {
|
||||||
|
"Data": {
|
||||||
|
"LowerDir": "/var/lib/docker/overlay2/05501c86219af9f713c74c129426cf5a17dc5e42f96f7f881f443cab100280e2-init/diff:/var/lib/docker/overlay2/105427179e5628eb7e893d53e21f42f9e76278f8b5665387ecdeed54a7231137/diff:/var/lib/docker/overlay2/09785ba17f27c783ef8b44f369f9aac0ca936000b57abf22b3c54d1e6eb8e27b/diff:/var/lib/docker/overlay2/6f8acd64ae44fd4d14bcb90c105eceba46854aa3985b5b6b317bcc5692cfc286/diff:/var/lib/docker/overlay2/73d41c15edb21c5f12cf53e313f48b5da55283aafc77d35b7bc662241879d7e7/diff:/var/lib/docker/overlay2/d97b55f3d966ae031492369a98e9e00d2bd31e520290fe2034e0a2b1ed77c91e/diff:/var/lib/docker/overlay2/053e9ca65c6b64cb9d98a812ff7488c7e77938b4fb8e0c4d2ad7f8ec235f0f20/diff",
|
||||||
|
"MergedDir": "/var/lib/docker/overlay2/05501c86219af9f713c74c129426cf5a17dc5e42f96f7f881f443cab100280e2/merged",
|
||||||
|
"UpperDir": "/var/lib/docker/overlay2/05501c86219af9f713c74c129426cf5a17dc5e42f96f7f881f443cab100280e2/diff",
|
||||||
|
"WorkDir": "/var/lib/docker/overlay2/05501c86219af9f713c74c129426cf5a17dc5e42f96f7f881f443cab100280e2/work"
|
||||||
|
},
|
||||||
|
"Name": "overlay2"
|
||||||
|
},
|
||||||
|
"Mounts": [],
|
||||||
|
"Config": {
|
||||||
|
"Hostname": "25e75393800b",
|
||||||
|
"Domainname": "",
|
||||||
|
"User": "",
|
||||||
|
"AttachStdin": false,
|
||||||
|
"AttachStdout": true,
|
||||||
|
"AttachStderr": true,
|
||||||
|
"ExposedPorts": {
|
||||||
|
"80/tcp": {}
|
||||||
|
},
|
||||||
|
"Tty": false,
|
||||||
|
"OpenStdin": false,
|
||||||
|
"StdinOnce": false,
|
||||||
|
"Env": [
|
||||||
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||||
|
"NGINX_VERSION=1.23.3",
|
||||||
|
"NJS_VERSION=0.7.9",
|
||||||
|
"PKG_RELEASE=1~bullseye"
|
||||||
|
],
|
||||||
|
"Cmd": [
|
||||||
|
"nginx",
|
||||||
|
"-g",
|
||||||
|
"daemon off;"
|
||||||
|
],
|
||||||
|
"Image": "nginx",
|
||||||
|
"Volumes": null,
|
||||||
|
"WorkingDir": "",
|
||||||
|
"Entrypoint": [
|
||||||
|
"/docker-entrypoint.sh"
|
||||||
|
],
|
||||||
|
"OnBuild": null,
|
||||||
|
"Labels": {
|
||||||
|
"com.docker.compose.config-hash": "8bb0e1c8c61f6d495840ba9133ebfb1e4ffda3e1adb701a011b03951848bb9fa",
|
||||||
|
"com.docker.compose.container-number": "1",
|
||||||
|
"com.docker.compose.depends_on": "producer:service_started:false",
|
||||||
|
"com.docker.compose.image": "sha256:904b8cb13b932e23230836850610fa45dce9eb0650d5618c2b1487c2a4f577b8",
|
||||||
|
"com.docker.compose.oneoff": "False",
|
||||||
|
"com.docker.compose.project": "wt-contnet",
|
||||||
|
"com.docker.compose.project.config_files": "/tmp/wt-contnet/docker-compose.yaml",
|
||||||
|
"com.docker.compose.project.working_dir": "/tmp/wt-contnet",
|
||||||
|
"com.docker.compose.replace": "07bb70608f96f577aa02b9f317500e23e691c94eb099f6fb52301dfb031d0668",
|
||||||
|
"com.docker.compose.service": "consumer",
|
||||||
|
"com.docker.compose.version": "2.19.1",
|
||||||
|
"desktop.docker.io/wsl-distro": "Ubuntu",
|
||||||
|
"maintainer": "NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e"
|
||||||
|
},
|
||||||
|
"StopSignal": "SIGQUIT"
|
||||||
|
},
|
||||||
|
"NetworkSettings": {
|
||||||
|
"Bridge": "",
|
||||||
|
"SandboxID": "",
|
||||||
|
"HairpinMode": false,
|
||||||
|
"LinkLocalIPv6Address": "",
|
||||||
|
"LinkLocalIPv6PrefixLen": 0,
|
||||||
|
"Ports": {},
|
||||||
|
"SandboxKey": "",
|
||||||
|
"SecondaryIPAddresses": null,
|
||||||
|
"SecondaryIPv6Addresses": null,
|
||||||
|
"EndpointID": "",
|
||||||
|
"Gateway": "",
|
||||||
|
"GlobalIPv6Address": "",
|
||||||
|
"GlobalIPv6PrefixLen": 0,
|
||||||
|
"IPAddress": "",
|
||||||
|
"IPPrefixLen": 0,
|
||||||
|
"IPv6Gateway": "",
|
||||||
|
"MacAddress": "",
|
||||||
|
"Networks": {}
|
||||||
|
}
|
||||||
|
}
|
380
pkg/container/mocks/data/container_net_supplier.json
Normal file
380
pkg/container/mocks/data/container_net_supplier.json
Normal file
|
@ -0,0 +1,380 @@
|
||||||
|
{
|
||||||
|
"Id": "25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2",
|
||||||
|
"Created": "2023-07-25T14:55:14.595662628Z",
|
||||||
|
"Path": "/gluetun-entrypoint",
|
||||||
|
"Args": [],
|
||||||
|
"State": {
|
||||||
|
"Status": "running",
|
||||||
|
"Running": true,
|
||||||
|
"Paused": false,
|
||||||
|
"Restarting": false,
|
||||||
|
"OOMKilled": false,
|
||||||
|
"Dead": false,
|
||||||
|
"Pid": 3648,
|
||||||
|
"ExitCode": 0,
|
||||||
|
"Error": "",
|
||||||
|
"StartedAt": "2023-07-25T14:55:15.193430103Z",
|
||||||
|
"FinishedAt": "0001-01-01T00:00:00Z",
|
||||||
|
"Health": {
|
||||||
|
"Status": "healthy",
|
||||||
|
"FailingStreak": 0,
|
||||||
|
"Log": [
|
||||||
|
{
|
||||||
|
"Start": "2023-07-25T15:00:32.078491228Z",
|
||||||
|
"End": "2023-07-25T15:00:32.194554876Z",
|
||||||
|
"ExitCode": 0,
|
||||||
|
"Output": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Start": "2023-07-25T15:00:37.199245496Z",
|
||||||
|
"End": "2023-07-25T15:00:37.294845687Z",
|
||||||
|
"ExitCode": 0,
|
||||||
|
"Output": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Start": "2023-07-25T15:00:42.299676089Z",
|
||||||
|
"End": "2023-07-25T15:00:42.384213818Z",
|
||||||
|
"ExitCode": 0,
|
||||||
|
"Output": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Start": "2023-07-25T15:00:47.389142447Z",
|
||||||
|
"End": "2023-07-25T15:00:47.514483294Z",
|
||||||
|
"ExitCode": 0,
|
||||||
|
"Output": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Start": "2023-07-25T15:00:52.518770886Z",
|
||||||
|
"End": "2023-07-25T15:00:52.644288742Z",
|
||||||
|
"ExitCode": 0,
|
||||||
|
"Output": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Image": "sha256:c22b543d33bfdcb9992cbef23961677133cdf09da71d782468ae2517138bad51",
|
||||||
|
"ResolvConfPath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/resolv.conf",
|
||||||
|
"HostnamePath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/hostname",
|
||||||
|
"HostsPath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/hosts",
|
||||||
|
"LogPath": "/var/lib/docker/containers/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2/25e75393800b5c450a6841212a3b92ed28fa35414a586dec9f2c8a520d4910c2-json.log",
|
||||||
|
"Name": "/wt-contnet-producer-1",
|
||||||
|
"RestartCount": 0,
|
||||||
|
"Driver": "overlay2",
|
||||||
|
"Platform": "linux",
|
||||||
|
"MountLabel": "",
|
||||||
|
"ProcessLabel": "",
|
||||||
|
"AppArmorProfile": "",
|
||||||
|
"ExecIDs": null,
|
||||||
|
"HostConfig": {
|
||||||
|
"Binds": null,
|
||||||
|
"ContainerIDFile": "",
|
||||||
|
"LogConfig": {
|
||||||
|
"Type": "json-file",
|
||||||
|
"Config": {}
|
||||||
|
},
|
||||||
|
"NetworkMode": "wt-contnet_default",
|
||||||
|
"PortBindings": {},
|
||||||
|
"RestartPolicy": {
|
||||||
|
"Name": "",
|
||||||
|
"MaximumRetryCount": 0
|
||||||
|
},
|
||||||
|
"AutoRemove": false,
|
||||||
|
"VolumeDriver": "",
|
||||||
|
"VolumesFrom": null,
|
||||||
|
"ConsoleSize": [
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"CapAdd": [
|
||||||
|
"NET_ADMIN"
|
||||||
|
],
|
||||||
|
"CapDrop": null,
|
||||||
|
"CgroupnsMode": "host",
|
||||||
|
"Dns": null,
|
||||||
|
"DnsOptions": null,
|
||||||
|
"DnsSearch": null,
|
||||||
|
"ExtraHosts": [],
|
||||||
|
"GroupAdd": null,
|
||||||
|
"IpcMode": "private",
|
||||||
|
"Cgroup": "",
|
||||||
|
"Links": null,
|
||||||
|
"OomScoreAdj": 0,
|
||||||
|
"PidMode": "",
|
||||||
|
"Privileged": false,
|
||||||
|
"PublishAllPorts": false,
|
||||||
|
"ReadonlyRootfs": false,
|
||||||
|
"SecurityOpt": null,
|
||||||
|
"UTSMode": "",
|
||||||
|
"UsernsMode": "",
|
||||||
|
"ShmSize": 67108864,
|
||||||
|
"Runtime": "runc",
|
||||||
|
"Isolation": "",
|
||||||
|
"CpuShares": 0,
|
||||||
|
"Memory": 0,
|
||||||
|
"NanoCpus": 0,
|
||||||
|
"CgroupParent": "",
|
||||||
|
"BlkioWeight": 0,
|
||||||
|
"BlkioWeightDevice": null,
|
||||||
|
"BlkioDeviceReadBps": null,
|
||||||
|
"BlkioDeviceWriteBps": null,
|
||||||
|
"BlkioDeviceReadIOps": null,
|
||||||
|
"BlkioDeviceWriteIOps": null,
|
||||||
|
"CpuPeriod": 0,
|
||||||
|
"CpuQuota": 0,
|
||||||
|
"CpuRealtimePeriod": 0,
|
||||||
|
"CpuRealtimeRuntime": 0,
|
||||||
|
"CpusetCpus": "",
|
||||||
|
"CpusetMems": "",
|
||||||
|
"Devices": null,
|
||||||
|
"DeviceCgroupRules": null,
|
||||||
|
"DeviceRequests": null,
|
||||||
|
"MemoryReservation": 0,
|
||||||
|
"MemorySwap": 0,
|
||||||
|
"MemorySwappiness": null,
|
||||||
|
"OomKillDisable": false,
|
||||||
|
"PidsLimit": null,
|
||||||
|
"Ulimits": null,
|
||||||
|
"CpuCount": 0,
|
||||||
|
"CpuPercent": 0,
|
||||||
|
"IOMaximumIOps": 0,
|
||||||
|
"IOMaximumBandwidth": 0,
|
||||||
|
"MaskedPaths": [
|
||||||
|
"/proc/asound",
|
||||||
|
"/proc/acpi",
|
||||||
|
"/proc/kcore",
|
||||||
|
"/proc/keys",
|
||||||
|
"/proc/latency_stats",
|
||||||
|
"/proc/timer_list",
|
||||||
|
"/proc/timer_stats",
|
||||||
|
"/proc/sched_debug",
|
||||||
|
"/proc/scsi",
|
||||||
|
"/sys/firmware"
|
||||||
|
],
|
||||||
|
"ReadonlyPaths": [
|
||||||
|
"/proc/bus",
|
||||||
|
"/proc/fs",
|
||||||
|
"/proc/irq",
|
||||||
|
"/proc/sys",
|
||||||
|
"/proc/sysrq-trigger"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"GraphDriver": {
|
||||||
|
"Data": {
|
||||||
|
"LowerDir": "/var/lib/docker/overlay2/618bd1e7a13880c07ec7f5bfc45012a9f81d5de452f942b49d8f49b3c67a19a2-init/diff:/var/lib/docker/overlay2/0d222a3aa067159831c4111a408e40325be1085b935c98d39c2e9a01ff50b224/diff:/var/lib/docker/overlay2/a20c9490a23ee8af51898892d9bf32258d44e0e07f3799475be8e8f273a50f73/diff:/var/lib/docker/overlay2/d4c97f367c37c6ada9de57f438a3e19cc714be2a54a6f582a03de9e42d88b344/diff",
|
||||||
|
"MergedDir": "/var/lib/docker/overlay2/618bd1e7a13880c07ec7f5bfc45012a9f81d5de452f942b49d8f49b3c67a19a2/merged",
|
||||||
|
"UpperDir": "/var/lib/docker/overlay2/618bd1e7a13880c07ec7f5bfc45012a9f81d5de452f942b49d8f49b3c67a19a2/diff",
|
||||||
|
"WorkDir": "/var/lib/docker/overlay2/618bd1e7a13880c07ec7f5bfc45012a9f81d5de452f942b49d8f49b3c67a19a2/work"
|
||||||
|
},
|
||||||
|
"Name": "overlay2"
|
||||||
|
},
|
||||||
|
"Mounts": [],
|
||||||
|
"Config": {
|
||||||
|
"Hostname": "25e75393800b",
|
||||||
|
"Domainname": "",
|
||||||
|
"User": "",
|
||||||
|
"AttachStdin": false,
|
||||||
|
"AttachStdout": true,
|
||||||
|
"AttachStderr": true,
|
||||||
|
"ExposedPorts": {
|
||||||
|
"8000/tcp": {},
|
||||||
|
"8388/tcp": {},
|
||||||
|
"8388/udp": {},
|
||||||
|
"8888/tcp": {}
|
||||||
|
},
|
||||||
|
"Tty": false,
|
||||||
|
"OpenStdin": false,
|
||||||
|
"StdinOnce": false,
|
||||||
|
"Env": [
|
||||||
|
"OPENVPN_PASSWORD=",
|
||||||
|
"SERVER_COUNTRIES=Sweden",
|
||||||
|
"VPN_SERVICE_PROVIDER=nordvpn",
|
||||||
|
"OPENVPN_USER=",
|
||||||
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||||
|
"VPN_TYPE=openvpn",
|
||||||
|
"VPN_ENDPOINT_IP=",
|
||||||
|
"VPN_ENDPOINT_PORT=",
|
||||||
|
"VPN_INTERFACE=tun0",
|
||||||
|
"OPENVPN_PROTOCOL=udp",
|
||||||
|
"OPENVPN_USER_SECRETFILE=/run/secrets/openvpn_user",
|
||||||
|
"OPENVPN_PASSWORD_SECRETFILE=/run/secrets/openvpn_password",
|
||||||
|
"OPENVPN_VERSION=2.5",
|
||||||
|
"OPENVPN_VERBOSITY=1",
|
||||||
|
"OPENVPN_FLAGS=",
|
||||||
|
"OPENVPN_CIPHERS=",
|
||||||
|
"OPENVPN_AUTH=",
|
||||||
|
"OPENVPN_PROCESS_USER=root",
|
||||||
|
"OPENVPN_CUSTOM_CONFIG=",
|
||||||
|
"WIREGUARD_PRIVATE_KEY=",
|
||||||
|
"WIREGUARD_PRESHARED_KEY=",
|
||||||
|
"WIREGUARD_PUBLIC_KEY=",
|
||||||
|
"WIREGUARD_ALLOWED_IPS=",
|
||||||
|
"WIREGUARD_ADDRESSES=",
|
||||||
|
"WIREGUARD_MTU=1400",
|
||||||
|
"WIREGUARD_IMPLEMENTATION=auto",
|
||||||
|
"SERVER_REGIONS=",
|
||||||
|
"SERVER_CITIES=",
|
||||||
|
"SERVER_HOSTNAMES=",
|
||||||
|
"ISP=",
|
||||||
|
"OWNED_ONLY=no",
|
||||||
|
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET=",
|
||||||
|
"VPN_PORT_FORWARDING=off",
|
||||||
|
"VPN_PORT_FORWARDING_PROVIDER=",
|
||||||
|
"VPN_PORT_FORWARDING_STATUS_FILE=/tmp/gluetun/forwarded_port",
|
||||||
|
"OPENVPN_CERT=",
|
||||||
|
"OPENVPN_KEY=",
|
||||||
|
"OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt",
|
||||||
|
"OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey",
|
||||||
|
"OPENVPN_ENCRYPTED_KEY=",
|
||||||
|
"OPENVPN_ENCRYPTED_KEY_SECRETFILE=/run/secrets/openvpn_encrypted_key",
|
||||||
|
"OPENVPN_KEY_PASSPHRASE=",
|
||||||
|
"OPENVPN_KEY_PASSPHRASE_SECRETFILE=/run/secrets/openvpn_key_passphrase",
|
||||||
|
"SERVER_NUMBER=",
|
||||||
|
"SERVER_NAMES=",
|
||||||
|
"FREE_ONLY=",
|
||||||
|
"MULTIHOP_ONLY=",
|
||||||
|
"PREMIUM_ONLY=",
|
||||||
|
"FIREWALL=on",
|
||||||
|
"FIREWALL_VPN_INPUT_PORTS=",
|
||||||
|
"FIREWALL_INPUT_PORTS=",
|
||||||
|
"FIREWALL_OUTBOUND_SUBNETS=",
|
||||||
|
"FIREWALL_DEBUG=off",
|
||||||
|
"LOG_LEVEL=info",
|
||||||
|
"HEALTH_SERVER_ADDRESS=127.0.0.1:9999",
|
||||||
|
"HEALTH_TARGET_ADDRESS=cloudflare.com:443",
|
||||||
|
"HEALTH_SUCCESS_WAIT_DURATION=5s",
|
||||||
|
"HEALTH_VPN_DURATION_INITIAL=6s",
|
||||||
|
"HEALTH_VPN_DURATION_ADDITION=5s",
|
||||||
|
"DOT=on",
|
||||||
|
"DOT_PROVIDERS=cloudflare",
|
||||||
|
"DOT_PRIVATE_ADDRESS=127.0.0.1/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,::1/128,fc00::/7,fe80::/10,::ffff:7f00:1/104,::ffff:a00:0/104,::ffff:a9fe:0/112,::ffff:ac10:0/108,::ffff:c0a8:0/112",
|
||||||
|
"DOT_VERBOSITY=1",
|
||||||
|
"DOT_VERBOSITY_DETAILS=0",
|
||||||
|
"DOT_VALIDATION_LOGLEVEL=0",
|
||||||
|
"DOT_CACHING=on",
|
||||||
|
"DOT_IPV6=off",
|
||||||
|
"BLOCK_MALICIOUS=on",
|
||||||
|
"BLOCK_SURVEILLANCE=off",
|
||||||
|
"BLOCK_ADS=off",
|
||||||
|
"UNBLOCK=",
|
||||||
|
"DNS_UPDATE_PERIOD=24h",
|
||||||
|
"DNS_ADDRESS=127.0.0.1",
|
||||||
|
"DNS_KEEP_NAMESERVER=off",
|
||||||
|
"HTTPPROXY=",
|
||||||
|
"HTTPPROXY_LOG=off",
|
||||||
|
"HTTPPROXY_LISTENING_ADDRESS=:8888",
|
||||||
|
"HTTPPROXY_STEALTH=off",
|
||||||
|
"HTTPPROXY_USER=",
|
||||||
|
"HTTPPROXY_PASSWORD=",
|
||||||
|
"HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user",
|
||||||
|
"HTTPPROXY_PASSWORD_SECRETFILE=/run/secrets/httpproxy_password",
|
||||||
|
"SHADOWSOCKS=off",
|
||||||
|
"SHADOWSOCKS_LOG=off",
|
||||||
|
"SHADOWSOCKS_LISTENING_ADDRESS=:8388",
|
||||||
|
"SHADOWSOCKS_PASSWORD=",
|
||||||
|
"SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password",
|
||||||
|
"SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305",
|
||||||
|
"HTTP_CONTROL_SERVER_LOG=on",
|
||||||
|
"HTTP_CONTROL_SERVER_ADDRESS=:8000",
|
||||||
|
"UPDATER_PERIOD=0",
|
||||||
|
"UPDATER_MIN_RATIO=0.8",
|
||||||
|
"UPDATER_VPN_SERVICE_PROVIDERS=",
|
||||||
|
"PUBLICIP_FILE=/tmp/gluetun/ip",
|
||||||
|
"PUBLICIP_PERIOD=12h",
|
||||||
|
"PPROF_ENABLED=no",
|
||||||
|
"PPROF_BLOCK_PROFILE_RATE=0",
|
||||||
|
"PPROF_MUTEX_PROFILE_RATE=0",
|
||||||
|
"PPROF_HTTP_SERVER_ADDRESS=:6060",
|
||||||
|
"VERSION_INFORMATION=on",
|
||||||
|
"TZ=",
|
||||||
|
"PUID=",
|
||||||
|
"PGID="
|
||||||
|
],
|
||||||
|
"Cmd": null,
|
||||||
|
"Healthcheck": {
|
||||||
|
"Test": [
|
||||||
|
"CMD-SHELL",
|
||||||
|
"/gluetun-entrypoint healthcheck"
|
||||||
|
],
|
||||||
|
"Interval": 5000000000,
|
||||||
|
"Timeout": 5000000000,
|
||||||
|
"StartPeriod": 10000000000,
|
||||||
|
"Retries": 1
|
||||||
|
},
|
||||||
|
"Image": "qmcgaw/gluetun",
|
||||||
|
"Volumes": null,
|
||||||
|
"WorkingDir": "",
|
||||||
|
"Entrypoint": [
|
||||||
|
"/gluetun-entrypoint"
|
||||||
|
],
|
||||||
|
"OnBuild": null,
|
||||||
|
"Labels": {
|
||||||
|
"com.docker.compose.config-hash": "6dc7dc42a86edb47039de3650a9cb9bdcf4866c113b8f9d797722c9dfd20428b",
|
||||||
|
"com.docker.compose.container-number": "1",
|
||||||
|
"com.docker.compose.depends_on": "",
|
||||||
|
"com.docker.compose.image": "sha256:c22b543d33bfdcb9992cbef23961677133cdf09da71d782468ae2517138bad51",
|
||||||
|
"com.docker.compose.oneoff": "False",
|
||||||
|
"com.docker.compose.project": "wt-contnet",
|
||||||
|
"com.docker.compose.project.config_files": "/tmp/wt-contnet/docker-compose.yaml",
|
||||||
|
"com.docker.compose.project.working_dir": "/tmp/wt-contnet",
|
||||||
|
"com.docker.compose.replace": "9bd1ce000be81819fc915aa60a1674c7573b59a26ac4643ecf427a5732b9785f",
|
||||||
|
"com.docker.compose.service": "producer",
|
||||||
|
"com.docker.compose.version": "2.19.1",
|
||||||
|
"desktop.docker.io/wsl-distro": "Ubuntu",
|
||||||
|
"org.opencontainers.image.authors": "quentin.mcgaw@gmail.com",
|
||||||
|
"org.opencontainers.image.created": "2023-07-22T16:07:05.641Z",
|
||||||
|
"org.opencontainers.image.description": "VPN client in a thin Docker container for multiple VPN providers, written in Go, and using OpenVPN or Wireguard, DNS over TLS, with a few proxy servers built-in.",
|
||||||
|
"org.opencontainers.image.documentation": "https://github.com/qdm12/gluetun",
|
||||||
|
"org.opencontainers.image.licenses": "MIT",
|
||||||
|
"org.opencontainers.image.revision": "eecfb3952f202c0de3867d88e96d80c6b0f48359",
|
||||||
|
"org.opencontainers.image.source": "https://github.com/qdm12/gluetun",
|
||||||
|
"org.opencontainers.image.title": "gluetun",
|
||||||
|
"org.opencontainers.image.url": "https://github.com/qdm12/gluetun",
|
||||||
|
"org.opencontainers.image.version": "latest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"NetworkSettings": {
|
||||||
|
"Bridge": "",
|
||||||
|
"SandboxID": "34a321b64bb1b15f994dfccff0e235f881504f240c2028876ff6683962eaa10e",
|
||||||
|
"HairpinMode": false,
|
||||||
|
"LinkLocalIPv6Address": "",
|
||||||
|
"LinkLocalIPv6PrefixLen": 0,
|
||||||
|
"Ports": {
|
||||||
|
"8000/tcp": null,
|
||||||
|
"8388/tcp": null,
|
||||||
|
"8388/udp": null,
|
||||||
|
"8888/tcp": null
|
||||||
|
},
|
||||||
|
"SandboxKey": "/var/run/docker/netns/34a321b64bb1",
|
||||||
|
"SecondaryIPAddresses": null,
|
||||||
|
"SecondaryIPv6Addresses": null,
|
||||||
|
"EndpointID": "",
|
||||||
|
"Gateway": "",
|
||||||
|
"GlobalIPv6Address": "",
|
||||||
|
"GlobalIPv6PrefixLen": 0,
|
||||||
|
"IPAddress": "",
|
||||||
|
"IPPrefixLen": 0,
|
||||||
|
"IPv6Gateway": "",
|
||||||
|
"MacAddress": "",
|
||||||
|
"Networks": {
|
||||||
|
"wt-contnet_default": {
|
||||||
|
"IPAMConfig": null,
|
||||||
|
"Links": null,
|
||||||
|
"Aliases": [
|
||||||
|
"wt-contnet-producer-1",
|
||||||
|
"producer",
|
||||||
|
"25e75393800b"
|
||||||
|
],
|
||||||
|
"NetworkID": "f0f652a79efc54bcad52aafb4cbcc3b5dce1acaf11b172d8678d25f665faf63d",
|
||||||
|
"EndpointID": "2429c2b5d08db6c986bbd419a52ca4dd352715d80c5aeae04742efb84b0356fc",
|
||||||
|
"Gateway": "172.19.0.1",
|
||||||
|
"IPAddress": "172.19.0.2",
|
||||||
|
"IPPrefixLen": 16,
|
||||||
|
"IPv6Gateway": "",
|
||||||
|
"GlobalIPv6Address": "",
|
||||||
|
"GlobalIPv6PrefixLen": 0,
|
||||||
|
"MacAddress": "02:42:ac:13:00:02",
|
||||||
|
"DriverOpts": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
115
pkg/container/mocks/data/image_net_consumer.json
Normal file
115
pkg/container/mocks/data/image_net_consumer.json
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
{
|
||||||
|
"Id": "sha256:904b8cb13b932e23230836850610fa45dce9eb0650d5618c2b1487c2a4f577b8",
|
||||||
|
"RepoTags": [
|
||||||
|
"nginx:latest"
|
||||||
|
],
|
||||||
|
"RepoDigests": [
|
||||||
|
"nginx@sha256:aa0afebbb3cfa473099a62c4b32e9b3fb73ed23f2a75a65ce1d4b4f55a5c2ef2"
|
||||||
|
],
|
||||||
|
"Parent": "",
|
||||||
|
"Comment": "",
|
||||||
|
"Created": "2023-03-01T18:43:12.914398123Z",
|
||||||
|
"Container": "71a4c9a59d252d7c54812429bfe5df477e54e91ebfff1939ae39ecdf055d445c",
|
||||||
|
"ContainerConfig": {
|
||||||
|
"Hostname": "71a4c9a59d25",
|
||||||
|
"Domainname": "",
|
||||||
|
"User": "",
|
||||||
|
"AttachStdin": false,
|
||||||
|
"AttachStdout": false,
|
||||||
|
"AttachStderr": false,
|
||||||
|
"ExposedPorts": {
|
||||||
|
"80/tcp": {}
|
||||||
|
},
|
||||||
|
"Tty": false,
|
||||||
|
"OpenStdin": false,
|
||||||
|
"StdinOnce": false,
|
||||||
|
"Env": [
|
||||||
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||||
|
"NGINX_VERSION=1.23.3",
|
||||||
|
"NJS_VERSION=0.7.9",
|
||||||
|
"PKG_RELEASE=1~bullseye"
|
||||||
|
],
|
||||||
|
"Cmd": [
|
||||||
|
"/bin/sh",
|
||||||
|
"-c",
|
||||||
|
"#(nop) ",
|
||||||
|
"CMD [\"nginx\" \"-g\" \"daemon off;\"]"
|
||||||
|
],
|
||||||
|
"Image": "sha256:6716b8a33f73b21e193bb63424ea1105eaaa6a8237fefe75570bea18c87a1711",
|
||||||
|
"Volumes": null,
|
||||||
|
"WorkingDir": "",
|
||||||
|
"Entrypoint": [
|
||||||
|
"/docker-entrypoint.sh"
|
||||||
|
],
|
||||||
|
"OnBuild": null,
|
||||||
|
"Labels": {
|
||||||
|
"maintainer": "NGINX Docker Maintainers <docker-maint@nginx.com>"
|
||||||
|
},
|
||||||
|
"StopSignal": "SIGQUIT"
|
||||||
|
},
|
||||||
|
"DockerVersion": "20.10.23",
|
||||||
|
"Author": "",
|
||||||
|
"Config": {
|
||||||
|
"Hostname": "",
|
||||||
|
"Domainname": "",
|
||||||
|
"User": "",
|
||||||
|
"AttachStdin": false,
|
||||||
|
"AttachStdout": false,
|
||||||
|
"AttachStderr": false,
|
||||||
|
"ExposedPorts": {
|
||||||
|
"80/tcp": {}
|
||||||
|
},
|
||||||
|
"Tty": false,
|
||||||
|
"OpenStdin": false,
|
||||||
|
"StdinOnce": false,
|
||||||
|
"Env": [
|
||||||
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||||
|
"NGINX_VERSION=1.23.3",
|
||||||
|
"NJS_VERSION=0.7.9",
|
||||||
|
"PKG_RELEASE=1~bullseye"
|
||||||
|
],
|
||||||
|
"Cmd": [
|
||||||
|
"nginx",
|
||||||
|
"-g",
|
||||||
|
"daemon off;"
|
||||||
|
],
|
||||||
|
"Image": "sha256:6716b8a33f73b21e193bb63424ea1105eaaa6a8237fefe75570bea18c87a1711",
|
||||||
|
"Volumes": null,
|
||||||
|
"WorkingDir": "",
|
||||||
|
"Entrypoint": [
|
||||||
|
"/docker-entrypoint.sh"
|
||||||
|
],
|
||||||
|
"OnBuild": null,
|
||||||
|
"Labels": {
|
||||||
|
"maintainer": "NGINX Docker Maintainers <docker-maint@nginx.com>"
|
||||||
|
},
|
||||||
|
"StopSignal": "SIGQUIT"
|
||||||
|
},
|
||||||
|
"Architecture": "amd64",
|
||||||
|
"Os": "linux",
|
||||||
|
"Size": 141838643,
|
||||||
|
"VirtualSize": 141838643,
|
||||||
|
"GraphDriver": {
|
||||||
|
"Data": {
|
||||||
|
"LowerDir": "/var/lib/docker/overlay2/09785ba17f27c783ef8b44f369f9aac0ca936000b57abf22b3c54d1e6eb8e27b/diff:/var/lib/docker/overlay2/6f8acd64ae44fd4d14bcb90c105eceba46854aa3985b5b6b317bcc5692cfc286/diff:/var/lib/docker/overlay2/73d41c15edb21c5f12cf53e313f48b5da55283aafc77d35b7bc662241879d7e7/diff:/var/lib/docker/overlay2/d97b55f3d966ae031492369a98e9e00d2bd31e520290fe2034e0a2b1ed77c91e/diff:/var/lib/docker/overlay2/053e9ca65c6b64cb9d98a812ff7488c7e77938b4fb8e0c4d2ad7f8ec235f0f20/diff",
|
||||||
|
"MergedDir": "/var/lib/docker/overlay2/105427179e5628eb7e893d53e21f42f9e76278f8b5665387ecdeed54a7231137/merged",
|
||||||
|
"UpperDir": "/var/lib/docker/overlay2/105427179e5628eb7e893d53e21f42f9e76278f8b5665387ecdeed54a7231137/diff",
|
||||||
|
"WorkDir": "/var/lib/docker/overlay2/105427179e5628eb7e893d53e21f42f9e76278f8b5665387ecdeed54a7231137/work"
|
||||||
|
},
|
||||||
|
"Name": "overlay2"
|
||||||
|
},
|
||||||
|
"RootFS": {
|
||||||
|
"Type": "layers",
|
||||||
|
"Layers": [
|
||||||
|
"sha256:650abce4b096b06ac8bec2046d821d66d801af34f1f1d4c5e272ad030c7873db",
|
||||||
|
"sha256:4dc5cd799a08ff49a603870c8378ea93083bfc2a4176f56e5531997e94c195d0",
|
||||||
|
"sha256:e161c82b34d21179db1f546c1cd84153d28a17d865ccaf2dedeb06a903fec12c",
|
||||||
|
"sha256:83ba6d8ffb8c2974174c02d3ba549e7e0656ebb1bc075a6b6ee89b6c609c6a71",
|
||||||
|
"sha256:d8466e142d8710abf5b495ebb536478f7e19d9d03b151b5d5bd09df4cfb49248",
|
||||||
|
"sha256:101af4ba983b04be266217ecee414e88b23e394f62e9801c7c1bdb37cb37bcaa"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Metadata": {
|
||||||
|
"LastTagTime": "0001-01-01T00:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
210
pkg/container/mocks/data/image_net_producer.json
Normal file
210
pkg/container/mocks/data/image_net_producer.json
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
{
|
||||||
|
"Id": "sha256:c22b543d33bfdcb9992cbef23961677133cdf09da71d782468ae2517138bad51",
|
||||||
|
"RepoTags": [
|
||||||
|
"qmcgaw/gluetun:latest"
|
||||||
|
],
|
||||||
|
"RepoDigests": [
|
||||||
|
"qmcgaw/gluetun@sha256:cd532bf4ef88a348a915c6dc62a9867a2eca89aa70559b0b4a1ea15cc0e595d1"
|
||||||
|
],
|
||||||
|
"Parent": "",
|
||||||
|
"Comment": "buildkit.dockerfile.v0",
|
||||||
|
"Created": "2023-07-22T16:10:29.457146856Z",
|
||||||
|
"Container": "",
|
||||||
|
"ContainerConfig": {
|
||||||
|
"Hostname": "",
|
||||||
|
"Domainname": "",
|
||||||
|
"User": "",
|
||||||
|
"AttachStdin": false,
|
||||||
|
"AttachStdout": false,
|
||||||
|
"AttachStderr": false,
|
||||||
|
"Tty": false,
|
||||||
|
"OpenStdin": false,
|
||||||
|
"StdinOnce": false,
|
||||||
|
"Env": null,
|
||||||
|
"Cmd": null,
|
||||||
|
"Image": "",
|
||||||
|
"Volumes": null,
|
||||||
|
"WorkingDir": "",
|
||||||
|
"Entrypoint": null,
|
||||||
|
"OnBuild": null,
|
||||||
|
"Labels": null
|
||||||
|
},
|
||||||
|
"DockerVersion": "",
|
||||||
|
"Author": "",
|
||||||
|
"Config": {
|
||||||
|
"Hostname": "",
|
||||||
|
"Domainname": "",
|
||||||
|
"User": "",
|
||||||
|
"AttachStdin": false,
|
||||||
|
"AttachStdout": false,
|
||||||
|
"AttachStderr": false,
|
||||||
|
"ExposedPorts": {
|
||||||
|
"8000/tcp": {},
|
||||||
|
"8388/tcp": {},
|
||||||
|
"8388/udp": {},
|
||||||
|
"8888/tcp": {}
|
||||||
|
},
|
||||||
|
"Tty": false,
|
||||||
|
"OpenStdin": false,
|
||||||
|
"StdinOnce": false,
|
||||||
|
"Env": [
|
||||||
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||||
|
"VPN_SERVICE_PROVIDER=pia",
|
||||||
|
"VPN_TYPE=openvpn",
|
||||||
|
"VPN_ENDPOINT_IP=",
|
||||||
|
"VPN_ENDPOINT_PORT=",
|
||||||
|
"VPN_INTERFACE=tun0",
|
||||||
|
"OPENVPN_PROTOCOL=udp",
|
||||||
|
"OPENVPN_USER=",
|
||||||
|
"OPENVPN_PASSWORD=",
|
||||||
|
"OPENVPN_USER_SECRETFILE=/run/secrets/openvpn_user",
|
||||||
|
"OPENVPN_PASSWORD_SECRETFILE=/run/secrets/openvpn_password",
|
||||||
|
"OPENVPN_VERSION=2.5",
|
||||||
|
"OPENVPN_VERBOSITY=1",
|
||||||
|
"OPENVPN_FLAGS=",
|
||||||
|
"OPENVPN_CIPHERS=",
|
||||||
|
"OPENVPN_AUTH=",
|
||||||
|
"OPENVPN_PROCESS_USER=root",
|
||||||
|
"OPENVPN_CUSTOM_CONFIG=",
|
||||||
|
"WIREGUARD_PRIVATE_KEY=",
|
||||||
|
"WIREGUARD_PRESHARED_KEY=",
|
||||||
|
"WIREGUARD_PUBLIC_KEY=",
|
||||||
|
"WIREGUARD_ALLOWED_IPS=",
|
||||||
|
"WIREGUARD_ADDRESSES=",
|
||||||
|
"WIREGUARD_MTU=1400",
|
||||||
|
"WIREGUARD_IMPLEMENTATION=auto",
|
||||||
|
"SERVER_REGIONS=",
|
||||||
|
"SERVER_COUNTRIES=",
|
||||||
|
"SERVER_CITIES=",
|
||||||
|
"SERVER_HOSTNAMES=",
|
||||||
|
"ISP=",
|
||||||
|
"OWNED_ONLY=no",
|
||||||
|
"PRIVATE_INTERNET_ACCESS_OPENVPN_ENCRYPTION_PRESET=",
|
||||||
|
"VPN_PORT_FORWARDING=off",
|
||||||
|
"VPN_PORT_FORWARDING_PROVIDER=",
|
||||||
|
"VPN_PORT_FORWARDING_STATUS_FILE=/tmp/gluetun/forwarded_port",
|
||||||
|
"OPENVPN_CERT=",
|
||||||
|
"OPENVPN_KEY=",
|
||||||
|
"OPENVPN_CLIENTCRT_SECRETFILE=/run/secrets/openvpn_clientcrt",
|
||||||
|
"OPENVPN_CLIENTKEY_SECRETFILE=/run/secrets/openvpn_clientkey",
|
||||||
|
"OPENVPN_ENCRYPTED_KEY=",
|
||||||
|
"OPENVPN_ENCRYPTED_KEY_SECRETFILE=/run/secrets/openvpn_encrypted_key",
|
||||||
|
"OPENVPN_KEY_PASSPHRASE=",
|
||||||
|
"OPENVPN_KEY_PASSPHRASE_SECRETFILE=/run/secrets/openvpn_key_passphrase",
|
||||||
|
"SERVER_NUMBER=",
|
||||||
|
"SERVER_NAMES=",
|
||||||
|
"FREE_ONLY=",
|
||||||
|
"MULTIHOP_ONLY=",
|
||||||
|
"PREMIUM_ONLY=",
|
||||||
|
"FIREWALL=on",
|
||||||
|
"FIREWALL_VPN_INPUT_PORTS=",
|
||||||
|
"FIREWALL_INPUT_PORTS=",
|
||||||
|
"FIREWALL_OUTBOUND_SUBNETS=",
|
||||||
|
"FIREWALL_DEBUG=off",
|
||||||
|
"LOG_LEVEL=info",
|
||||||
|
"HEALTH_SERVER_ADDRESS=127.0.0.1:9999",
|
||||||
|
"HEALTH_TARGET_ADDRESS=cloudflare.com:443",
|
||||||
|
"HEALTH_SUCCESS_WAIT_DURATION=5s",
|
||||||
|
"HEALTH_VPN_DURATION_INITIAL=6s",
|
||||||
|
"HEALTH_VPN_DURATION_ADDITION=5s",
|
||||||
|
"DOT=on",
|
||||||
|
"DOT_PROVIDERS=cloudflare",
|
||||||
|
"DOT_PRIVATE_ADDRESS=127.0.0.1/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,::1/128,fc00::/7,fe80::/10,::ffff:7f00:1/104,::ffff:a00:0/104,::ffff:a9fe:0/112,::ffff:ac10:0/108,::ffff:c0a8:0/112",
|
||||||
|
"DOT_VERBOSITY=1",
|
||||||
|
"DOT_VERBOSITY_DETAILS=0",
|
||||||
|
"DOT_VALIDATION_LOGLEVEL=0",
|
||||||
|
"DOT_CACHING=on",
|
||||||
|
"DOT_IPV6=off",
|
||||||
|
"BLOCK_MALICIOUS=on",
|
||||||
|
"BLOCK_SURVEILLANCE=off",
|
||||||
|
"BLOCK_ADS=off",
|
||||||
|
"UNBLOCK=",
|
||||||
|
"DNS_UPDATE_PERIOD=24h",
|
||||||
|
"DNS_ADDRESS=127.0.0.1",
|
||||||
|
"DNS_KEEP_NAMESERVER=off",
|
||||||
|
"HTTPPROXY=",
|
||||||
|
"HTTPPROXY_LOG=off",
|
||||||
|
"HTTPPROXY_LISTENING_ADDRESS=:8888",
|
||||||
|
"HTTPPROXY_STEALTH=off",
|
||||||
|
"HTTPPROXY_USER=",
|
||||||
|
"HTTPPROXY_PASSWORD=",
|
||||||
|
"HTTPPROXY_USER_SECRETFILE=/run/secrets/httpproxy_user",
|
||||||
|
"HTTPPROXY_PASSWORD_SECRETFILE=/run/secrets/httpproxy_password",
|
||||||
|
"SHADOWSOCKS=off",
|
||||||
|
"SHADOWSOCKS_LOG=off",
|
||||||
|
"SHADOWSOCKS_LISTENING_ADDRESS=:8388",
|
||||||
|
"SHADOWSOCKS_PASSWORD=",
|
||||||
|
"SHADOWSOCKS_PASSWORD_SECRETFILE=/run/secrets/shadowsocks_password",
|
||||||
|
"SHADOWSOCKS_CIPHER=chacha20-ietf-poly1305",
|
||||||
|
"HTTP_CONTROL_SERVER_LOG=on",
|
||||||
|
"HTTP_CONTROL_SERVER_ADDRESS=:8000",
|
||||||
|
"UPDATER_PERIOD=0",
|
||||||
|
"UPDATER_MIN_RATIO=0.8",
|
||||||
|
"UPDATER_VPN_SERVICE_PROVIDERS=",
|
||||||
|
"PUBLICIP_FILE=/tmp/gluetun/ip",
|
||||||
|
"PUBLICIP_PERIOD=12h",
|
||||||
|
"PPROF_ENABLED=no",
|
||||||
|
"PPROF_BLOCK_PROFILE_RATE=0",
|
||||||
|
"PPROF_MUTEX_PROFILE_RATE=0",
|
||||||
|
"PPROF_HTTP_SERVER_ADDRESS=:6060",
|
||||||
|
"VERSION_INFORMATION=on",
|
||||||
|
"TZ=",
|
||||||
|
"PUID=",
|
||||||
|
"PGID="
|
||||||
|
],
|
||||||
|
"Cmd": null,
|
||||||
|
"Healthcheck": {
|
||||||
|
"Test": [
|
||||||
|
"CMD-SHELL",
|
||||||
|
"/gluetun-entrypoint healthcheck"
|
||||||
|
],
|
||||||
|
"Interval": 5000000000,
|
||||||
|
"Timeout": 5000000000,
|
||||||
|
"StartPeriod": 10000000000,
|
||||||
|
"Retries": 1
|
||||||
|
},
|
||||||
|
"Image": "",
|
||||||
|
"Volumes": null,
|
||||||
|
"WorkingDir": "",
|
||||||
|
"Entrypoint": [
|
||||||
|
"/gluetun-entrypoint"
|
||||||
|
],
|
||||||
|
"OnBuild": null,
|
||||||
|
"Labels": {
|
||||||
|
"org.opencontainers.image.authors": "quentin.mcgaw@gmail.com",
|
||||||
|
"org.opencontainers.image.created": "2023-07-22T16:07:05.641Z",
|
||||||
|
"org.opencontainers.image.description": "VPN client in a thin Docker container for multiple VPN providers, written in Go, and using OpenVPN or Wireguard, DNS over TLS, with a few proxy servers built-in.",
|
||||||
|
"org.opencontainers.image.documentation": "https://github.com/qdm12/gluetun",
|
||||||
|
"org.opencontainers.image.licenses": "MIT",
|
||||||
|
"org.opencontainers.image.revision": "eecfb3952f202c0de3867d88e96d80c6b0f48359",
|
||||||
|
"org.opencontainers.image.source": "https://github.com/qdm12/gluetun",
|
||||||
|
"org.opencontainers.image.title": "gluetun",
|
||||||
|
"org.opencontainers.image.url": "https://github.com/qdm12/gluetun",
|
||||||
|
"org.opencontainers.image.version": "latest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Architecture": "amd64",
|
||||||
|
"Os": "linux",
|
||||||
|
"Size": 42602255,
|
||||||
|
"VirtualSize": 42602255,
|
||||||
|
"GraphDriver": {
|
||||||
|
"Data": {
|
||||||
|
"LowerDir": "/var/lib/docker/overlay2/a20c9490a23ee8af51898892d9bf32258d44e0e07f3799475be8e8f273a50f73/diff:/var/lib/docker/overlay2/d4c97f367c37c6ada9de57f438a3e19cc714be2a54a6f582a03de9e42d88b344/diff",
|
||||||
|
"MergedDir": "/var/lib/docker/overlay2/0d222a3aa067159831c4111a408e40325be1085b935c98d39c2e9a01ff50b224/merged",
|
||||||
|
"UpperDir": "/var/lib/docker/overlay2/0d222a3aa067159831c4111a408e40325be1085b935c98d39c2e9a01ff50b224/diff",
|
||||||
|
"WorkDir": "/var/lib/docker/overlay2/0d222a3aa067159831c4111a408e40325be1085b935c98d39c2e9a01ff50b224/work"
|
||||||
|
},
|
||||||
|
"Name": "overlay2"
|
||||||
|
},
|
||||||
|
"RootFS": {
|
||||||
|
"Type": "layers",
|
||||||
|
"Layers": [
|
||||||
|
"sha256:78a822fe2a2d2c84f3de4a403188c45f623017d6a4521d23047c9fbb0801794c",
|
||||||
|
"sha256:122dbeefc08382d88b3fe57ad81c1e2428af5b81c172d112723a33e2a20fe880",
|
||||||
|
"sha256:3d215e55b88a99dcd7cf4349618326ab129771e12fdf6c6ef5cbb71a265dbb6c"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Metadata": {
|
||||||
|
"LastTagTime": "0001-01-01T00:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
|
@ -110,6 +110,7 @@ func createNotifier(urls []string, level log.Level, tplString string, legacy boo
|
||||||
legacyTemplate: legacy,
|
legacyTemplate: legacy,
|
||||||
data: data,
|
data: data,
|
||||||
params: params,
|
params: params,
|
||||||
|
delay: delay,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,7 @@ var _ = Describe("the auth module", func() {
|
||||||
Expect(getScopeFromImageAuthURL("ghcr.io/containrrr/watchtower")).To(Equal("containrrr/watchtower"))
|
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",`
|
input := `bearer realm="https://ghcr.io/token",service="ghcr.io",scope="repository:user/image:pull",`
|
||||||
imageRef, err := ref.ParseNormalizedNamed("containrrr/watchtower")
|
imageRef, err := ref.ParseNormalizedNamed("containrrr/watchtower")
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
@ -120,7 +120,7 @@ var _ = Describe("the auth module", func() {
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(res).NotTo(BeNil())
|
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`
|
input := `bearer realm="https://ghcr.io/token",service="ghcr.io",scope="repository:user/image:pull",valuelesskey`
|
||||||
imageRef, err := ref.ParseNormalizedNamed("containrrr/watchtower")
|
imageRef, err := ref.ParseNormalizedNamed("containrrr/watchtower")
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
|
@ -52,7 +52,7 @@ type Container interface {
|
||||||
SafeImageID() ImageID
|
SafeImageID() ImageID
|
||||||
ImageName() string
|
ImageName() string
|
||||||
Enabled() (bool, bool)
|
Enabled() (bool, bool)
|
||||||
IsMonitorOnly() bool
|
IsMonitorOnly(UpdateParams) bool
|
||||||
Scope() (string, bool)
|
Scope() (string, bool)
|
||||||
Links() []string
|
Links() []string
|
||||||
ToRestart() bool
|
ToRestart() bool
|
||||||
|
@ -67,7 +67,7 @@ type Container interface {
|
||||||
VerifyConfiguration() error
|
VerifyConfiguration() error
|
||||||
SetStale(bool)
|
SetStale(bool)
|
||||||
IsStale() bool
|
IsStale() bool
|
||||||
IsNoPull() bool
|
IsNoPull(UpdateParams) bool
|
||||||
SetLinkedToRestarting(bool)
|
SetLinkedToRestarting(bool)
|
||||||
IsLinkedToRestarting() bool
|
IsLinkedToRestarting() bool
|
||||||
PreUpdateTimeout() int
|
PreUpdateTimeout() int
|
||||||
|
|
|
@ -6,11 +6,13 @@ import (
|
||||||
|
|
||||||
// UpdateParams contains all different options available to alter the behavior of the Update func
|
// UpdateParams contains all different options available to alter the behavior of the Update func
|
||||||
type UpdateParams struct {
|
type UpdateParams struct {
|
||||||
Filter Filter
|
Filter Filter
|
||||||
Cleanup bool
|
Cleanup bool
|
||||||
NoRestart bool
|
NoRestart bool
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
MonitorOnly bool
|
MonitorOnly bool
|
||||||
LifecycleHooks bool
|
NoPull bool
|
||||||
RollingRestart bool
|
LifecycleHooks bool
|
||||||
|
RollingRestart bool
|
||||||
|
LabelPrecedence bool
|
||||||
}
|
}
|
||||||
|
|
42
scripts/contnet-tests.sh
Executable file
42
scripts/contnet-tests.sh
Executable file
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
function exit_env_err() {
|
||||||
|
>&2 echo "Required environment variable not set: $1"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -z "$VPN_SERVICE_PROVIDER" ]; then exit_env_err "VPN_SERVICE_PROVIDER"; fi
|
||||||
|
if [ -z "$OPENVPN_USER" ]; then exit_env_err "OPENVPN_USER"; fi
|
||||||
|
if [ -z "$OPENVPN_PASSWORD" ]; then exit_env_err "OPENVPN_PASSWORD"; fi
|
||||||
|
# if [ -z "$SERVER_COUNTRIES" ]; then exit_env_err "SERVER_COUNTRIES"; fi
|
||||||
|
|
||||||
|
|
||||||
|
export SERVER_COUNTRIES=${SERVER_COUNTRIES:"Sweden"}
|
||||||
|
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
||||||
|
COMPOSE_FILE="$REPO_ROOT/dockerfiles/container-networking/docker-compose.yml"
|
||||||
|
DEFAULT_WATCHTOWER="$REPO_ROOT/watchtower"
|
||||||
|
WATCHTOWER="$*"
|
||||||
|
WATCHTOWER=${WATCHTOWER:-$DEFAULT_WATCHTOWER}
|
||||||
|
echo "repo root path is $REPO_ROOT"
|
||||||
|
echo "watchtower path is $WATCHTOWER"
|
||||||
|
echo "compose file path is $COMPOSE_FILE"
|
||||||
|
|
||||||
|
echo; echo "=== Forcing network container producer update..."
|
||||||
|
|
||||||
|
echo "Pull previous version of gluetun..."
|
||||||
|
docker pull qmcgaw/gluetun:v3.34.3
|
||||||
|
echo "Fake new version of gluetun by retagging v3.34.4 as v3.35.0..."
|
||||||
|
docker tag qmcgaw/gluetun:v3.34.3 qmcgaw/gluetun:v3.35.0
|
||||||
|
|
||||||
|
echo; echo "=== Creating containers..."
|
||||||
|
|
||||||
|
docker compose -p "wt-contnet" -f "$COMPOSE_FILE" up -d
|
||||||
|
|
||||||
|
echo; echo "=== Running watchtower"
|
||||||
|
$WATCHTOWER --run-once
|
||||||
|
|
||||||
|
echo; echo "=== Removing containers..."
|
||||||
|
|
||||||
|
docker compose -p "wt-contnet" -f "$COMPOSE_FILE" down
|
Loading…
Add table
Add a link
Reference in a new issue