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

Subscribers

People subscribed via source and target branches

to all changes: