Compare commits

..

29 Commits

Author SHA1 Message Date
mag37
61f90893ef async version bump 2025-02-24 22:12:11 +01:00
mag37
229cde0efb Updated README.md
Added changelog.
Added info about Docker Hub pull limit.
2025-02-24 22:03:20 +01:00
mag37
0c6674ac8e Merge pull request #128 from Thaurin/parallel_check
Add async checking for updates for improved performance
2025-02-24 21:59:30 +01:00
mag37
3e079e2ec5 Update dockcheck.sh
Added MaxAsync variable.
Added POSIX xargs check.
Rewrote Excludes.
2025-02-24 21:49:19 +01:00
Thaurin
b2d67c9f52 Removed experimental -z flag and old version check code 2025-02-21 17:57:09 +01:00
Thaurin
3aeee837f9 Print entire line on error 2025-02-21 17:54:19 +01:00
Thaurin
408a8b14dd Fix -d parameter not working anymore 2025-02-20 18:48:55 +01:00
Thaurin
a2868ea505 Add error message; increase number of subprocesses 2025-02-20 18:48:55 +01:00
Thaurin
62a3d10b4f Add async checking for updates for improved performance 2025-02-20 18:48:55 +01:00
mag37
210c076968 Merge pull request #130 from mag37/osx_support
Some osx / bsd compatibility changes.
Rewritten the dependency (jq + regctl) install method.
Many thanks to @pshannon-git for testing!
2025-02-19 20:36:42 +01:00
mag37
65e875e860 fixed typo 2025-02-19 20:26:54 +01:00
mag37
3655f5ae8a version bump 2025-02-19 13:36:30 +01:00
mag37
78a7e1137f OSX appname return 2025-02-18 10:07:21 +01:00
mag37
76e6a5c38b Update dockcheck.sh
typo
2025-02-18 08:15:52 +01:00
mag37
41029f628d Update dockcheck.sh
indentation error.
2025-02-18 07:59:11 +01:00
mag37
b918844336 reformatting 2025-02-17 22:38:14 +01:00
mag37
27896c18ba squashed multiple dependency downloaders to one function 2025-02-17 22:23:10 +01:00
mag37
e68adb34d0 added info about macos osx 2025-02-15 13:23:42 +01:00
mag37
6bc896b193 typo 2025-02-14 13:01:23 +01:00
mag37
6cba140522 too tired.. forgot fi's 2025-02-13 22:21:53 +01:00
mag37
73050abf10 rewrote regctl download 2025-02-13 22:18:08 +01:00
mag37
d4d89c305c osx/bsd compatibility changes 2025-02-13 21:39:29 +01:00
mag37
bc6a3529c7 Update README.md 2025-02-07 19:34:24 +01:00
mag37
01b9b33751 Merge pull request #124 from mag37/prometheus_addon
Prometheus addon
2025-02-07 19:28:43 +01:00
mag37
9ed2a0bad0 finalized prometheus addon 2025-02-07 19:27:14 +01:00
mag37
43307350ca formatting of header 2025-01-31 23:58:05 +01:00
mag37
b3600f26ac restructure, moved to addon-subdir and split code to sourced snippet 2025-01-31 23:52:10 +01:00
mag37
79def47754 Merge pull request #121 from tdralle/main
Added an export for node_exporter's text file collector to provide metrics to Prometheus
2025-01-31 23:37:24 +01:00
Thorsten Dralle
58d53d3aaf Added a node collector to export statistics to prometheus 2025-01-27 20:57:19 +01:00
6 changed files with 617 additions and 75 deletions

View File

@@ -10,24 +10,22 @@
<a href="https://github.com/sponsors/mag37"><img src="https://img.shields.io/badge/-Sponsor-grey?style=flat-square&logo=github" alt="Github Sponsor"></a> <a href="https://github.com/sponsors/mag37"><img src="https://img.shields.io/badge/-Sponsor-grey?style=flat-square&logo=github" alt="Github Sponsor"></a>
</p> </p>
<h3 align="center">CLI tool to automate docker image updates. <br>No <b>pre-pull</b>, selective, optional notifications and prune when done.</h3> <h2 align="center">CLI tool to automate docker image updates or notifying when updates are available.</h2>
<h2 align="center">Now with simple notification integrations!</h2> <h3 align="center">Features:</h3>
<h4 align="center">With features like excluding specific containers, custom container labels, auto-prune when done and more.</h4> <h3 align="center">selective updates, exclude containers, custom labels, notification plugins, prune when done and more.</h3>
<h4 align="center">Also see the fresh Podman fork <a href="https://github.com/sudo-kraken/podcheck">sudo-kraken/podcheck</a>!</h4>
<h4 align="center">For Podman - see the fork <a href="https://github.com/sudo-kraken/podcheck">sudo-kraken/podcheck</a>!</h4>
<h4 align="center">:whale: Docker Hub pull limit :chart_with_downwards_trend: not an issue for checks but for actual pulls - <a href="#whale-docker-hub-pull-limit-chart_with_downwards_trend-not-an-issue-for-checks-but-for-actual-pulls">read more</a></h4>
___ ___
## :bell: Changelog ## :bell: Changelog
- **v0.5.3**: Local image check changed (use imageId instead of name) and Gotify-template fixed (whale icon removed). - **v0.5.6.0**: Heavily improved performance due to async checking for updates.
- **v0.5.5.0**: osx and bsd compatibility changes + rewrite of dependency installer
- **v0.5.4.0**: Added support for a Prometheus+node_exporter metric collection through a file collector.
- **v0.5.3.0**: Local image check changed (use imageId instead of name) and Gotify-template fixed (whale icon removed).
- **v0.5.2.1**: Rewrite of dependency downloads, jq can be installed with package manager or static binary. - **v0.5.2.1**: Rewrite of dependency downloads, jq can be installed with package manager or static binary.
- **v0.5.1**: DEPENDENCY WARNING: now requires **jq**. + Upstreaming changes from [sudo-kraken/podcheck](https://github.com/sudo-kraken/podcheck)
- **v0.5.0**: Rewritten notify logic - all templates are adjusted and should be migrated!
- Copy the custom settings from your current template to the new version of the same template.
- Look into, copy and customize the `urls.list` file if that's of interest.
- Other changes:
- Added Discord notify template.
- Verbosity changed of `regctl`.
- **v0.4.9**: Added a function to enrich the notify-message with release note URLs. See [Release notes addon](https://github.com/mag37/dockcheck#date-release-notes-addon-to-notifications)
___ ___
@@ -41,6 +39,7 @@ Example: dockcheck.sh -y -d 10 -e nextcloud,heimdall
Options:" Options:"
-a|y Automatic updates, without interaction. -a|y Automatic updates, without interaction.
-c D Exports metrics as prom file for the prometheus node_exporter. Provide the collector textfile directory.
-d N Only update to new images that are N+ days old. Lists too recent with +prefix and age. 2xSlower. -d N Only update to new images that are N+ days old. Lists too recent with +prefix and age. 2xSlower.
-e X Exclude containers, separated by comma. -e X Exclude containers, separated by comma.
-f Force stack restart after update. Caution: restarts once for every updated container within stack. -f Force stack restart after update. Caution: restarts once for every updated container within stack.
@@ -80,14 +79,16 @@ ___
## :nut_and_bolt: Dependencies ## :nut_and_bolt: Dependencies
- Running docker (duh) and compose, either standalone or plugin. (see [Podman fork](https://github.com/sudo-kraken/podcheck) - Running docker (duh) and compose, either standalone or plugin. (see [Podman fork](https://github.com/sudo-kraken/podcheck)
- Bash shell or compatible shell of at least v4.3 - Bash shell or compatible shell of at least v4.3
- [jq](https://github.com/jqlang/jq) - POSIX `xargs`, usually default but can be installed with the `findutils` package - to enable async.
- [jq](https://github.com/jqlang/jq)
- User will be prompted to install with package manager or download static binary. - User will be prompted to install with package manager or download static binary.
- [regclient/regctl](https://github.com/regclient/regclient) (Licensed under [Apache-2.0 License](http://www.apache.org/licenses/LICENSE-2.0)) - [regclient/regctl](https://github.com/regclient/regclient) (Licensed under [Apache-2.0 License](http://www.apache.org/licenses/LICENSE-2.0))
- User will be prompted to download `regctl` if not in `PATH` or `PWD`. - User will be prompted to download `regctl` if not in `PATH` or `PWD`.
- regctl requires `amd64/arm64` - see [workaround](#roller_coaster-workaround-for-non-amd64--arm64) if other architecture is used. - regctl requires `amd64/arm64` - see [workaround](#roller_coaster-workaround-for-non-amd64--arm64) if other architecture is used.
## :tent: Install Instructions ## :tent: Install Instructions
Download the script to a directory in **PATH**, I'd suggest using `~/.local/bin` as that's usually in **PATH**. Download the script to a directory in **PATH**, I'd suggest using `~/.local/bin` as that's usually in **PATH**.
For OSX/macOS preferably use `/usr/local/bin`.
```sh ```sh
# basic example with curl: # basic example with curl:
curl -L https://raw.githubusercontent.com/mag37/dockcheck/main/dockcheck.sh -o ~/.local/bin/dockcheck.sh curl -L https://raw.githubusercontent.com/mag37/dockcheck/main/dockcheck.sh -o ~/.local/bin/dockcheck.sh
@@ -95,6 +96,9 @@ chmod +x ~/.local/bin/dockcheck.sh
# or oneliner with wget: # or oneliner with wget:
wget -O ~/.local/bin/dockcheck.sh "https://raw.githubusercontent.com/mag37/dockcheck/main/dockcheck.sh" && chmod +x ~/.local/bin/dockcheck.sh wget -O ~/.local/bin/dockcheck.sh "https://raw.githubusercontent.com/mag37/dockcheck/main/dockcheck.sh" && chmod +x ~/.local/bin/dockcheck.sh
# OSX or macOS version with curl:
curl -L https://raw.githubusercontent.com/mag37/dockcheck/main/dockcheck.sh -o /usr/local/bin/dockcheck.sh && chmod +x /usr/local/bin/dockcheck.sh
``` ```
Then call the script anywhere with just `dockcheck.sh`. Then call the script anywhere with just `dockcheck.sh`.
Add preferred `notify.sh`-template to the same directory - this will not be touched by the scripts self-update function. Add preferred `notify.sh`-template to the same directory - this will not be touched by the scripts self-update function.
@@ -123,8 +127,8 @@ Further additions are welcome - suggestions or PR!
<sub><sup>Initiated and first contributed by [yoyoma2](https://github.com/yoyoma2).</sup></sub> <sub><sup>Initiated and first contributed by [yoyoma2](https://github.com/yoyoma2).</sup></sub>
### :date: Release notes addon to Notifications ### :date: Release notes addon to Notifications
There's a function to use a lookup-file to add release note URL's to the notification message. There's a function to use a lookup-file to add release note URL's to the notification message.
Copy the notify_templates/`urls.list` file to the script directory, it will be used automatically if it's there. Modify it as necessary, the names of interest in the left column needs to match your container names. Copy the notify_templates/`urls.list` file to the script directory, it will be used automatically if it's there. Modify it as necessary, the names of interest in the left column needs to match your container names.
The output of the notification will look something like this: The output of the notification will look something like this:
``` ```
Containers on hostname with updates available: Containers on hostname with updates available:
@@ -135,6 +139,17 @@ nginx -> https://github.com/docker-library/official-images/blob/master/library
``` ```
The `urls.list` file is just an example and I'd gladly see that people contribute back when they add their preferred URLs to their lists. The `urls.list` file is just an example and I'd gladly see that people contribute back when they add their preferred URLs to their lists.
## :chart_with_upwards_trend: Prometheus and node_exporter
Dockcheck can be used together with [Prometheus](https://github.com/prometheus/prometheus) and [node_exporter](https://github.com/prometheus/node_exporter) to export metrics via the file collector, scheduled with cron or likely.
This is done with the `-c` option, like this:
```
dockcheck.sh -c /path/to/exporter/directory
```
See the [README.md](./addons/prometheus/README.md) for more detailed information on how to set it up!
<sub><sup>Contributed by [tdralle](https://github.com/tdralle).</sup></sub>
## :bookmark: Labels ## :bookmark: Labels
Optionally add labels to compose-files. Currently these are the usable labels: Optionally add labels to compose-files. Currently these are the usable labels:
``` ```
@@ -165,7 +180,15 @@ chmod 755 regctl
``` ```
Test it with `./regctl --help` and then either add the file to the same path as *dockcheck.sh* or in your path (eg. `~/.local/bin/regctl`). Test it with `./regctl --help` and then either add the file to the same path as *dockcheck.sh* or in your path (eg. `~/.local/bin/regctl`).
## :guardsman: Function to auth with docker hub before running ## :whale: Docker Hub pull limit :chart_with_downwards_trend: not an issue for checks but for actual pulls
Due to recent changes in [Docker Hub usage and limits](https://docs.docker.com/docker-hub/usage/)
>Unauthenticated users: 10 pulls/hour
>Authenticated users with a free account: 100 pulls/hour
This is not an issue for registry checks. But if you have a large stack and pull more than 10 updates at once consider updating more often or to create a free account.
You could use/modify the login-wrapper function in the example below to automate the login prior to running `dockcheck.sh`.
### :guardsman: Function to auth with docker hub before running
**Example** - Change names, paths, and remove cat+password flag if you rather get prompted: **Example** - Change names, paths, and remove cat+password flag if you rather get prompted:
```sh ```sh
function dchk { function dchk {
@@ -186,7 +209,7 @@ function dchk {
Containers need to be manually stopped, removed and created again to run on the new image. Containers need to be manually stopped, removed and created again to run on the new image.
## :wrench: Debugging ## :wrench: Debugging
If you hit issues, you could check the output of the `extras/errorCheck.sh` script for clues. If you hit issues, you could check the output of the `extras/errorCheck.sh` script for clues.
Another option is to run the main script with debugging in a subshell `bash -x dockcheck.sh` - if there's a particular container/image that's causing issues you can filter for just that through `bash -x dockcheck.sh nginx`. Another option is to run the main script with debugging in a subshell `bash -x dockcheck.sh` - if there's a particular container/image that's causing issues you can filter for just that through `bash -x dockcheck.sh nginx`.
## :scroll: License ## :scroll: License
@@ -199,4 +222,3 @@ dockcheck is created and released under the [GNU GPL v3.0](https://www.gnu.org/l
___ ___
### :floppy_disk: The [story](https://mag37.org/posts/project_dockcheck/) behind it. 1 year in retrospect. ### :floppy_disk: The [story](https://mag37.org/posts/project_dockcheck/) behind it. 1 year in retrospect.

View File

@@ -0,0 +1,61 @@
## [Prometheus](https://github.com/prometheus/prometheus) and [node_exporter](https://github.com/prometheus/node_exporter)
Dockcheck is capable to export metrics to prometheus via the text file collector provided by the node_exporter.
In order to do so the -c flag has to be specified followed by the file path that is configured in the text file collector of the node_exporter.
A simple cron job can be configured to export these metrics on a regular interval as shown in the sample below:
```
0 1 * * * /root/dockcheck.sh -n -c /var/lib/node_exporter/textfile_collector
```
The following metrics are exported to prometheus
```
# HELP dockcheck_images_analyzed Docker images that have been analyzed
# TYPE dockcheck_images_analyzed gauge
dockcheck_images_analyzed 22
# HELP dockcheck_images_outdated Docker images that are outdated
# TYPE dockcheck_images_outdated gauge
dockcheck_images_outdated 7
# HELP dockcheck_images_latest Docker images that are outdated
# TYPE dockcheck_images_latest gauge
dockcheck_images_latest 14
# HELP dockcheck_images_error Docker images with analysis errors
# TYPE dockcheck_images_error gauge
dockcheck_images_error 1
# HELP dockcheck_images_analyze_timestamp_seconds Last dockercheck run time
# TYPE dockcheck_images_analyze_timestamp_seconds gauge
dockcheck_images_analyze_timestamp_seconds 1737924029
```
Once those metrics are exported they can be used to define alarms as shown below
```
- alert: dockcheck_images_outdated
expr: sum by(instance) (dockcheck_images_outdated) > 0
for: 15s
labels:
severity: warning
annotations:
summary: "{{ $labels.instance }} has {{ $value }} outdated docker images."
description: "{{ $labels.instance }} has {{ $value }} outdated docker images."
- alert: dockcheck_images_error
expr: sum by(instance) (dockcheck_images_error) > 0
for: 15s
labels:
severity: warning
annotations:
summary: "{{ $labels.instance }} has {{ $value }} docker images having an error."
description: "{{ $labels.instance }} has {{ $value }} docker images having an error."
- alert: dockercheck_image_last_analyze
expr: (time() - dockcheck_images_analyze_timestamp_seconds) > (3600 * 24 * 3)
for: 15s
labels:
severity: warning
annotations:
summary: "{{ $labels.instance }} has not updated the dockcheck statistics for more than 3 days."
description: "{{ $labels.instance }} has not updated the dockcheck statistics for more than 3 days."
```
There is a reference Grafana dashboard in [grafana/grafana_dashboard.json](./grafana/grafana_dashboard.json).
![](./grafana/grafana_dashboard.png)

View File

@@ -0,0 +1,382 @@
{
"__inputs": [
{
"name": "DS_PROMETHEUS",
"label": "prometheus",
"description": "",
"type": "datasource",
"pluginId": "prometheus",
"pluginName": "Prometheus"
}
],
"__elements": {},
"__requires": [
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "11.4.0"
},
{
"type": "datasource",
"id": "prometheus",
"name": "Prometheus",
"version": "1.0.0"
},
{
"type": "panel",
"id": "table",
"name": "Table",
"version": ""
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [],
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"custom": {
"align": "auto",
"cellOptions": {
"type": "auto"
},
"inspect": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "last_analyze_timestamp"
},
"properties": [
{
"id": "unit",
"value": "dateTimeAsIso"
}
]
},
{
"matcher": {
"id": "byName",
"options": "last_analyze_since"
},
"properties": [
{
"id": "unit",
"value": "s"
},
{
"id": "custom.cellOptions",
"value": {
"mode": "gradient",
"type": "color-background"
}
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 259200
}
]
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "images_outdated"
},
"properties": [
{
"id": "custom.cellOptions",
"value": {
"mode": "gradient",
"type": "color-background"
}
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 1
}
]
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "images_error"
},
"properties": [
{
"id": "custom.cellOptions",
"value": {
"mode": "gradient",
"type": "color-background"
}
},
{
"id": "thresholds",
"value": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 1
}
]
}
}
]
}
]
},
"gridPos": {
"h": 14,
"w": 24,
"x": 0,
"y": 0
},
"id": 2,
"options": {
"cellHeight": "sm",
"footer": {
"countRows": false,
"fields": "",
"reducer": [
"sum"
],
"show": false
},
"frameIndex": 1,
"showHeader": true,
"sortBy": []
},
"pluginVersion": "11.4.0",
"targets": [
{
"disableTextWrap": false,
"editorMode": "code",
"exemplar": false,
"expr": "sum by(instance) (dockcheck_images_analyzed)",
"format": "table",
"fullMetaSearch": false,
"hide": false,
"includeNullMetadata": true,
"instant": true,
"interval": "",
"legendFormat": "{{instance}}",
"range": false,
"refId": "dockcheck_images_analyzed",
"useBackend": false,
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
}
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"disableTextWrap": false,
"editorMode": "code",
"exemplar": false,
"expr": "sum by(instance) (dockcheck_images_outdated)",
"format": "table",
"fullMetaSearch": false,
"hide": false,
"includeNullMetadata": true,
"instant": true,
"legendFormat": "{{instance}}",
"range": false,
"refId": "dockcheck_images_outdated",
"useBackend": false
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"disableTextWrap": false,
"editorMode": "code",
"exemplar": false,
"expr": "sum by(instance) (dockcheck_images_latest)",
"format": "table",
"fullMetaSearch": false,
"hide": false,
"includeNullMetadata": true,
"instant": true,
"legendFormat": "{{instance}}",
"range": false,
"refId": "dockcheck_images_latest",
"useBackend": false
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"exemplar": false,
"expr": "sum by(instance) (dockcheck_images_error)",
"format": "table",
"hide": false,
"instant": true,
"legendFormat": "{{instance}}",
"range": false,
"refId": "dockcheck_images_error"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"exemplar": false,
"expr": "dockcheck_images_analyze_timestamp_seconds * 1000",
"format": "table",
"hide": false,
"instant": true,
"legendFormat": "{{instance}}",
"range": false,
"refId": "dockcheck_images_analyze_timestamp_seconds"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"exemplar": false,
"expr": "time() - dockcheck_images_analyze_timestamp_seconds",
"format": "table",
"hide": false,
"instant": true,
"legendFormat": "{{instance}}",
"range": false,
"refId": "dockcheck_images_last_analyze"
}
],
"title": "Dockcheck Status",
"transformations": [
{
"id": "merge",
"options": {}
},
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true,
"__name__": true,
"job": true
},
"includeByName": {},
"indexByName": {
"Time": 0,
"Value #dockcheck_images_analyze_timestamp_seconds": 2,
"Value #dockcheck_images_analyzed": 4,
"Value #dockcheck_images_error": 7,
"Value #dockcheck_images_last_analyze": 3,
"Value #dockcheck_images_latest": 5,
"Value #dockcheck_images_outdated": 6,
"instance": 1,
"job": 8
},
"renameByName": {
"Value #A": "analyze_timestamp",
"Value #dockcheck_images_analyze_timestamp_seconds": "last_analyze_timestamp",
"Value #dockcheck_images_analyzed": "images_analyzed",
"Value #dockcheck_images_error": "images_error",
"Value #dockcheck_images_last_analyze": "last_analyze_since",
"Value #dockcheck_images_latest": "images_latest",
"Value #dockcheck_images_outdated": "images_outdated"
}
}
}
],
"type": "table"
}
],
"schemaVersion": 40,
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Dockcheck Status",
"uid": "feb4pv3kv1hxca",
"version": 17,
"weekStart": ""
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -0,0 +1,28 @@
prometheus_exporter() {
checkedImages=$(($1 + $2 + $3))
checkTimestamp=$(date +%s)
promFileContent=()
promFileContent+=("# HELP dockcheck_images_analyzed Docker images that have been analyzed")
promFileContent+=("# TYPE dockcheck_images_analyzed gauge")
promFileContent+=("dockcheck_images_analyzed $checkedImages")
promFileContent+=("# HELP dockcheck_images_outdated Docker images that are outdated")
promFileContent+=("# TYPE dockcheck_images_outdated gauge")
promFileContent+=("dockcheck_images_outdated ${#GotUpdates[@]}")
promFileContent+=("# HELP dockcheck_images_latest Docker images that are outdated")
promFileContent+=("# TYPE dockcheck_images_latest gauge")
promFileContent+=("dockcheck_images_latest ${#NoUpdates[@]}")
promFileContent+=("# HELP dockcheck_images_error Docker images with analysis errors")
promFileContent+=("# TYPE dockcheck_images_error gauge")
promFileContent+=("dockcheck_images_error ${#GotErrors[@]}")
promFileContent+=("# HELP dockcheck_images_analyze_timestamp_seconds Last dockercheck run time")
promFileContent+=("# TYPE dockcheck_images_analyze_timestamp_seconds gauge")
promFileContent+=("dockcheck_images_analyze_timestamp_seconds $checkTimestamp")
printf "%s\n" "${promFileContent[@]}" > "$CollectorTextFileDirectory/dockcheck_info.prom\$\$"
mv -f "$CollectorTextFileDirectory/dockcheck_info.prom\$\$" "$CollectorTextFileDirectory/dockcheck_info.prom"
}

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
VERSION="v0.5.3.0" VERSION="v0.5.6.0"
### ChangeNotes: Bugfixes - local image check changed, Gotify-template fixed ### ChangeNotes: Heavily improved performance due to asynchronous update checks.
Github="https://github.com/mag37/dockcheck" Github="https://github.com/mag37/dockcheck"
RawUrl="https://raw.githubusercontent.com/mag37/dockcheck/main/dockcheck.sh" RawUrl="https://raw.githubusercontent.com/mag37/dockcheck/main/dockcheck.sh"
@@ -20,6 +20,7 @@ Help() {
echo echo
echo "Options:" echo "Options:"
echo "-a|y Automatic updates, without interaction." echo "-a|y Automatic updates, without interaction."
echo "-c Exports metrics as prom file for the prometheus node_exporter. Provide the collector textfile directory."
echo "-d N Only update to new images that are N+ days old. Lists too recent with +prefix and age. 2xSlower." echo "-d N Only update to new images that are N+ days old. Lists too recent with +prefix and age. 2xSlower."
echo "-e X Exclude containers, separated by comma." echo "-e X Exclude containers, separated by comma."
echo "-f Force stack restart after update. Caution: restarts once for every updated container within stack." echo "-f Force stack restart after update. Caution: restarts once for every updated container within stack."
@@ -45,11 +46,14 @@ c_blue="\033[0;34m"
c_teal="\033[0;36m" c_teal="\033[0;36m"
c_reset="\033[0m" c_reset="\033[0m"
MaxAsync=32
Timeout=10 Timeout=10
Stopped="" Stopped=""
while getopts "aynpfrhlisvme:d:t:" options; do while getopts "aynpfrhlisvmc:e:d:t:" options; do
case "${options}" in case "${options}" in
a|y) AutoUp="yes" ;; a|y) AutoUp="yes" ;;
c) CollectorTextFileDirectory="${OPTARG}"
if ! [[ -d $CollectorTextFileDirectory ]] ; then { printf "The directory (%s) does not exist.\n" "${CollectorTextFileDirectory}" ; exit 2; } fi ;;
n) AutoUp="no" ;; n) AutoUp="no" ;;
r) DRunUp="yes" ;; r) DRunUp="yes" ;;
p) AutoPrune="yes" ;; p) AutoPrune="yes" ;;
@@ -128,7 +132,8 @@ choosecontainers() {
datecheck() { datecheck() {
ImageDate=$($regbin -v error image inspect "$RepoUrl" --format='{{.Created}}' | cut -d" " -f1 ) ImageDate=$($regbin -v error image inspect "$RepoUrl" --format='{{.Created}}' | cut -d" " -f1 )
ImageAge=$(( ( $(date +%s) - $(date -d "$ImageDate" +%s) )/86400 )) ImageEpoch=$(date -d "$ImageDate" +%s 2>/dev/null) || ImageEpoch=$(date -f "%Y-%m-%d" -j "$ImageDate" +%s)
ImageAge=$(( ( $(date +%s) - $ImageEpoch )/86400 ))
if [ "$ImageAge" -gt "$DaysOld" ] ; then if [ "$ImageAge" -gt "$DaysOld" ] ; then
return 0 return 0
else else
@@ -149,7 +154,7 @@ progress_bar() {
} }
# Function to add user-provided urls to releasenotes # Function to add user-provided urls to releasenotes
releasenotes() { releasenotes() {
for update in ${GotUpdates[@]}; do for update in ${GotUpdates[@]}; do
found=false found=false
while read -r container url; do while read -r container url; do
@@ -177,14 +182,14 @@ IFS=',' read -r -a Excludes <<< "$Exclude" ; unset IFS
binary_downloader() { binary_downloader() {
BinaryName="$1" BinaryName="$1"
BinaryUrl="$2" BinaryUrl="$2"
case "$(uname --machine)" in case "$(uname -m)" in
x86_64|amd64) architecture="amd64" ;; x86_64|amd64) architecture="amd64" ;;
arm64|aarch64) architecture="arm64";; arm64|aarch64) architecture="arm64";;
*) printf "\n%bArchitecture not supported, exiting.%b\n" "$c_red" "$c_reset" ; exit 1;; *) printf "\n%bArchitecture not supported, exiting.%b\n" "$c_red" "$c_reset" ; exit 1;;
esac esac
GetUrl="${BinaryUrl/TEMP/"$architecture"}" GetUrl="${BinaryUrl/TEMP/"$architecture"}"
if [[ $(command -v curl) ]]; then curl -L $GetUrl > "$ScriptWorkDir/$BinaryName" ; if [[ $(command -v curl) ]]; then curl -L $GetUrl > "$ScriptWorkDir/$BinaryName" ;
elif [[ $(command -v wget) ]]; then wget $GetUrl -O "$ScriptWorkDir/$BinaryName" ; elif [[ $(command -v wget) ]]; then wget $GetUrl -O "$ScriptWorkDir/$BinaryName" ;
else printf "%s\n" "curl/wget not available - get $BinaryName manually from the repo link, exiting."; exit 1; else printf "%s\n" "curl/wget not available - get $BinaryName manually from the repo link, exiting."; exit 1;
fi fi
[[ -f "$ScriptWorkDir/$BinaryName" ]] && chmod +x "$ScriptWorkDir/$BinaryName" [[ -f "$ScriptWorkDir/$BinaryName" ]] && chmod +x "$ScriptWorkDir/$BinaryName"
@@ -192,49 +197,49 @@ binary_downloader() {
distro_checker() { distro_checker() {
if [[ -f /etc/arch-release ]] ; then PkgInstaller="pacman -S" if [[ -f /etc/arch-release ]] ; then PkgInstaller="pacman -S"
elif [[ -f /etc/redhat-release ]] ; then PkgInstaller="dnf install" elif [[ -f /etc/redhat-release ]] ; then PkgInstaller="sudo dnf install"
elif [[ -f /etc/SuSE-release ]] ; then PkgInstaller="zypper install" elif [[ -f /etc/SuSE-release ]] ; then PkgInstaller="sudo zypper install"
elif [[ -f /etc/debian_version ]] ; then PkgInstaller="apt-get install" elif [[ -f /etc/debian_version ]] ; then PkgInstaller="sudo apt-get install"
elif [[ $(uname -s) == "Darwin" ]] ; then PkgInstaller="brew install"
else PkgInstaller="ERROR" ; printf "\n%bNo distribution could be determined%b, falling back to static binary.\n" "$c_yellow" "$c_reset" else PkgInstaller="ERROR" ; printf "\n%bNo distribution could be determined%b, falling back to static binary.\n" "$c_yellow" "$c_reset"
fi fi
} }
# Dependency check for jq in PATH or directory # Dependency check + installer function
if [[ $(command -v jq) ]]; then jqbin="jq" ; dependency_check() {
elif [[ -f "$ScriptWorkDir/jq" ]]; then jqbin="$ScriptWorkDir/jq" ; AppName="$1"
else AppVar="$2"
printf "%s\n" "Required dependency 'jq' missing, do you want to install it?" AppUrl="$3"
read -r -p "y: With packagemanager (sudo). / s: Download static binary. y/s/[n] " GetJq if [[ $(command -v $AppName) ]]; then export $AppVar="$AppName" ;
GetJq=${GetJq:-no} # set default to no if nothing is given elif [[ -f "$ScriptWorkDir/$AppName" ]]; then export $AppVar="$ScriptWorkDir/$AppName" ;
if [[ "$GetJq" =~ [yYsS] ]] ; then else
[[ "$GetJq" =~ [yY] ]] && distro_checker printf "%s\n" "Required dependency '$AppName' missing, do you want to install it?"
if [[ -n "$PkgInstaller" && "$PkgInstaller" != "ERROR" ]] ; then read -r -p "y: With packagemanager (sudo). / s: Download static binary. y/s/[n] " GetBin
(sudo $PkgInstaller jq) ; PkgExitcode="$?" GetBin=${GetBin:-no} # set default to no if nothing is given
[[ "$PkgExitcode" == 0 ]] && jqbin="jq" || printf "\n%bPackagemanager install failed%b, falling back to static binary.\n" "$c_yellow" "$c_reset" if [[ "$GetBin" =~ [yYsS] ]] ; then
[[ "$GetBin" =~ [yY] ]] && distro_checker
if [[ -n "$PkgInstaller" && "$PkgInstaller" != "ERROR" ]] ; then
[[ $(uname -s) == "Darwin" && "$AppName" == "regctl" ]] && AppName="regclient"
($PkgInstaller $AppName) ; PkgExitcode="$?" && AppName="$1"
if [[ "$PkgExitcode" == 0 ]] ; then { export $AppVar="$AppName" && printf "\n%b$AppName installed.%b\n" "$c_green" "$c_reset"; }
else printf "\n%bPackagemanager install failed%b, falling back to static binary.\n" "$c_yellow" "$c_reset"
fi
fi
if [[ "$GetBin" =~ [sS] || "$PkgInstaller" == "ERROR" || "$PkgExitcode" != 0 ]] ; then
binary_downloader "$AppName" "$AppUrl"
[[ -f "$ScriptWorkDir/$AppName" ]] && { export $AppVar="$ScriptWorkDir/$1" && printf "\n%b$AppName downloaded.%b\n" "$c_green" "$c_reset"; }
fi
else printf "\n%bDependency missing, exiting.%b\n" "$c_red" "$c_reset" ; exit 1 ;
fi fi
if [[ "$GetJq" =~ [nN] || "$PkgInstaller" == "ERROR" || "$PkgExitcode" != 0 ]] ; then
binary_downloader "jq" "https://github.com/jqlang/jq/releases/latest/download/jq-linux-TEMP"
[[ -f "$ScriptWorkDir/jq" ]] && jqbin="$ScriptWorkDir/jq"
fi
else printf "\n%bDependency missing, exiting.%b\n" "$c_red" "$c_reset" ; exit 1 ;
fi fi
fi # Final check if binary is correct
# Final check if binary is correct [[ "$1" == "jq" ]] && VerFlag="--version"
$jqbin --version &> /dev/null || { printf "%s\n" "jq is not working - try to remove it and re-download it, exiting."; exit 1; } [[ "$1" == "regctl" ]] && VerFlag="version"
${!AppVar} $VerFlag &> /dev/null || { printf "%s\n" "$AppName is not working - try to remove it and re-download it, exiting."; exit 1; }
}
# Dependency check for regctl in PATH or directory dependency_check "regctl" "regbin" "https://github.com/regclient/regclient/releases/latest/download/regctl-linux-TEMP"
if [[ $(command -v regctl) ]]; then regbin="regctl" ; dependency_check "jq" "jqbin" "https://github.com/jqlang/jq/releases/latest/download/jq-linux-TEMP"
elif [[ -f "$ScriptWorkDir/regctl" ]]; then regbin="$ScriptWorkDir/regctl" ;
else
read -r -p "Required dependency 'regctl' missing, do you want it downloaded? y/[n] " GetRegctl
if [[ "$GetRegctl" =~ [yY] ]] ; then
binary_downloader "regctl" "https://github.com/regclient/regclient/releases/latest/download/regctl-linux-TEMP"
[[ -f "$ScriptWorkDir/regctl" ]] && regbin="$ScriptWorkDir/regctl"
else printf "\n%bDependency missing, exiting.%b\n" "$c_red" "$c_reset" ; exit 1 ;
fi
fi
# Final check if binary is correct
$regbin version &> /dev/null || { printf "%s\n" "regctl is not working - try to remove it and re-download it, exiting."; exit 1; }
# Check docker compose binary # Check docker compose binary
if docker compose version &> /dev/null ; then DockerBin="docker compose" ; if docker compose version &> /dev/null ; then DockerBin="docker compose" ;
@@ -278,31 +283,70 @@ if [[ $t_out ]]; then
else t_out="" else t_out=""
fi fi
# Check the image-hash of every running container VS the registry check_image() {
for i in $(docker ps $Stopped --filter "name=$SearchName" --format '{{.Names}}') ; do i="$1"
((RegCheckQue+=1)) local Excludes=($Excludes_string)
progress_bar "$RegCheckQue" "$ContCount" for e in "${Excludes[@]}" ; do
# Looping every item over the list of excluded names and skipping if [[ "$i" == "$e" ]]; then
for e in "${Excludes[@]}" ; do [[ "$i" == "$e" ]] && continue 2 ; done echo Skip $i
return
fi
done
local NoUpdates GotUpdates GotErrors
ImageId=$(docker inspect "$i" --format='{{.Image}}') ImageId=$(docker inspect "$i" --format='{{.Image}}')
RepoUrl=$(docker inspect "$i" --format='{{.Config.Image}}') RepoUrl=$(docker inspect "$i" --format='{{.Config.Image}}')
LocalHash=$(docker image inspect "$ImageId" --format '{{.RepoDigests}}') LocalHash=$(docker image inspect "$ImageId" --format '{{.RepoDigests}}')
# Checking for errors while setting the variable # Checking for errors while setting the variable
if RegHash=$(${t_out} $regbin -v error image digest --list "$RepoUrl" 2>&1) ; then if RegHash=$(${t_out} $regbin -v error image digest --list "$RepoUrl" 2>&1) ; then
if [[ "$LocalHash" = *"$RegHash"* ]] ; then if [[ "$LocalHash" = *"$RegHash"* ]] ; then
NoUpdates+=("$i") echo NoUpdates "$i"
else else
if [[ -n "$DaysOld" ]] && ! datecheck ; then if [[ -n "$DaysOld" ]] && ! datecheck ; then
NoUpdates+=("+$i ${ImageAge}d") echo NoUpdates "+$i ${ImageAge}d"
else else
GotUpdates+=("$i") echo GotUpdates "$i"
fi fi
fi fi
else else
# Here the RegHash is the result of an error code # Here the RegHash is the result of an error code
GotErrors+=("$i - ${RegHash}") echo GotErrors "$i - ${RegHash}"
fi fi
done }
# Make required functions and variables available to subprocesses
export -f check_image datecheck
export Excludes_string="${Excludes[@]}" # Can only export scalar variables
export t_out regbin RepoUrl DaysOld
# Check for POSIX xargs with -P option, fallback without async
if (echo "test" | xargs -P 10 >/dev/null 2>&1) ; then
XargsAsync="-P $MaxAsync"
else
XargsAsync=""
printf "%bMissing POSIX xargs, consider installing 'findutils' for asynchronous lookups.%b\n" "$c_red" "$c_reset"
fi
# Asynchronously check the image-hash of every running container VS the registry
while read -r line; do
((RegCheckQue+=1))
progress_bar "$RegCheckQue" "$ContCount"
Got=${line%% *} # Extracts the first word (NoUpdates, GotUpdates, GotErrors)
item=${line#* }
case "$Got" in
NoUpdates) NoUpdates+=("$item") ;;
GotUpdates) GotUpdates+=("$item") ;;
GotErrors) GotErrors+=("$item") ;;
Skip) ;;
*) echo "Error! Unexpected output from subprocess: ${line}" ;;
esac
done < <( \
docker ps $Stopped --filter "name=$SearchName" --format '{{.Names}}' | \
xargs ${XargsAsync} -I {} bash -c 'check_image "{}"' \
)
# Sort arrays alphabetically # Sort arrays alphabetically
IFS=$'\n' IFS=$'\n'
@@ -310,6 +354,11 @@ NoUpdates=($(sort <<<"${NoUpdates[*]}"))
GotUpdates=($(sort <<<"${GotUpdates[*]}")) GotUpdates=($(sort <<<"${GotUpdates[*]}"))
unset IFS unset IFS
# Run the prometheus exporter function
if [ -n "$CollectorTextFileDirectory" ] ; then
source "$ScriptWorkDir"/addons/prometheus/prometheus_collector.sh && prometheus_exporter ${#NoUpdates[@]} ${#GotUpdates[@]} ${#GotErrors[@]}
fi
# Define how many updates are available # Define how many updates are available
UpdCount="${#GotUpdates[@]}" UpdCount="${#GotUpdates[@]}"