Compare commits

..

No commits in common. "master" and "master" have entirely different histories.

13 changed files with 26 additions and 242 deletions

3
.gitignore vendored
View File

@ -1,3 +0,0 @@
__pycache__/
updates.json

View File

@ -1,2 +1,2 @@
FROM alpine:3.7
RUN apk add --update --no-cache py3-yaml bash && pip3 install docker-compose requests packaging
RUN apk add --update --no-cache py3-yaml bash && pip3 install docker-compose

View File

@ -4,7 +4,6 @@ services:
image: docker.clkl.de/docker/update:0.1_alpine
build: .
volumes:
# - /opt/docker/services:/services
- ./docker-compose.py:/docker-compose.py
- ./sample:/services
- ./:/out/
command: bash -c 'python3 /out/show_updateable.py -s -o /out/updates.json /services/* --ignore zammad'
command: bash -c 'python3 /docker_compose.py /services/*'

View File

@ -41,7 +41,7 @@ def parse_dockerfile(build):
return [f]
keyword = "FROM"
with open(path, "r") as src:
sources = [source_to_image(line) for line in src if line.strip().startswith(keyword)] # TODO lower/upper
sources = [source_to_image(line) for line in src if line.strip().startswith(keyword)]
return sources
def image_info(image):
@ -129,7 +129,6 @@ def args_setup(description):
parser.add_argument("compose_files", nargs="+")
parser.add_argument("--output", "-o")
parser.add_argument("--ignore", "-i", nargs="+", default=False)
parser.add_argument("--match-suffix", "-s", action="store_true")
return parser
if __name__ == "__main__":

View File

@ -17,7 +17,7 @@ TAG_STORE = {}
def api_call(url):
result = requests.get(url)
if not result.ok:
log.error(f"{result}, {result.status_code}, {result.url}")
log.error(result, result.url)
return {}
data = result.json()
tags = {}
@ -44,11 +44,7 @@ def replace(string, replacements):
return string
def compare(base, other, match_suffix=False, replacements=[("-","+"),]):
if match_suffix and "-" in base:
suffix = base.split("-")[-1]
if not other.endswith(suffix):
return False
def compare(base, other, replacements=[("-","+"),]):
base = replace(base, replacements)
other = replace(other, replacements)
v1 = version.parse(base)
@ -57,17 +53,23 @@ def compare(base, other, match_suffix=False, replacements=[("-","+"),]):
log.debug(f"{v1} < {v2}: {result}")
return result
def get_new_tags(image, match_suffix=False):
def get_new_tags(image):
if not ":" in image:
log.warn("using implicit latest, skip")
return
image_name, current_tag = image.split(":")
if not image_name in TAG_STORE:
TAG_STORE[image_name] = get_tags(image_name)
#if current_tag in TAG_STORE[image_name]:
# first_update = TAG_STORE[image_name][current_tag]
#else:
# print("!!! FALLBACK!")
# first_update = list(TAG_STORE[image_name].values())[0]
#print(first_update)
new_tags = {}
for tag in TAG_STORE[image_name]:
log.debug("check("+str(tag)+")")
if compare(current_tag, tag, match_suffix):
if compare(current_tag, tag):
log.debug("NEWER!!!")
update = TAG_STORE[image_name][tag]
new_tags[tag] = str(update)

127
readme.md
View File

@ -1,127 +0,0 @@
Docker Update
=============
Show available image-updates for your docker-compose managed services. Checks docker-compose image-tags as well as connected Dockerfiles in build-sections.
Lists (possible) available updates and where the old image(-tag) is used. Optimized for use with "pinned" tags. (Use a specific minor version tag to fuse your infrastrucutre - e.g. `10.2-alpine` instead of `10-alpine`.)
Requirements
------------
* Docker
* docker-compose
* Python >=3.6
* Libraries: requirements.txt (`pip3 install -r requirements.txt` or docker ;))
Filesystem Structure
--------------------
You can call it on a single directory containing your `docker-compose.yml`.
Or you can execute it on a directory containing your service directories. These services must have a docker-compose.yml to get checked.
Example:
```
└── services
├── bitpoll.example.org
│   ├── docker
│  │  └── Dockerfile
│   └── docker-compose.yml
├── dockerui
│   └── docker-compose.yml
└── gitea
└── docker-compose.yml
```
If there are files or directories without a docker-compse.yml, it will just notify you and ignore it.
If the compose file contains a build-section, the Dockerfile is inspected, too.
Usage
----
### Docker
Modify mount of services directory. Mount your directory as `/services`.
```
docker-compose up
```
Output file: `updates.json`
### Command line
```
$ python3 show_updateable.py -h
usage: show_updateable.py [-h] [--output OUTPUT]
[--ignore IGNORE [IGNORE ...]] [--match-suffix]
compose_files [compose_files ...]
```
* output: json file for results
* ignore: ignore services (ignore is substring of service path)
* match-suffix: use only same suffixes in image labels (e.g. only -alpine images)
* compose files: service directories: see #example (multiple paths allowed)
Example Output
--------------
```
{
"postgres:10-alpine": {
"updates": {
"10.1-alpine": "2018-01-10 04:44:17.433471",
"10.2-alpine": "2018-02-19 19:43:46.911031",
"10.3-alpine": "2018-05-12 10:44:57.814207",
"10.4-alpine": "2018-08-01 14:49:09.002434",
"11-alpine": "2018-08-01 14:46:34.449579"
},
"usages": [
{
"path": "/services/bitpoll.example.org/docker-compose.yml",
"service_name": "dbbitpoll.example.org"
},
{
"path": "/services/gitea/docker-compose.yml",
"service_name": "dbgitea"
}
]
}
}
```
Advantages
----------
* No access to Docker-Socket
* No deamon
* No state
* Detect new major versions
* Report only, no uncontrolled automated actions
* tbc ...
Alternatives
------------
* https://github.com/v2tec/watchtower (seems quite dead, look for forks)
* https://github.com/pyouroboros/ouroboros
* https://engineering.salesforce.com/open-sourcing-dockerfile-image-update-6400121c1a
* https://stackoverflow.com/questions/26423515/how-to-automatically-update-your-docker-containers-if-base-images-are-updated
* tbc ...
Known Issues
------------
* Still WiP/PoC
* http/https sources are not implemented yet
* does not handle image updates without changing tags
* some images have … weird tags
* pull requests welcome

View File

@ -2,7 +2,7 @@ version: "2"
services:
http:
image: httpd:2.4.32-alpine
image: httpd:alpine
# ports:
# - "80:80"
volumes:
@ -12,9 +12,8 @@ services:
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik_net"
- "traefik.http.frontend.rule=Host:git24.example"
- "traefik.http.frontend.rule=Host:potato.kinf.wiai.uni-bamberg.de,www.potato.kinf.wiai.uni-bamberg.de,141.13.94.68,localhost,uni.clkl.de"
networks:
traefik_net:
external:
name: traefik_net

View File

@ -1,35 +0,0 @@
version: '2'
services:
cloud:
image: nextcloud:14.0.8-apache
# image: owncloud/server:10.0.8
restart: on-failure:5
depends_on:
- dbcloud
volumes:
- ./data:/files
- ./config:/var/www/html/config
- ./apps:/var/www/html/custom_apps
ports:
- "127.0.0.1:3020:80"
dbcloud:
image: mariadb:10.3.2
restart: on-failure:5
env_file:
- docker.env
environment:
# - MYSQL_ROOT_PASSWORD=VerySecurePassword
# - MYSQL_USER=cloud
# - MYSQL_PASSWORD=CorrectBatteryHorseStaple
# - MYSQL_DATABASE=cloud
- MYSQL_MAX_ALLOWED_PACKET=128M
- MYSQL_INNODB_LOG_FILE_SIZE=64M
- MYSQL_INNODB_LARGE_PREFIX=ON
- MYSQL_INNODB_FILE_FORMAT=Barracuda
volumes:
- ./mysql:/var/lib/mysql

View File

@ -1,22 +0,0 @@
version: "3"
services:
someservice:
build:
context: ./docker/
dockerfile: Dockerfile
ports:
- "8080:80"
restart: on-failure:5
depends_on:
- dbsomeservice
dbsomeservice:
image: mariadb:10.3.2
restart: on-failure:5
env_file:
- docker.env
volumes:
- ./mysql:/var/lib/mysql

View File

@ -1,7 +0,0 @@
FROM alpine:3.6
EXPOSE 80
RUN apk add --update python3 python3-dev git g++ nodejs-npm mariadb-dev mariadb-client-libs openldap-dev \
&& apk del nodejs-npm mariadb-dev git g++ python3-dev

View File

@ -1,21 +1,10 @@
import argparse
import json
import logging
import docker_compose
import image_tags
log = logging
def find_updates(image_ref, usages, match_suffix=False):
try:
newer_tags = image_tags.get_new_tags(image_ref, match_suffix)
except ValueError as e:
newer_tags = e.args
return {
"updates": newer_tags,
"usages": usages,
}
def main(args):
@ -24,24 +13,14 @@ def main(args):
for image in images:
for tag in images[image]:
image_ref = f"{image}:{tag}"
if image_ref in updates:
continue
updates[image_ref] = find_updates(image_ref, images[image][tag], args.match_suffix)
for usage in images[image][tag]:
if not "base_images" in usage:
continue
for base in usage["base_images"]:
info = {
"is_base_image": True,
"path": usage["path"],
"service_name": usage["service_name"]
}
if base in updates:
updates[base]["usages"].append(info)
else:
log.info(f"find base image updates for {base}")
updates[base] = find_updates(base, [info], args.match_suffix)
try:
newer_tags = image_tags.get_new_tags(image_ref)
except ValueError as e:
newer_tags = e.args
updates[image_ref] = {
"updates": newer_tags,
"usages": images[image][tag]
}
if args.output:
with open(args.output, "w") as out:
json.dump(updates, out, indent=1, sort_keys=True)
@ -52,5 +31,5 @@ def main(args):
if __name__=="__main__":
parser = docker_compose.args_setup("Show updates for docker-compose style services")
args = parser.parse_args()
print(args)
main(args)