1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
|
#!/usr/bin/env bash
#
# usage:
# maint/cargo-crate-owners
#
# Lists the ownerships of all published crates to stderr,
# and checks that they're all the same.
#
# Exit status is
# 0 all crates have the same owners
# 2 some crates have varying own ers
# other trouble
#
# crates which are mentioned in the workspace, but which have never been published,
# are ignored.
#
#
# Override facility
#
# Sometimes a crate ownership invitation can remain unaccepted for reasons
# relating to the personal circumstances of the prospective owner.
# We have a facility for avoiding CI failure in this situation, without
# exposing Personally Identifying Information more widely than needed.
#
# To use this facility, in gitlab CI, create a "CI/CD variable"
# with the following properties:
# - Type: File
# - Environments: all (default)
# - Visibility: Visible (default)
# - Flags: [ ] Protect (default)
# [ ] Expand variable reference (changed)
# - Key `MAINT_CARGO_CRATE_OWNERS_UNTIL`
# - Value, a multi-line string as described below
# This causes gitlab to set an environment variable MAINT_CARGO_CRATE_OWNERS_UNTIL
# to the name of a file, containing the multi-line string.
#
# The file contents is in this form:
# v1 YYYY-MM-DD
# crates.io-username
# crates.io-username
# ...
#
# The file (the value) may also be empty, in which case it will be ignored.
# (This is to allow you to preserve the variable properties,
# while remooving the actual data.)
#
# `v1` is a fixed string, to allow future evolution of this protocol.
# Everything on a line after a # is a comment and is ignored.
#
# YYYY-MM-DD is an expiry date for this data.
# After this date, this script will start to unconditionally fail!
# You should set it to the time when you expect to be able to remove the exception,
# and then this script will help enforce that the exception is removed.
#
# Every following line should be a username.
# That user will be treated as if they are an owner of every crate,
# regardless of whether they really are.
set -e
set -o pipefail
maint=maint
# shellcheck source=maint/crates-io-utils.sh
source "$maint"/crates-io-utils.sh
crates=$("$maint"/list_crates)
if [ $# != 0 ]; then fail 'bad usage: no arguments allowed'; fi
# Reads the file named by MAINT_CARGO_CRATE_OWNERS_UNTIL (see spec above),
# and digests it into "$tmp"//override.json, which is a sequence of strings
# being the usernames to pretend every crate has as owner.
read_owners_override () {
: >"$tmp"/override.json
if [ "$MAINT_CARGO_CRATE_OWNERS_UNTIL" = '' ]; then return; fi
# `read` gives status 1 on missing final newline, which would result in
# us perhaps ignoring the last line if we just read it directly!
# But, perl will happily read such a file, and we can then use it fix it up.
# Also, we use perl to do comment filtering.
perl -pe 's{\#.*}{}; s/\n*$/\n/' <"$MAINT_CARGO_CRATE_OWNERS_UNTIL" >"$tmp"/override-filtered
exec 3<"$tmp"/override-filtered
if ! read -r <&3 version override_expiry; then
# We couldn't read even one line. The file is empty: ignore it.
return
fi
case "$version" in
v1) ;;
*) fail "MAINT_CARGO_CRATE_OWNERS_UNTIL: expected version v1, got $version" ;;
esac
case "$override_expiry" in
[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) ;;
*) fail "MAINT_CARGO_CRATE_OWNERS_UNTIL: bad expiry" ;;
esac
expiry_tt=$(date -d "$override_expiry" +%s)
current_tt=$(date -u +%s)
if [ "$current_tt" -gt "$expiry_tt" ]; then
fail "MAINT_CARGO_CRATE_OWNERS_UNTIL: override data expired! (expiry date $override_expiry)"
fi
while read -r <&3 user; do
if [ "$user" = '' ]; then continue; fi
printf "%s" "$user" | jq --slurp -R . >>"$tmp"//override.json
done
}
tmp_trap_exit_setup
read_owners_override
override_changed=0
for p in $crates; do
printf "checking owners of %-40s " "$p"
crates_io_api_call "v1/crates/$p/owners" .users "$tmp/p,$p.json"
case "$http_code" in
404)
echo "unpublished"
continue
;;
200)
;;
*)
fail 'internal error'
;;
esac
jq -S '.users[].login' <"$tmp/p,$p.json" >"$tmp/owners-1,$p.json"
cat "$tmp"/override.json >>"$tmp/owners-1,$p.json"
jq -s 'unique | .[]' <"$tmp/owners-1,$p.json" >>"$tmp/owners,$p.json"
set +e
cmp -s "$tmp"/owners{-1,},"$p.json"
rc=$?
set -e
case "$rc" in
0) ;;
1) override_changed=$(( override_changed + 1 )) ;;
*) fail "cmp failed" ;;
esac
hash=$(sha256sum <"$tmp/owners,$p.json")
hash=${hash%% *}
cp "$tmp/owners,$p.json" "$tmp/byhash.$hash.owners"
printf '%s\n' "$p" >>"$tmp/byhash.$hash.packages"
n_owners=$(jq <"$tmp/owners,$p.json" 1 | wc -l)
n_packages=$(wc -l <"$tmp/byhash.$hash.packages")
printf '%d owners (group size: %d)\n' "$n_owners" "$n_packages"
done
wc -l "$tmp"/byhash.*.packages | grep -v ' total$' | sort -rn >"$tmp/list"
n_groups=$(wc -l <"$tmp/list")
if [ "$n_groups" = 1 ]; then
echo
echo 'all ownerships are identical:'
echo
status=0
else
cat <<END
ownerships of published crates vary!
$n_groups different sets of owners
END
status=2
fi
# in case we want to redirect the report at some future point
exec 4>&2
exec 3<"$tmp/list"
# shellcheck disable=SC2162 # we don't need -r, it has no backslashes
while read <&3 n_packages packages_file; do
owners_file="${packages_file%.packages}.owners"
n_packages=$(wc -l <"$packages_file")
echo "$n_packages package(s) have the following owner(s):" >&4
sed 's/^/\t/' "$owners_file" | cat -v >&4
echo " those are owner(s) of the following package(s):" >&4
sed 's/^/\t/' "$packages_file" >&4
echo >&4
done
if [ "x$override_expiry" != x ]; then
cat <<END
NB, used MAINT_CARGO_CRATE_OWNERS_UNTIL, expiry $override_expiry:
Pretended owners added to $override_changed crates.
END
fi
tmp_trap_exit_finish_status $status
|