summaryrefslogtreecommitdiff
path: root/maint/cargo-crate-owners
blob: 5e2ade6efd005925f9ed3e6035775cc9beaf5eb1 (plain)
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