Merge ~ubuntu-docker-images/ubuntu-docker-images/+git/utils:update-and-check-images into ~ubuntu-docker-images/ubuntu-docker-images/+git/utils:master

Proposed by Bryce Harrington
Status: Rejected
Rejected by: Bryce Harrington
Proposed branch: ~ubuntu-docker-images/ubuntu-docker-images/+git/utils:update-and-check-images
Merge into: ~ubuntu-docker-images/ubuntu-docker-images/+git/utils:master
Diff against target: 391 lines (+379/-0)
2 files modified
software-versions.sh (+229/-0)
update-images.sh (+150/-0)
Reviewer Review Type Date Requested Status
Sergio Durigan Junior Needs Fixing
Lucas Kanashiro Needs Information
Review via email: mp+398323@code.launchpad.net

Description of the change

Towards the objective of flagging merge opportunities for OCI image updates, and to assist in mechanizing at least the simpler refreshes, this adds a pair of scripts:

update-images.sh handles the mechanics of updating the Dockerfile, building the image, and running the unit tests for verification. It uploads the images to the packager's dockerhub repos, but does not push the images to production (that's a job for push-images.sh). The packager is also responsible for pushing the git branch and filing the MP.

check-images.sh extracts version numbers from the official images, as well as local images such as generated from update-images.sh. This first version of the script is limited to five packages, so will need further development to make it more broadly useful.

I'm not wedded to these names for the scripts.

These have been developed rather independently from push-images.sh, however there are some similar functional needs between all of them. I expect that the commonly used functionality could be broken out to a shared include file. There are likely other adjustments that can be made to make all the scripts consistent with code style, variable naming, etc. as followup work.

To post a comment you must log in.
Revision history for this message
Lucas Kanashiro (lucaskanashiro) wrote :

Thanks for the scripts Bryce. I did not take a deep look into your implementation but based on your description I would say that update-images.sh does not need to upload the image to DockerHub under the packager's namespace, you could simply update the image locally and run the tests against the local image. I can see no gain in uploading it to a personal DockerHub repository, do you have a reason to do it?

review: Needs Information
Revision history for this message
Bryce Harrington (bryce) wrote :

The reason was just in case it facilitated easier testing during review. However, if not then I agree that's probably out of scope for this script.

Revision history for this message
Sergio Durigan Junior (sergiodj) wrote :

I agree with Lucas: we can run the tests locally, so the part about pushing the tags to a developer namespace seems like it's not needed.

I have a few comments/questions about the script, but other than that it looks great. Thanks for doing it.

review: Needs Fixing
Revision history for this message
Bryce Harrington (bryce) wrote :

Discussion on the comments below.

I've dropped the push statement. I'll work on the function codedocs and other refinements for the next rev.

Revision history for this message
Sergio Durigan Junior (sergiodj) :
Revision history for this message
Sergio Durigan Junior (sergiodj) :
Revision history for this message
Bryce Harrington (bryce) wrote :

On Fri, Feb 19, 2021 at 10:19:00PM -0000, Sergio Durigan Junior wrote:
> > +
> > + cd "${REPOS_DIR}/${software_name}" \
> > + || die "Could not chdir to ${REPOS_DIR}/${software_name}"
> > +
> > + git checkout -f edge
>
> Ops, scratch that. Of course you will need to be inside a valid branch to create another one from it. I understand why you're using edge here now. But just as the edge tag will move as we update the images, I think the branch ought to move as well. Maybe the way to go here is to recreate the edge branch off of the current one, once the script finishes the update.
>

Yeah, like I mentioned I expect this section of code will be expanded on
as we sort out how we want to handle branch updates, but I'm not sure
how this should work yet. Next time we do updates to these branches
we'll need to majorly rework this section.

39a0596... by Bryce Harrington

check-images: Add support for updating redis

2649b8f... by Bryce Harrington

update-images: Resync shared functions with check-images

b76c036... by Bryce Harrington

Rename check-images.sh to software-versions.sh and make it sourceable

Also document each of the routines

6542054... by Bryce Harrington

Make update-versions.sh source software-versions.sh

This allows avoiding code duplication. Routines pertaining to software
version determination are consolodated to software-versions.sh.

Also document each of the routines.

c86e57d... by Bryce Harrington

update-images.sh: Don't create new branches

The original code handled the rename of 'edge' branches to the new
naming scheme. Now that this renaming is completed, there is no need
for this logic.

For now, simply expect that the branch we are going to work in already
exists. This might need further development to create new branches when
there are new major versions of software, but for now assume we'll
handle those situations manually.

f3edcd8... by Bryce Harrington

update-images.sh: Also refresh the docs

Revision history for this message
Bryce Harrington (bryce) wrote :

I've reworked both scripts a fair bit, and added code docs as requested.

Both scripts still run (for me at least). The software-versions.sh script outputs:

Software Distro Current-Image Proposed-Image
apache2 2.4.46-2ubuntu1 2.4.41-4ubuntu3.1 2.4.46-2ubuntu1
memcached 1.6.9+dfsg-1 1.6.9+dfsg-1 1.6.9+dfsg-1
mysql-8.0 8.0.23-3build1 8.0.23-3build1 8.0.23-1ubuntu2
nginx 1.18.0-6ubuntu4 1.18.0-6ubuntu4 -
postgresql-13 13.2-1 - -
redis 5:6.0.11-1 5:6.0.9-4 -

I don't consider either script 'finished' by any stretch, but this is a good checkpoint for review purposes.

Future work includes:
* Add support for telegraf(?) and the snap-based images(???)
* More error checking
* Use log()/error() from push-images.sh instead of die()
* Output version data as JSON (for feeding into merge opportunity report)
* Ability to update branches for stable Ubuntu releases too (i.e. not just the devel release)
* Ability to appropriately create branches as needed for new major versions
* Less hard-coding of package-specific information, perhaps by relying on data/<package>.yaml
* Add use of getopts and better usage()

Revision history for this message
Sergio Durigan Junior (sergiodj) :
3eb648b... by Bryce Harrington

software-versions.sh: Special-case postgres' software version

Minor version bumps of postgres are allowed in stable Ubuntu releases,
per security team policy. Thus for tagging OCI images we should
reference only the major version in this particular case.

Revision history for this message
Bryce Harrington (bryce) wrote :

I've done a reimplementation on software-versions.sh, with the goal of establishing a better git history for review, and to further generalize handling of package-specific stuff. I'll reintroduce this script with a new MP.

Regarding the update-images.sh script, there has been a bit of fluidity in tag naming policies and in our requirements for how things need updated. Due to this I don't feel we're at a point we can really nail down the requirements for this script. I'm going to withdraw this script for now; it can be reimplemented and reintroduced when the maintenance situation is more stable.

Unmerged commits

3eb648b... by Bryce Harrington

software-versions.sh: Special-case postgres' software version

Minor version bumps of postgres are allowed in stable Ubuntu releases,
per security team policy. Thus for tagging OCI images we should
reference only the major version in this particular case.

f3edcd8... by Bryce Harrington

update-images.sh: Also refresh the docs

c86e57d... by Bryce Harrington

update-images.sh: Don't create new branches

The original code handled the rename of 'edge' branches to the new
naming scheme. Now that this renaming is completed, there is no need
for this logic.

For now, simply expect that the branch we are going to work in already
exists. This might need further development to create new branches when
there are new major versions of software, but for now assume we'll
handle those situations manually.

6542054... by Bryce Harrington

Make update-versions.sh source software-versions.sh

This allows avoiding code duplication. Routines pertaining to software
version determination are consolodated to software-versions.sh.

Also document each of the routines.

b76c036... by Bryce Harrington

Rename check-images.sh to software-versions.sh and make it sourceable

Also document each of the routines

2649b8f... by Bryce Harrington

update-images: Resync shared functions with check-images

39a0596... by Bryce Harrington

check-images: Add support for updating redis

087c41b... by Bryce Harrington

Add initial version of check-images.sh script

This script can be used to examine the versions of software installed in
the docker images. Currently, this script only works with apache2,
memcached, mysql, nginx, and postgres, and has only been lightly tested
even with them.

Future work includes cli support to allow specifying the software to
be examined, better error checking, and ability to output in some
structured file format (i.e. JSON and/or YAML). Better command line
argument handling would likely also help...

Further, this copies some functionality from update-images.sh that
should be moved into a common file and sourced.

7638fb8... by Bryce Harrington

Add preliminary script 'update-images.sh'

This script is intended to facilitate simple refreshes of docker images
using the version in the current devel release of Ubuntu. This has only
been tested against apache2, memcached, mysql, nginx, and postgres.
Other images might work, but YMMV.

In particular, if adjustments need to be made to the Dockerfile to
facilitate the new software, this script probably won't be sufficient to
do the update.

Note that the docker image name, test script name, source package name,
and binary package name for a given piece of software may be
considerably different. To address that, we just brute force hack
around it by hardcoding some name translations; hopefully later on this
can be done better.

In particular, some software names include major version numbers
(e.g. to indicate API levels), such as mysql-8.0 and postgresql-13.
This script includes some logic to figure these numbers out, which may
or may not work more broadly. Again, it's hoped this can be replaced by
something more robust eventually.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/software-versions.sh b/software-versions.sh
0new file mode 1007550new file mode 100755
index 0000000..4172ce8
--- /dev/null
+++ b/software-versions.sh
@@ -0,0 +1,229 @@
1#!/usr/bin/env bash
2# -*- coding: utf-8; mode: sh -*-
3
4# software-versions.sh: Determine versions of packages installed in OCI images
5
6progname=$(basename "${0}")
7progdir=$(dirname "$(readlink -f "${0}")")
8
9if [ -z "${DOCKER_USER}" ]; then
10 DOCKER_USER=$(docker info | grep Username: | cut -d: -f2 | xargs)
11fi
12if [ -z "${DISTRO_CODENAME}" ]; then
13 DISTRO_CODENAME=$(distro-info --devel)
14fi
15
16die() {
17 echo "Error: ${*}" 1>&2
18 exit 1
19}
20
21
22# Converts a package version number to equivalent upstream version.
23# Strips off epoch, if any, and drops the debian version.
24#
25# :param v: Package version number to convert
26upstream_version() {
27 local v="${1#*:}"
28 echo "${v%-*}"
29}
30
31# Look up version of package in a given Ubuntu release
32#
33# @param source_package_name: Name of the source package to look up
34get_source_package_version() {
35 local source_package_name="${1}"
36
37 rmadison -s "${DISTRO_CODENAME}" "${source_package_name}" | cut -d\| -f2 | xargs
38}
39
40# Provides the "version family" of the software
41#
42# For a given Ubuntu stable release, updates will be limited to certain
43# ranges of version numbers based on API and stability policies. For
44# example, some may get patch-level updates (i.e. M.N.1->M.N.2->M.N.3)
45# and thus have an "M.N" version family, others may get minor-level
46# updates (such as postrgres, which in focal had 12.4->12.5->12.6) so
47# have an "M" version family.
48#
49# This routine determines this from the debian version by extracting the
50# first two numbers from upstream version portion.
51#
52# @param software_name: Formal name of the upstream software
53get_source_package_major() {
54 # TODO: This routine should be named get_software_version_family
55 # or get_software_main_version or something similar
56 local software_name="${1}"
57 local package_version=""
58
59 # For apache2, just return 2.4
60 if [ "${software_name}" = "apache2" ]; then
61 package_version=$(get_source_package_version apache2)
62
63 # For postgres return 12, 13, etc.
64 elif [ "${software_name}" = "postgres" ]; then
65 pg_api_version=$(get_source_package_version postgresql)
66 pg_api_package="postgresql-${pg_api_version%+*}"
67 package_version=$(get_source_package_version "${pg_api_package}")
68
69 # For mysql return 5.7, 8.0, etc.
70 elif [ "${software_name}" = "mysql" ]; then
71 package_version=$(get_source_package_version mysql-server)
72
73 elif [ "${software_name}" = "redis" ]; then
74 package_version=$(get_source_package_version redis-server)
75 fi
76
77 if [ -n "${package_version}" ]; then
78 version=$(upstream_version "${package_version}")
79 local v=( ${version//./ } )
80 local version_major="${v[0]}"
81 local version_minor="${v[1]}"
82 if [ "${software_name}" = "postgres" ]; then
83 echo "${version_major}"
84 else
85 echo "${version_major}.${version_minor}"
86 fi
87 fi
88
89 # For everything else, return blank
90 echo ""
91}
92
93# Determine the source package for the given software name
94#
95# In many cases, the software name is identical to the source package's
96# name, however there are cases where the spelling may vary
97# (e.g. postgres vs. postgresql), or where the source package includes
98# part of the version number. This routine does this mapping.
99#
100# @param software_name: Formal name of the upstream software
101get_software_source_package() {
102 local software_name="${1}"
103 local source_package_name=""
104
105 # Look to see if there is a <software>-N.M package
106 local package_major=$(get_source_package_major "${software_name}")
107 if [ -n "${package_major}" ]; then
108 local prefix="${software_name}"
109 if [ "${software_name}" = "postgres" ]; then
110 prefix="postgresql"
111 fi
112
113 source_package_name=$(rmadison -s "${DISTRO_CODENAME}" "${prefix}-${package_major}" | cut -d\| -f1 | xargs)
114 if [ -n "${source_package_name}" ] && [ "${source_package_name}" != "${prefix}-${package_major}" ]; then
115 die "error: ${source_package_name} is not ${prefix}-${package_major} as expected"
116 fi
117 fi
118
119 # If not, default to just <software>
120 if [ -z "${source_package_name}" ]; then
121 source_package_name="${software_name}"
122 fi
123
124 echo "${source_package_name}"
125 return 0
126}
127
128# Get primary binary package for given software
129#
130# For some software, the binary package names differ from the source
131# package name, so when checking installed versions we need to be careful
132# to use the appropriate binary package name. For now, we just manually
133# translate a few specific cases.
134#
135# @param software_name: Formal name of the upstream software
136get_software_binary_package() {
137 local software_name="${1}"
138 local package_major=$(get_source_package_major "${software_name}")
139
140 if [ "${software_name}" = "mysql" ]; then
141 # mysql-client-core-N and mysql-client-N are also installed
142 echo "mysql-server-core-${package_major}"
143 elif [ "${software_name}" = "postgres" ]; then
144 echo "postgresql-${package_major}"
145 elif [ "${software_name}" = "nginx" ]; then
146 # nginx-common is also installed
147 echo "nginx-full"
148 elif [ "${software_name}" = "redis" ]; then
149 echo "redis-server"
150 else
151 echo "${software_name}"
152 fi
153}
154
155# Lookup a binary package version present in a given Docker image
156#
157# Uses apt-cache policy to determine installed version of a specific
158# binary package.
159#
160# @param image_id: The name of the image to examine
161# @param binary_package_name: The binary package to look up version info
162get_image_installed_package_version() {
163 local image_id="${1}"
164 local binary_package_name="${2}"
165
166 [ -n "${image_id}" ] || return 1
167
168 docker run --rm \
169 --name "${image_id}-version-check" \
170 "${image_id}" \
171 apt-cache policy "${binary_package_name}" \
172 | grep "Installed:" | cut -d: -f2- | xargs
173}
174
175# Display a table entry for the given software
176#
177# This is an aide for summarizing versions of software packages in the
178# distro, vs. what's in the existing ubuntu-namespaced Docker images.
179# It also displays versions of the package in the user's namespace
180# as a convenience for development work.
181#
182# @param software_name: Formal name of the upstream software
183get_software_versions() {
184 # For given software_name:
185 # Create container from the image
186 # Log into the container
187 # Retrieve the version for software_name
188 software_name="${1}"
189 source_package_name="$(get_software_source_package ${software_name})"
190 binary_package_name="$(get_software_binary_package ${software_name})"
191 proposed_image_name="${DOCKER_USER}/${software_name}"
192 current_image_name="ubuntu/${software_name}"
193
194 proposed_image_id=$(docker image ls | grep ^${proposed_image_name} | grep edge | awk '{print $3}' | uniq)
195 current_image_id=$(docker image ls | grep ^${current_image_name} | grep edge | awk '{print $3}' | uniq)
196 package_version_distro=$(get_source_package_version "${source_package_name}")
197 package_version_current=$(get_image_installed_package_version "${current_image_id}" "${binary_package_name}")
198 package_version_proposed=$(get_image_installed_package_version "${proposed_image_id}" "${binary_package_name}")
199
200 printf "%-20s %-30s %-30s %-30s\n" "${source_package_name}" \
201 "${package_version_distro:--}" "${package_version_current:--}" "${package_version_proposed:--}"
202}
203
204# Tabular report of Canonical Server's debian-based docker images
205#
206# Creates a summary of package version numbers for software installed
207# in docker images that the canonical server team maintains.
208display_version_report() {
209 docker image pull ubuntu/apache2:edge
210 docker image pull ubuntu/memcached:edge
211 docker image pull ubuntu/mysql:edge
212 docker image pull ubuntu/nginx:edge
213 docker image pull ubuntu/postgres:edge
214 docker image pull ubuntu/redis:edge
215
216 printf "%-20s %-30s %-30s %-30s\n" "Software" "Distro" "Current-Image" "Proposed-Image"
217 get_software_versions "apache2"
218 get_software_versions "memcached"
219 get_software_versions "mysql"
220 get_software_versions "nginx"
221 get_software_versions "postgres"
222 get_software_versions "redis"
223}
224
225# If we're being run directly (i.e. not sourced), then
226# print out the current software versions.
227if [[ "${0}" == "${BASH_SOURCE[0]}" ]]; then
228 display_version_report
229fi
diff --git a/update-images.sh b/update-images.sh
0new file mode 100755230new file mode 100755
index 0000000..ecea08a
--- /dev/null
+++ b/update-images.sh
@@ -0,0 +1,150 @@
1#!/usr/bin/env bash
2# -*- coding: utf-8; mode: sh -*-
3
4# update-images.sh: Refresh software versions for Ubuntu's OCI images
5
6progname=$(basename "${0}")
7progdir=$(dirname "$(readlink -f "${0}")")
8
9# shellcheck source=/dev/null disable=SC1090
10source "${progdir}/software-versions.sh"
11
12if ! groups | grep docker > /dev/null; then
13 echo "Error: Must be in docker group" 1>&2
14 exit 1
15fi
16
17if [ -z "${TESTS_DIR}" ]; then
18 TESTS_DIR="${progdir}/../server-test-scripts/oci-unit-tests"
19fi
20if [ -z "${REPOS_DIR}" ]; then
21 REPOS_DIR="${progdir}/../"
22fi
23
24usage() {
25 cat >&2 <<EOF
26Usage: ${progname} <package-name> [package-name ...]
27
28EOF
29 # shellcheck source=/dev/null disable=SC2086
30 exit "${1}"
31}
32
33# Modifies contents of the Dockerfile in the current directory
34#
35# @param software_name: Formal name of the upstream software for the Dockerfile
36update_dockerfile_parameters() {
37 local software_name="${1}"
38 local dockerfile="./Dockerfile"
39
40 # TODO: Given that we're in the git repository, can we determine the
41 # software_name from just that? If so, then this can run without
42 # argument $1.
43
44 if [ ! -e "${dockerfile}" ]; then
45 die "error: Dockerfile ${dockerfile} not found at ${PWD}"
46 fi
47
48 # Update global parameters
49 sed -i "/FROM ubuntu:/c\FROM ubuntu:${DISTRO_CODENAME}" "${dockerfile}"
50
51 # Update per-package parameters
52 if [ "${software_name}" = "postgres" ]; then
53 pg_major=$(get_source_package_major postgresql)
54 sed -i "/ENV PG_MAJOR/c\ENV PG_MAJOR ${pg_major}" "${dockerfile}"
55 fi
56
57 return 0
58}
59
60# Creates branch for a new Ubuntu distro series and updates package to match
61#
62# This routine can be run to create an updated image for a new version
63# of Ubuntu. It sets the Dockerfile to the new Ubuntu version, and
64# rebuilds it with the version of the software's package from that
65# release.
66#
67# @param software_name: Formal name of the upstream software for the Dockerfile
68update_image() {
69 [ -n "${1}" ] \
70 || usage 1
71
72 local software_name="${1}"
73 local package_name=$(get_software_source_package "${software_name}")
74 local package_version=$(get_source_package_version "${package_name}")
75 local package_major=$(get_source_package_major "${software_name}")
76 local upstream_version=$(upstream_version "${package_version}")
77 # TODO: Remove +dfsg
78 local v=( ${upstream_version//./ } )
79 local version_major="${v[0]}"
80 local version_minor="${v[1]}"
81 local docker_image="${DOCKER_USER}/${software_name}:21.04"
82 local test_script="${TESTS_DIR}/${software_name}_test.sh"
83 local distro_release=$(distro-info --series "${DISTRO_CODENAME}" -r)
84 local branch_name="${version_major}.${version_minor}-${distro_release}"
85
86 # Hack, due to postgres/postgresql discrepancy
87 if [ "${software_name}" = "postgres" ]; then
88 test_script="${TESTS_DIR}/postgresql_test.sh"
89 fi
90
91 echo
92 echo "########################################################"
93 echo "# distro: ${DISTRO_CODENAME} (${distro_release})"
94 echo "# software: ${software_name} -> ${package_name} ${package_major}"
95 echo "# docker_image: ${docker_image}"
96 echo "# test_script: ${test_script}"
97 echo "# version: ${package_version}"
98 echo "# upstream: ${upstream_version}"
99 echo "# branch_name: ${branch_name}"
100 echo "########################################################"
101
102 cd "${REPOS_DIR}/${software_name}" \
103 || die "Could not chdir to ${REPOS_DIR}/${software_name}"
104
105 git checkout "${branch_name}" \
106 || die "Could not checkout ${branch_name} from git"
107
108 # Update Dockerfile parameters
109 echo
110 echo "### Updating Dockerfile ###"
111 update_dockerfile_parameters "${software_name}"
112
113 # Update the docs
114 make all-doc \
115 || die "Could not update the documentation"
116
117 # Build the image
118 echo
119 echo "### Building Image ###"
120 docker build --tag "${docker_image}" . \
121 || die "Failed to build ${docker_image} for ${package_name}"
122
123 git commit -a -m "Update docker image to ${package_version} for ${DISTRO_CODENAME}"
124
125 # Run the test
126 echo
127 echo "### Testing Image ###"
128 if [ ! -e "${test_script}" ]; then
129 die "Test script for ${software_name} not found at ${test_script}"
130 fi
131 cd "${TESTS_DIR}" \
132 || die "Could not chdir to ${TESTS_DIR}"
133 DOCKER_IMAGE="${docker_image}" bash ${test_script} \
134 || die "Failed ${docker_image}'s test for ${package_name}"
135
136 exit 0
137}
138
139if [[ "${0}" == "${BASH_SOURCE[0]}" ]]; then
140 [ -n "${1}" ] \
141 || usage 1
142
143 for package_name in ${@}; do
144 echo "Updating ${package_name}"
145 update_image "${package_name}"
146 echo
147 echo "---"
148 echo
149 done
150fi

Subscribers

People subscribed via source and target branches

to all changes: