Merge ~ubuntu-docker-images/ubuntu-docker-images/+git/utils:software-versions into ~ubuntu-docker-images/ubuntu-docker-images/+git/utils:master

Proposed by Bryce Harrington
Status: Merged
Merge reported by: Bryce Harrington
Merged at revision: 328e3508aa1d358e0193c61c2f578df556b1d8b3
Proposed branch: ~ubuntu-docker-images/ubuntu-docker-images/+git/utils:software-versions
Merge into: ~ubuntu-docker-images/ubuntu-docker-images/+git/utils:master
Diff against target: 279 lines (+273/-0)
1 file modified
software-versions.sh (+273/-0)
Reviewer Review Type Date Requested Status
Sergio Durigan Junior Approve
Canonical Server Pending
Review via email: mp+401008@code.launchpad.net

Description of the change

This is not functionally complete yet but is at a good checkpoint to review.

This is a top-down rewrite of my original POC, with commits split up and organized better for review. The implementation focus for this part is determining the Ubuntu archive version information, for both focal and hirsute. (The other part still to come is determining the package version in the corresponding OCI package, but that will be followup.)

The script can be run in one of two ways:

    $ software-versions.sh --format text
    $ software-versions.sh --format json

The first form is the default if no args are provided. Both should provide equivalent data, just displayed differently. The latter is intended for machine consumption so must always generate valid JSON data. There is a json-check script in ubuntu-helpers that can do this verification.

While this only prints out info for hirsute and focal, I am developing it with an eye towards eventually needing to add maybe xenial + bionic + i-series, although I've not tested those yet.

One other possible future change is that currently the list of packages is hardcoded. I hope to maybe move package-specific data into data files, maybe included in the package oci repositories. Thoughts on that would be appreciated. At this point there's just two package-specific bits - the M.N style needed for the source package name, and the identity of the primary binary package installed from the source package.

To post a comment you must log in.
Revision history for this message
Sergio Durigan Junior (sergiodj) wrote :

Thanks for the MP, Bryce.

The very first thing I did was to run shellcheck on the script. I've been trying to keep all of these scripts shellcheck-free (except the "push-images.sh", which is an old script and might not even be needed anymore). Could you fix the shellcheck warnings/notices, please?

Now, about the feature itself: I like it. I think it's a nice addition to our swiss-knife kit. I have a few comments/questions on the code, let me know what you think.

c7fd886... by Bryce Harrington

software_versions: Fix shellcheck issues

Mostly changing the local declarations, and a few quoted variables.
There was also a typo in the num_software decremental.

f030a57... by Bryce Harrington

software-versions: Add mention of --format to usage()

fb76f34... by Bryce Harrington

software-versions: Omit prometheus and other snap-based packages

(For now anyway...)

328e350... by Bryce Harrington

software-versions.sh: Set $CODENAMES as read-only

Revision history for this message
Bryce Harrington (bryce) wrote :
Download full text (11.5 KiB)

Thanks for reviewing!

On Tue, Apr 13, 2021 at 01:21:05AM -0000, Sergio Durigan Junior wrote:
> Thanks for the MP, Bryce.
>
> The very first thing I did was to run shellcheck on the script. I've been trying to keep all of these scripts shellcheck-free (except the "push-images.sh", which is an old script and might not even be needed anymore). Could you fix the shellcheck warnings/notices, please?
>

Done. One of the shellcheck flags actually revealed a legit bug.

> Now, about the feature itself: I like it. I think it's a nice addition to our swiss-knife kit. I have a few comments/questions on the code, let me know what you think.
>
> Diff comments:
>
> > diff --git a/software-versions.sh b/software-versions.sh
> > new file mode 100755
> > index 0000000..895982a
> > --- /dev/null
> > +++ b/software-versions.sh
> > @@ -0,0 +1,273 @@
> > +#!/bin/bash
> > +# -*- coding: utf-8; mode: sh -*-
> > +#
> > +# software-versions.sh: Determine versions of packages installed in OCI images
> > +
> > +progname=$(basename "${0}")
> > +progdir=$(dirname "$(readlink -f "${0}")")
> > +
> > +# shellcheck disable=SC1090
> > +source "${progdir}/helpers/log.sh"
> > +
> > +###############
> > +### Options ###
> > +###############
> > +
> > +declare -a ARGS
> > +
> > +# Whether to pull latest versions of the images
> > +opt_report_format="text"
> > +
> > +## Explains how to use this command.
> > +usage() {
> > + cat > /dev/stderr <<EOF
> > +${progname} - Determine versions of packages installed in OCI images.
> > +
> > +This script can either be run stand-alone to display a text report or
> > +JSON data structure, or can be sourced into other bash scripts for its
> > +version functionality.
> > +
> > +Usage:
> > +
> > + ${progname} [options]
> > +
> > +Arguments:
> > + -h|--help Display this usage
>
> I think you can mention the "--format" option here.

thx

> > +EOF
> > +}
> > +
> > +## Parses command line arguments
> > +##
> > +## @param *: Command line to be parsed.
> > +parse_args() {
> > + ARGS=()
> > +
> > + # Transform long options to short ones
> > + for arg in "${@}"; do
> > + shift
> > + case "${arg}" in
> > + "--help") set -- "${@}" "-h" ;;
> > + "--format") set -- "${@}" "-F" ;;
> > + "--*") usage; exit 1 ;;
> > + *) set -- "${@}" "${arg}" ;;
> > + esac
> > + done
> > +
> > + # Parse short options
> > + while [ ${#} -gt 0 ]; do
> > + local OPTIND=1
> > + while getopts "hF:\?" opt "${@}"; do
> > + case "${opt}" in
> > + h | \?) usage; exit 0; ;;
> > + F) opt_report_format="${OPTARG}" ;;
> > + *) error "Unknown option '${opt}'" ;;
> > + esac
> > + done
> > + shift "$((OPTIND - 1))"
> > + ARGS+=("${1}")
> > + shift
> > + done
> > + return 0
> > +}
> > +
> > +
> > +###############################
> > +### Source Package Versions ###
> > +###############################
> > +
> > +## Converts a package version number to equivalent upstream version.
> > +## Strips off epoch, if any, and drops the debian version.
> > +##
> > +## @param v:...

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

Awesome, thanks Bryce. This version LGTM. +1.

review: Approve
Revision history for this message
Athos Ribeiro (athos-ribeiro) wrote :

The multi-arch-tagger script has its own README file describing some script dependencies.

Should we have something similar for this new script (i.e., somewhere documenting it needs distro-info installed) or maybe get it to fail gracefully in case the distro-info is not available in the current $PATH?

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

Athos, yes you're right, thanks. I'll include that task in the next MP for this script.

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..1bb3125
--- /dev/null
+++ b/software-versions.sh
@@ -0,0 +1,273 @@
1#!/bin/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
9# shellcheck disable=SC1090
10source "${progdir}/helpers/log.sh"
11
12###############
13### Options ###
14###############
15
16declare -a ARGS
17
18# Whether to pull latest versions of the images
19opt_report_format="text"
20
21## Explains how to use this command.
22usage() {
23 cat > /dev/stderr <<EOF
24${progname} - Determine versions of packages installed in OCI images.
25
26This script can either be run stand-alone to display a text report or
27JSON data structure, or can be sourced into other bash scripts for its
28version functionality.
29
30Usage:
31
32 ${progname} [options]
33
34Arguments:
35 -h|--help Display this usage
36 --format [text|json] Whether to print results as JSON or text (default)
37EOF
38}
39
40## Parses command line arguments
41##
42## @param *: Command line to be parsed.
43parse_args() {
44 ARGS=()
45
46 # Transform long options to short ones
47 for arg in "${@}"; do
48 shift
49 case "${arg}" in
50 "--help") set -- "${@}" "-h" ;;
51 "--format") set -- "${@}" "-F" ;;
52 "--*") usage; exit 1 ;;
53 *) set -- "${@}" "${arg}" ;;
54 esac
55 done
56
57 # Parse short options
58 while [ ${#} -gt 0 ]; do
59 local OPTIND=1
60 while getopts "hF:\?" opt "${@}"; do
61 case "${opt}" in
62 h | \?) usage; exit 0; ;;
63 F) opt_report_format="${OPTARG}" ;;
64 *) error "Unknown option '${opt}'" ;;
65 esac
66 done
67 shift "$((OPTIND - 1))"
68 ARGS+=("${1}")
69 shift
70 done
71 return 0
72}
73
74
75###############################
76### Source Package Versions ###
77###############################
78
79## Converts a package version number to equivalent upstream version.
80## Strips off epoch, if any, and drops the debian version.
81##
82## @param v: Package version number to convert
83upstream_version() {
84 local v="${1#*:}"
85 [ -n "${v}" ] || return 1
86
87 local v2="${v%-*}"
88 echo "${v2%+*}"
89}
90
91## Look up version of source or binary package in a given Ubuntu release.
92##
93## @param package_name: Name of the source or binary package to look up.
94## @param distro_codename: Ubuntu release name to check.
95## @param type: Lookup either "source" or "binary" packages.
96package_version() {
97 local package_name="${1}"
98 local distro_codename="${2}"
99 local type="${3:-source}"
100 [ -n "${package_name}" ] || return 1
101 [ -n "${type}" ] || return 1
102 [ -n "${distro_codename}" ] || return 1
103
104 local suites="${distro_codename}"
105 if [ "${distro_codename}" != "$(distro-info --devel)" ]; then
106 # Not a devel version, need to consider -updates and -security, too
107 suites="${distro_codename},${distro_codename}-updates,${distro_codename}-security"
108 fi
109
110 local source_or_binary
111 if [ "${type}" = "source" ]; then
112 source_or_binary="-a \"source\""
113 fi
114
115 rmadison \
116 "${source_or_binary}" \
117 -s "${suites}" \
118 "${package_name}" \
119 | cut -d\| -f2 \
120 | sort --version-sort \
121 | tail -n 1 \
122 | xargs
123}
124
125source_package() {
126 local template v M N
127 template="${1}"
128 v=( "${2//./ }" )
129 M="${v[0]}"
130 N="${v[1]}"
131
132 echo "${template}" | \
133 sed -e "s/{M}/${M}/g" | \
134 sed -e "s/{N}/${N}/g" | \
135 sed -e "s/{M.N}/${M}.${N}/g"
136}
137
138
139#########################
140### Software Versions ###
141#########################
142
143## Returns the M.N version from a package version.
144##
145## We define "software version" to mean the initial two values in the
146## upstream package version number, i.e. the 'major' and 'minor' version
147## numbers. Epoch numbers, debian revisions, alpha/beta indicators, and
148## other such components are excluded.
149##
150## @param str version: The package version to extract M.N from.
151## @stdout: The M.N version string.
152software_version() {
153 local version v version_major version_minor
154 version=$(upstream_version "${1}")
155
156 v=( "${version//./ }" )
157 version_major="${v[0]}"
158 version_minor="${v[1]}"
159
160 echo "${version_major}.${version_minor}"
161}
162
163############
164### Main ###
165############
166
167## Top level entry point for the 'software-versions' script
168##
169## Expects to be passed the command line parameter string.
170main() {
171 # Parse options
172 parse_args "${@}" \
173 || error "Could not process options"
174 set -- "${ARGS[@]}"
175
176 # Debian packages in focal
177 declare -A SOFTWARE=(
178 # Software Source Binary
179 [apache2]=" apache{M} apache2"
180 [memcached]=" memcached memcached"
181 [mysql]=" mysql-{M.N} mysql-server"
182 [nginx]=" nginx nginx"
183 [postgres]=" postgresql-{M} postgresql"
184 [redis]=" redis redis-server"
185 )
186
187 declare -ar CODENAMES=(
188 "focal"
189 "hirsute"
190 )
191
192 case "${opt_report_format}" in
193 "text")
194 # Generate text table report
195 printf "%-25s " "Software"
196 printf "%-50s " "${CODENAMES[@]::${#CODENAMES[@]}-1}"
197 printf "%s " "${CODENAMES[-1]}"
198 printf "\n"
199 for software_name in "${!SOFTWARE[@]}"; do
200 local RECORD=() source_package_template binary_package_name
201
202 source_package_template=$(echo "${SOFTWARE[${software_name}]}" | awk '{print $1}')
203 binary_package_name=$(echo "${SOFTWARE[${software_name}]}" | awk '{print $2}')
204
205 local codename
206 for codename in "${CODENAMES[@]}"; do
207 local binary_package_version software_version source_package version version_upstream
208 binary_package_version=$(package_version "${binary_package_name}" "${codename}" "binary" )
209 software_version=$(software_version "${binary_package_version}")
210 source_package=$(source_package "${source_package_template}" "${software_version}")
211 version=$(package_version "${source_package}" "${codename}" "source")
212 version_upstream=$(upstream_version "${version}")
213 RECORD+=("${version} (${version_upstream})")
214 done
215
216 printf "%-25s " "${software_name}:"
217 printf "%-50s " "${RECORD[@]::${#RECORD[@]}-1}"
218 printf "%s " "${RECORD[-1]}"
219 printf "\n"
220 done
221 ;;
222 "json")
223 # Dump data as JSON data
224 echo "["
225 local num_software=${#SOFTWARE[@]}
226 for software_name in "${!SOFTWARE[@]}"; do
227 local RECORD=() source_package_template binary_package_name
228 source_package_template=$(echo "${SOFTWARE[${software_name}]}" | awk '{print $1}')
229 binary_package_name=$(echo "${SOFTWARE[${software_name}]}" | awk '{print $2}')
230 echo " {"
231 echo " \"software_name\": \"${software_name}\","
232
233 local codename
234 local num_codenames=${#CODENAMES[@]}
235 for codename in "${CODENAMES[@]}"; do
236 local binary_package_version software_version source_package version version_upstream
237 binary_package_version=$(package_version "${binary_package_name}" "${codename}" "binary" )
238 software_version=$(software_version "${binary_package_version}")
239 source_package=$(source_package "${source_package_template}" "${software_version}")
240 version=$(package_version "${source_package}" "${codename}" "source")
241 version_upstream=$(upstream_version "${version}")
242 echo " \"${codename}\": {
243 \"source_package\": \"${source_package}\",
244 \"software_version\": \"${software_version}\",
245 \"version\": \"${version}\",
246 \"version_upstream\": \"${version_upstream}\""
247 if [ "${num_codenames}" = "1" ]; then
248 echo " }"
249 else
250 echo " },"
251 num_codenames=$(( num_codenames - 1 ))
252 fi
253 done
254 if [ "${num_software}" -eq 1 ]; then
255 echo " }"
256 else
257 echo " },"
258 num_software=$(( num_software - 1 ))
259 fi
260 done
261 echo "]"
262 ;;
263 "*")
264 error "Unknown report format '${opt_report_format}' (expected 'text')"
265 ;;
266 esac
267}
268
269# If we're being run directly (i.e. not sourced), then
270# print out the current software versions.
271if [[ "${0}" == "${BASH_SOURCE[0]}" ]]; then
272 main "${@}"
273fi

Subscribers

People subscribed via source and target branches

to all changes: