|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +set -iuo pipefail |
| 4 | +IFS=$'\n\t' |
| 5 | + |
| 6 | +readonly AUDITOR_ROLE='system:image-auditor' |
| 7 | + |
| 8 | +readonly USAGE="Usage: $(basename ${BASH_SOURCE[0]}) [OPTIONS] |
| 9 | +
|
| 10 | +It fetches manifests of images from OpenShift integrated registry forcing it to |
| 11 | +store the manifests on the local storage and remove them from etcd. This will |
| 12 | +work as long as PR https://github.com/openshift/origin/pull/11925 is compiled |
| 13 | +in the regisry binary. |
| 14 | +
|
| 15 | +The script doesn't make any changes unless -a flag is given. |
| 16 | +
|
| 17 | +See bug https://bugzilla.redhat.com/show_bug.cgi?id=1378180 for more details. |
| 18 | +
|
| 19 | +Options: |
| 20 | + -h Print this message and exit. |
| 21 | + -a Apply. Do the changes. |
| 22 | + -r <registry_url> Specify registry url to connect to. |
| 23 | + If not given, script will try to determine from |
| 24 | + cluster status. |
| 25 | + -s Registry is secured. |
| 26 | + -c <cacert> CA certificate for use with registry if the registry is |
| 27 | + secured. |
| 28 | + -t <token> Token to use to query the registry. |
| 29 | + Its user or service account must be able to |
| 30 | + get imagestreams/layers in all the namespaces. |
| 31 | + If not given, token of current user will be used. |
| 32 | + Run this command as cluster admin to give particular |
| 33 | + user enough rights to query the images: |
| 34 | +
|
| 35 | + \$ oadm policy add-cluster-role-to-user registry-viewer <user> |
| 36 | +
|
| 37 | + -f Force migration of externally managed images (those imported |
| 38 | + from remote registries). A migration attempt is done by |
| 39 | + default. If it fails (manifest cannot be stored on |
| 40 | + registry's storage because of missing dependencies), the |
| 41 | + manifest will be kept in image object (and in etcd). This |
| 42 | + option causes a removal of the manifest in any way. It |
| 43 | + will still be available only on the remote registry. If |
| 44 | + pullthrough feature is enabled, registry will serve such |
| 45 | + manifests from the external locations. |
| 46 | +
|
| 47 | + For this to work, the user must be an image auditor: |
| 48 | +
|
| 49 | + \$ oadm policy add-cluster-role-to-user ${AUDITOR_ROLE} <user> |
| 50 | +" |
| 51 | + |
| 52 | +registry_address="" |
| 53 | +secured=0 |
| 54 | +cacert="" |
| 55 | +token="" |
| 56 | +force_removal_of_manifests=0 |
| 57 | +dry_run=1 |
| 58 | + |
| 59 | +function get_docker_registry_url() { |
| 60 | + local service_ip ports |
| 61 | + local tmpl=$'{{.spec.clusterIP}}%{{range $i, $port := .spec.ports}}{{$port.targetPort}}@{{$port.port}},{{end}}' |
| 62 | + IFS=% read -r service_ip ports <<<"$(oc get -o go-template="${tmpl}" -n default svc/docker-registry)" |
| 63 | + if [[ -z "${service_ip:-}" ]]; then |
| 64 | + echo "failed to get service ip of svc/docker-registry" >&2 |
| 65 | + return 1 |
| 66 | + fi |
| 67 | + |
| 68 | + # first, try to get a port with targetPort == 5000 |
| 69 | + local port="$(echo "${ports}" | tr ',' '\n' | sed -n 's/^5000@\([0-9]\+\)/\1/p')" |
| 70 | + if [[ -z "${port:-}" ]]; then |
| 71 | + # if no such port, get the first port |
| 72 | + port=$(echo "${ports}" | sed 's/^[^@]\+@\([^,]\+\).*/\1/') |
| 73 | + fi |
| 74 | + if [[ -z "${port:-}" ]]; then |
| 75 | + echo "failed to get port of svc/docker-registry" >&2 |
| 76 | + return 1 |
| 77 | + fi |
| 78 | + echo "${service_ip}:${port}" |
| 79 | +} |
| 80 | + |
| 81 | +function check_permissions() { |
| 82 | + local authorized=1 |
| 83 | + local verb |
| 84 | + for verb in get list update; do |
| 85 | + if [[ "$(oc policy can-i "${verb}" images)" != yes ]]; then |
| 86 | + echo "The user isn't authorized to ${verb} images!" >&2 |
| 87 | + authorized=0 |
| 88 | + fi |
| 89 | + done |
| 90 | + if [[ "${authorized}" == 0 ]]; then |
| 91 | + echo "Ask your admin to give you permissions to work with images, e.g.:" >&2 |
| 92 | + echo " oadm policy add-cluster-role-to-user ${AUDITOR_ROLE} $(oc whoami)" >&2 |
| 93 | + return 1 |
| 94 | + fi |
| 95 | + |
| 96 | + if [[ "$(oc policy can-i get imagestreams/layers --token=${token})" != yes ]]; then |
| 97 | + echo "The registry user isn't authorized to get imagestreams/layers!" >&2 |
| 98 | + echo " oadm policy add-cluster-role-to-user registry-viewer <user>" >&2 |
| 99 | + return 1 |
| 100 | + fi |
| 101 | +} |
| 102 | + |
| 103 | +function manifest_removed() { |
| 104 | + local ref="$1" |
| 105 | + local tmpl_manifest_removed=$'{{if .dockerImageManifest}}present{{else}}removed{{end}},' |
| 106 | + tmpl_manifest_removed+=$'{{if .metadata.annotations}}' |
| 107 | + tmpl_manifest_removed+=$'{{if index .metadata.annotations "openshift.io/image.managed"}}' |
| 108 | + tmpl_manifest_removed+=$'managed{{else}}external{{end}}{{else}}external{{end}}\n' |
| 109 | + |
| 110 | + oc get image -o go-template="${tmpl_manifest_removed}" "${ref}" |
| 111 | +} |
| 112 | + |
| 113 | +function force_manifest_removal() { |
| 114 | + local reference="$1" |
| 115 | + |
| 116 | + echo "Forcibly removing manifest from external image ${reference}". |
| 117 | + if ! oc patch image "${reference}" -p 'dockerImageManifest: ""'; then |
| 118 | + echo "Failed to remove manifest from image ${reference}!" >&2 |
| 119 | + fi |
| 120 | + # config can be safely removed as well |
| 121 | + oc patch image "${reference}" -p 'dockerImageConfig: ""' >/dev/null 2>&1 || : |
| 122 | +} |
| 123 | + |
| 124 | +function migrate() { |
| 125 | + local tmpl_istags=$'{{range $isi, $is := .items}}{{range $tagname, $tag := $is.status.tags}}' |
| 126 | + tmpl_istags+=$'{{range $i, $item := $tag.items}}{{$is.metadata.name}}@{{$item.image}}\n' |
| 127 | + tmpl_istags+=$'{{end}}{{end}}{{end}}' |
| 128 | + |
| 129 | + declare -A processed_images |
| 130 | + local total=0 |
| 131 | + local to_migrate=0 |
| 132 | + local proj isimage isname reference removed managed |
| 133 | + |
| 134 | + for proj in $(oc get -o jsonpath=$'{range .items[*]}{.metadata.name}\n{end}' project | sort -u); do |
| 135 | + echo "Processing project '${proj}'" |
| 136 | + for isimage in $(oc get -n "${proj}" -o go-template="${tmpl_istags}" is 2>/dev/null); do |
| 137 | + IFS='@' read -r isname reference <<<"${isimage}" |
| 138 | + [[ -n "${processed_images[${reference}]+set}" ]] && continue |
| 139 | + processed_images[${reference}]=1 |
| 140 | + total="$((${total} + 1))" |
| 141 | + |
| 142 | + IFS=',' read -r removed managed <<<"$(manifest_removed "${reference}")" |
| 143 | + [[ "${removed}" == 'removed' || -z "${removed}" ]] && continue |
| 144 | + to_migrate="$((${to_migrate} + 1))" |
| 145 | + |
| 146 | + if [[ "${dry_run}" = 1 ]]; then |
| 147 | + echo "Would migrate ${managed} isimage '${proj}/${isname}@${reference}'" |
| 148 | + continue |
| 149 | + fi |
| 150 | + |
| 151 | + echo "Migrating ${managed} isimage '${proj}/${isname}@${reference}'" |
| 152 | + if ! curl --fail -k ${curlargs[@]-} -u "unused:${token}" -i -s \ |
| 153 | + "${url}/v2/${proj}/${isname}/manifests/${reference}" | \ |
| 154 | + sed -e '/^[[:space:]]*$/,$d' | grep '^HTTP'; then |
| 155 | + echo "Failed to fetch '${url}/v2/${proj}/${isname}/manifests/${reference}'!" |
| 156 | + fi |
| 157 | + |
| 158 | + if [[ "${force_removal_of_manifests}" == 1 && "${managed}" == 'external' ]]; then |
| 159 | + force_manifest_removal "${reference}" |
| 160 | + fi |
| 161 | + done |
| 162 | + done |
| 163 | + |
| 164 | + if [[ "${dry_run}" = 1 ]]; then |
| 165 | + printf '\nWould migrate %d out of %d images.\n' "${to_migrate}" "${total}" |
| 166 | + fi |
| 167 | +} |
| 168 | + |
| 169 | +while getopts 'hr:sc:t:fa' opt; do |
| 170 | + case "${opt}" in |
| 171 | + h) |
| 172 | + echo "${USAGE}"; |
| 173 | + exit 0 |
| 174 | + ;; |
| 175 | + a) |
| 176 | + dry_run=0 |
| 177 | + ;; |
| 178 | + s) |
| 179 | + secured=1 |
| 180 | + ;; |
| 181 | + c) |
| 182 | + cacert="${OPTARG}" |
| 183 | + ;; |
| 184 | + r) |
| 185 | + registry_address="${OPTARG}" |
| 186 | + ;; |
| 187 | + t) |
| 188 | + token="${OPTARG}" |
| 189 | + ;; |
| 190 | + f) |
| 191 | + force_removal_of_manifests=1 |
| 192 | + ;; |
| 193 | + *) |
| 194 | + echo "${USAGE}" >&2 |
| 195 | + exit 1 |
| 196 | + ;; |
| 197 | + esac |
| 198 | +done |
| 199 | + |
| 200 | +if [[ -z "${registry_address:-}" ]]; then |
| 201 | + registry_address="$(get_docker_registry_url)" |
| 202 | +fi |
| 203 | + |
| 204 | +case "${registry_address},${secured}" in |
| 205 | + https://*) |
| 206 | + secured=1 |
| 207 | + url="${registry_address}" |
| 208 | + ;; |
| 209 | + http://*) |
| 210 | + secured=0 |
| 211 | + url="${registry_address}" |
| 212 | + ;; |
| 213 | + *,1) |
| 214 | + url="https://${registry_address}" |
| 215 | + ;; |
| 216 | + *,0) |
| 217 | + url="http://${registry_address}" |
| 218 | + ;; |
| 219 | +esac |
| 220 | + |
| 221 | +curlargs=() |
| 222 | +if [[ -n "${cacert}" ]]; then |
| 223 | + curlargs+=( "--cacert" "${cacert}" ) |
| 224 | +fi |
| 225 | + |
| 226 | +if [[ -z "${token:-}" ]]; then |
| 227 | + token="$(oc whoami -t)" |
| 228 | +fi |
| 229 | + |
| 230 | +if [[ -z "${token:-}" ]]; then |
| 231 | + echo "Please, provide a token of user authorized to get imagestreams/layers in all namespaces." >&2 |
| 232 | + exit 1 |
| 233 | +fi |
| 234 | + |
| 235 | +if ! curl --fail ${curlargs[@]-} --max-time 15 "${url}/healthz"; then |
| 236 | + echo "Please, provide endpoint of integrated docker registry." >&2 |
| 237 | + exit 1 |
| 238 | +fi |
| 239 | + |
| 240 | +check_permissions || exit 1 |
| 241 | +migrate |
0 commit comments