Compare commits
No commits in common. "master" and "master" have entirely different histories.
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
__pycache__/
|
||||
updates.json
|
||||
|
||||
@ -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
|
||||
@ -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/*'
|
||||
@ -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__":
|
||||
|
||||
@ -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
127
readme.md
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user