Push all images to simulated Docker registry only
[oom/offline-installer.git] / build / build_nexus_blob.sh
1 #! /usr/bin/env bash
2
3 #   COPYRIGHT NOTICE STARTS HERE
4 #
5 #   Copyright 2018-2020© Samsung Electronics Co., Ltd.
6 #
7 #   Licensed under the Apache License, Version 2.0 (the "License");
8 #   you may not use this file except in compliance with the License.
9 #   You may obtain a copy of the License at
10 #
11 #       http://www.apache.org/licenses/LICENSE-2.0
12 #
13 #   Unless required by applicable law or agreed to in writing, software
14 #   distributed under the License is distributed on an "AS IS" BASIS,
15 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 #   See the License for the specific language governing permissions and
17 #   limitations under the License.
18 #
19 #   COPYRIGHT NOTICE ENDS HERE
20
21 ### This script prepares Nexus repositories data blobs for ONAP
22
23 ## The script requires following dependencies are installed: nodejs, jq, docker, twine, expect
24 ## All required resources are expected in the upper directory created during
25 ## download procedure as DATA_DIR or in the directory given as --input-directory
26 ## All lists used must be in project data_lists directory or in the directory given
27 ## as --resource-list-directory
28
29 # Fail fast settings
30 set -e
31
32 TIMESTAMP="date +'%Y-%m-%d_%H-%M-%S'"
33 SCRIPT_LOG="/tmp/$(basename $0)_$(eval ${TIMESTAMP}).log"
34
35 # Log everything
36 exec &> >(tee -a "${SCRIPT_LOG}")
37
38 # Nexus repository properties
39 NEXUS_DOMAIN="nexus"
40 NEXUS_PORT="8081"
41 NEXUS_DOCKER_PORT="8082"
42 DEFAULT_REGISTRY="docker.io"
43
44 # Nexus repository credentials
45 NEXUS_USERNAME=admin
46 NEXUS_PASSWORD=admin123
47 NEXUS_EMAIL=admin@example.org
48
49 # Setting paths
50 LOCAL_PATH="$(readlink -f $(dirname ${0}))"
51
52 # Defaults
53 DOCKER_LOAD="false"
54 NPM_PUSH="false"
55 PYPI_PUSH="false"
56 DATA_DIR="$(realpath ${LOCAL_PATH}/../../resources)"
57 NEXUS_DATA_DIR="${DATA_DIR}/nexus_data"
58 LISTS_DIR="${LOCAL_PATH}/data_lists"
59
60 # Required dependencies
61 COMMANDS=(jq docker)
62
63 usage () {
64     echo "
65     Usage: $(basename $0) [OPTION...] [FILE]...
66
67     This script prepares Nexus repositories data blobs for ONAP
68
69     Following dependencies are required: nodejs, jq, docker, twine, expect
70     By default, without any lists or dirs provided, the resources are expected as downloaded
71     during download process and default lists will be used to build the Nexus blob in the same
72     resources dir
73
74     Examples:
75         $(basename $0) --input-directory </path/to/downloaded/files/dir> -ld --output-directory
76            </path/to/output/dir> --resource-list-directory </path/to/dir/with/resource/list>
77            # Docker images, npms and pypi packages will be loaded from specified directory
78            # and the blob is created
79         $(basename $0) -d </path/to/docker/images/list> -d </path/to/another/docker/images/list>
80         -n </path/to/npm/list> -p </path/to/pip/list>
81            # Docker images, npms and pypi packages will be pushed to Nexus based and provided data
82            # lists (multiple lists can be provided)
83
84      -d  | --docker                     use specific list of docker images to be pushed into Nexus
85                                         (in case of -ld used, this list will be used for loading of
86                                         the images)
87      -h  | --help                       print this usage
88      -i  | --input-directory            use specific directory containing resources needed to
89                                         create nexus blob
90                                         The structure of this directory must organized as described
91                                         in build guide
92      -ld | --load-docker-images         load docker images from resource directory
93      -n  | --npm                        list of npm packages to be pushed into Nexus
94      -o  | --output-directory           use specific directory for the target blob
95      -p  | --pypi                       use specific list of pypi packages to be pushed into Nexus
96      -rl | --resource-list-directory    use specific directory with docker, pypi and npm lists
97      -c  | --container-name             use specific Nexus docker container name
98     "
99     exit 1
100 }
101
102 publish_ports () {
103     for REGISTRY in $(sed -n '/\.[^/].*\//p' ${1} | sed -e 's/\/.*$//' | sort -u | grep -v ${DEFAULT_REGISTRY} || true) ${NEXUS_PORT}; do
104         if [[ ${REGISTRY} != *":"* ]]; then
105             if [[ ${PUBLISHED_PORTS} != *"80:${NEXUS_DOCKER_PORT}"* ]]; then
106                 PUBLISHED_PORTS="${PUBLISHED_PORTS} -p 80:${NEXUS_DOCKER_PORT}"
107             fi
108         else
109             REGISTRY_PORT="$(sed 's/^.*\:\([[:digit:]]*\)$/\1/' <<< ${REGISTRY})"
110             if [[ ${PUBLISHED_PORTS} != *"${REGISTRY_PORT}:${NEXUS_DOCKER_PORT}"* ]]; then
111                 PUBLISHED_PORTS="${PUBLISHED_PORTS} -p ${REGISTRY_PORT}:${NEXUS_DOCKER_PORT}"
112             fi
113         fi
114     done
115 }
116
117 simulated_hosts () {
118     SIMUL_HOSTS=($(sed -n '/\.[^/].*\//p' ${1} | sed -e 's/\/.*$// ; s/:.*$//' | sort -u | grep -v ${DEFAULT_REGISTRY} || true ) ${NEXUS_DOMAIN})
119     for HOST in "${SIMUL_HOSTS[@]}"; do
120         if ! grep -wq ${HOST} /etc/hosts; then
121             echo "127.0.0.1 ${HOST}" >> /etc/hosts
122         fi
123     done
124 }
125
126 load_docker_images () {
127     for ARCHIVE in $(sed $'s/\r// ; /^#/d ; s/\:/\_/g ; s/\//\_/g ; s/$/\.tar/g' ${1} | awk '{ print $1 }'); do
128         docker load -i ${NXS_SRC_DOCKER_IMG_DIR}/${ARCHIVE}
129     done
130 }
131
132 prepare_npm () {
133     # Configure NPM registry to our Nexus repository
134     echo "Configure NPM registry to ${NPM_REGISTRY}"
135     npm config set registry "${NPM_REGISTRY}"
136
137     # Login to NPM registry
138     /usr/bin/expect <<- EOF
139         spawn npm login
140         expect "Username:"
141         send "${NEXUS_USERNAME}\n"
142         expect "Password:"
143         send "${NEXUS_PASSWORD}\n"
144         expect Email:
145         send "${NEXUS_EMAIL}\n"
146         expect eof
147         EOF
148 }
149
150 patch_npm () {
151     # Patch problematic package
152     PATCHED_NPM="$(grep tsscmp ${1} | sed $'s/\r// ; s/\\@/\-/ ; s/$/\.tgz/')"
153     if [[ ! -z "${PATCHED_NPM}" ]] && ! zgrep -aq "${NPM_REGISTRY}" "${PATCHED_NPM}" 2>/dev/null
154     then
155         tar xzf "${PATCHED_NPM}"
156         rm -f "${PATCHED_NPM}"
157         sed -i 's|\"registry\":\ \".*\"|\"registry\":\ \"'"${NPM_REGISTRY}"'\"|g' package/package.json
158         tar -zcf "${PATCHED_NPM}" package
159         rm -rf package
160     fi
161 }
162
163 push_npm () {
164     for ARCHIVE in $(sed $'s/\r// ; s/\\@/\-/g ; s/$/\.tgz/g' ${1}); do
165         npm publish --access public ${ARCHIVE} > /dev/null
166         echo "NPM ${ARCHIVE} pushed to Nexus"
167     done
168 }
169
170 push_pip () {
171     for PACKAGE in $(sed $'s/\r//; s/==/-/' ${1}); do
172         twine upload -u "${NEXUS_USERNAME}" -p "${NEXUS_PASSWORD}" --repository-url ${PYPI_REGISTRY} ${PACKAGE}* > /dev/null
173         echo "PYPI ${PACKAGE} pushed to Nexus"
174     done
175 }
176
177 docker_login () {
178     if ! grep -wqs ${DOCKER_REGISTRY} ~/.docker/config.json; then
179         echo "Docker login to ${DOCKER_REGISTRY}"
180         echo -n "${NEXUS_PASSWORD}" | docker login -u "${NEXUS_USERNAME}" --password-stdin ${DOCKER_REGISTRY} > /dev/null
181     fi
182 }
183
184 push_docker () {
185     for IMAGE in $(sed $'s/\r// ; /^#/d' ${1} | awk '{ print $1 }'); do
186         PUSH=""
187         if [[ ${IMAGE} != *"/"* ]]; then
188             PUSH="${DOCKER_REGISTRY}/library/${IMAGE}"
189         elif [[ ${IMAGE} == *"${DEFAULT_REGISTRY}"* ]]; then
190             if [[ ${IMAGE} == *"/"*"/"* ]]; then
191                 PUSH="$(sed 's/'"${DEFAULT_REGISTRY}"'/'"${DOCKER_REGISTRY}"'/' <<< ${IMAGE})"
192             else
193                 PUSH="$(sed 's/'"${DEFAULT_REGISTRY}"'/'"${DOCKER_REGISTRY}"'\/library/' <<< ${IMAGE})"
194             fi
195         elif [[ -z $(sed -n '/\.[^/].*\//p' <<< ${IMAGE}) ]]; then
196             PUSH="${DOCKER_REGISTRY}/${IMAGE}"
197         else
198             # substitute all host names with $DOCKER_REGISTRY
199             repo_host=$(sed -e 's/\/.*$//' <<< ${IMAGE})
200             PUSH="$(sed -e 's/'"${repo_host}"'/'"${DOCKER_REGISTRY}"'/' <<< ${IMAGE})"
201         fi
202         docker tag ${IMAGE} ${PUSH}
203         docker push ${PUSH}
204         # Remove created tag
205         docker rmi ${PUSH}
206         echo "${IMAGE} pushed as ${PUSH} to Nexus"
207     done
208 }
209
210 validate_container_name () {
211     # Verify $1 is a valid hostname
212     if ! echo "${1}" | egrep -q "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$";
213     then
214         echo "ERROR: ${1} is not a valid name!"
215         exit 1;
216     fi
217 }
218
219 while [ "${1}" != "" ]; do
220     case ${1} in
221         -d | --docker )                    shift
222                                            NXS_DOCKER_IMG_LISTS+=("$(realpath ${1})")
223                                            ;;
224         -i | --input-directory )           shift
225                                            DATA_DIR="$(realpath ${1})"
226                                            ;;
227         -ld | --load-docker-images )       DOCKER_LOAD="true"
228                                            ;;
229         -n | --npm )                       NPM_PUSH="true"
230                                            COMMANDS+=(expect npm)
231                                            shift
232                                            NXS_NPM_LISTS+=("$(realpath ${1})")
233                                            ;;
234         -c | --container-name )            shift
235                                            validate_container_name "${1}"
236                                            NEXUS_DOMAIN="${1}"
237                                            ;;
238         -o | --output-directory )          shift
239                                            NEXUS_DATA_DIR="$(realpath ${1})"
240                                            ;;
241         -p | --pypi )                      PYPI_PUSH="true"
242                                            COMMANDS+=(twine)
243                                            shift
244                                            NXS_PYPI_LISTS+=("$(realpath ${1})")
245                                            ;;
246         -rl | --resource-list-directory )  shift
247                                            LISTS_DIR="$(realpath ${1})"
248                                            ;;
249         -h | --help )                      usage
250                                            ;;
251         *)                                 usage
252                                            ;;
253     esac
254     shift
255 done
256
257 # Verify all dependencies are available in PATH
258 FAILED_COMMANDS=()
259 for cmd in ${COMMANDS[*]}; do
260     command -v $cmd >/dev/null 2>&1 || FAILED_COMMANDS+=($cmd)
261 done
262
263 if [ ${#FAILED_COMMANDS[*]} -gt 0 ]; then
264     echo "Following commands where not found in PATH and are required:"
265     echo ${FAILED_COMMANDS[*]}
266     echo "Aborting."
267     exit 1
268 fi
269
270 # Nexus repository locations
271 NPM_REGISTRY="http://${NEXUS_DOMAIN}:${NEXUS_PORT}/repository/npm-private/"
272 PYPI_REGISTRY="http://${NEXUS_DOMAIN}:${NEXUS_PORT}/repository/pypi-private/"
273 DOCKER_REGISTRY="${NEXUS_DOMAIN}:${NEXUS_DOCKER_PORT}"
274
275 # Setup directories with resources for docker, npm and pypi
276 NXS_SRC_DOCKER_IMG_DIR="${DATA_DIR}/offline_data/docker_images_for_nexus"
277 NXS_SRC_NPM_DIR="${DATA_DIR}/offline_data/npm_tar"
278 NXS_SRC_PYPI_DIR="${DATA_DIR}/offline_data/pypi"
279
280 # Setup specific resources lists
281 NXS_INFRA_LIST="${LISTS_DIR}/infra_docker_images.list"
282 NXS_DOCKER_IMG_LIST="${LISTS_DIR}/onap_docker_images.list"
283 NXS_RKE_DOCKER_IMG_LIST="${LISTS_DIR}/rke_docker_images.list"
284 NXS_K8S_DOCKER_IMG_LIST="${LISTS_DIR}/k8s_docker_images.list"
285
286 # Setup Nexus image used for build and install infra
287 NEXUS_IMAGE="$(grep sonatype/nexus3 ${NXS_INFRA_LIST})"
288 NEXUS_IMAGE_TAR="${DATA_DIR}/offline_data/docker_images_infra/$(sed 's/\//\_/ ; s/$/\.tar/ ; s/\:/\_/' <<< ${NEXUS_IMAGE})"
289
290 # Set default lists if nothing specific defined by user
291 if [ ${#NXS_DOCKER_IMG_LISTS[@]} -eq 0 ]; then
292     NXS_DOCKER_IMG_LISTS=("${NXS_DOCKER_IMG_LIST}" "${NXS_RKE_DOCKER_IMG_LIST}" "${NXS_K8S_DOCKER_IMG_LIST}")
293 fi
294
295 # Backup /etc/hosts
296 HOSTS_BACKUP="$(eval ${TIMESTAMP}_hosts.bk)"
297 cp /etc/hosts /etc/${HOSTS_BACKUP}
298
299 # Backup the current docker registry settings
300 if [ -f ~/.docker/config.json ]; then
301     DOCKER_CONF_BACKUP="$(eval ${TIMESTAMP}_config.json.bk)"
302     mv ~/.docker/config.json ~/.docker/${DOCKER_CONF_BACKUP}
303 fi
304
305 # Setup default ports published to host as docker registry
306 PUBLISHED_PORTS="-p ${NEXUS_PORT}:${NEXUS_PORT} -p ${NEXUS_DOCKER_PORT}:${NEXUS_DOCKER_PORT}"
307
308 # Setup additional ports published to host based on simulated docker registries
309 # Setup simulated domain names to be able to push all to private Nexus repository
310 for DOCKER_IMG_LIST in "${NXS_DOCKER_IMG_LISTS[@]}"; do
311     publish_ports "${DOCKER_IMG_LIST}"
312     simulated_hosts "${DOCKER_IMG_LIST}"
313 done
314
315 # Nexus repository configuration setup
316 NEXUS_CONFIG_GROOVY='import org.sonatype.nexus.security.realm.RealmManager
317 import org.sonatype.nexus.repository.attributes.AttributesFacet
318 import org.sonatype.nexus.security.user.UserManager
319 import org.sonatype.nexus.repository.manager.RepositoryManager
320 import org.sonatype.nexus.security.user.UserNotFoundException
321 /* Use the container to look up some services. */
322 realmManager = container.lookup(RealmManager.class)
323 userManager = container.lookup(UserManager.class, "default") //default user manager
324 repositoryManager = container.lookup(RepositoryManager.class)
325 /* Managers are used when scripting api cannot. Note that scripting api can only create mostly, and that creation methods return objects of created entities. */
326 /* Perform cleanup by removing all repos and users. Realms do not need to be re-disabled, admin and anonymous user will not be removed. */
327 userManager.listUserIds().each({ id ->
328     if (id != "anonymous" && id != "admin")
329         userManager.deleteUser(id)
330 })
331 repositoryManager.browse().each {
332     repositoryManager.delete(it.getName())
333 }
334 /* Add bearer token realms at the end of realm lists... */
335 realmManager.enableRealm("NpmToken")
336 realmManager.enableRealm("DockerToken")
337 realmManager.enableRealm("PypiToken")
338 /* Create the docker user. */
339 security.addUser("docker", "docker", "docker", "docker@example.com", true, "docker", ["nx-anonymous"])
340 /* Create docker, npm and pypi repositories. Their default configuration should be compliant with our requirements, except the docker registry creation. */
341 repository.createNpmHosted("npm-private")
342 repository.createPyPiHosted("pypi-private")
343 def r = repository.createDockerHosted("onap", 8082, 0)
344 /* force basic authentication true by default, must set to false for docker repo. */
345 conf=r.getConfiguration()
346 conf.attributes("docker").set("forceBasicAuth", false)
347 repositoryManager.update(conf)'
348
349 # Prepare the Nexus configuration
350 NEXUS_CONFIG=$(echo "${NEXUS_CONFIG_GROOVY}" | jq -Rsc  '{"name":"configure", "type":"groovy", "content":.}')
351
352 #################################
353 # Docker repository preparation #
354 #################################
355
356 if [ "${DOCKER_LOAD}" == "true" ]; then
357     # Load predefined Nexus image
358     docker load -i ${NEXUS_IMAGE_TAR}
359     # Load all necessary images
360     for DOCKER_IMG_LIST in "${NXS_DOCKER_IMG_LISTS[@]}"; do
361         load_docker_images "${DOCKER_IMG_LIST}"
362     done
363 fi
364
365 ################################
366 # Nexus repository preparation #
367 ################################
368
369 # Prepare nexus-data directory
370 if [ -d ${NEXUS_DATA_DIR} ]; then
371    if [ "$(docker ps -q -f name="${NEXUS_DOMAIN}")" ]; then
372        echo "Removing container ${NEXUS_DOMAIN}"
373        docker rm -f $(docker ps -aq -f name="${NEXUS_DOMAIN}")
374    fi
375    pushd ${NEXUS_DATA_DIR}/..
376    NXS_BACKUP="$(eval ${TIMESTAMP})_$(basename ${NEXUS_DATA_DIR})_bk"
377    mv ${NEXUS_DATA_DIR} "${NXS_BACKUP}"
378    echo "${NEXUS_DATA_DIR} already exists - backing up to ${NXS_BACKUP}"
379    popd
380 fi
381
382 mkdir -p ${NEXUS_DATA_DIR}
383 chown 200:200 ${NEXUS_DATA_DIR}
384 chmod 777 ${NEXUS_DATA_DIR}
385
386 # Save Nexus version to prevent/catch data incompatibility
387 # Adding commit informations to have link to data from which the blob was built
388 cat >> ${NEXUS_DATA_DIR}/nexus.ver << INFO
389 nexus_image=$(docker image ls ${NEXUS_IMAGE} --no-trunc --format "{{.Repository}}:{{.Tag}}\nnexus_image_digest={{.ID}}")
390 $(for INDEX in ${!NXS_DOCKER_IMG_LISTS[@]}; do printf 'used_image_list%s=%s\n' "$INDEX" "$(sed 's/^.*\/\(.*\)$/\1/' <<< ${NXS_DOCKER_IMG_LISTS[$INDEX]})"; done)
391 $(sed -n 's/^.*OOM\ commit\ /oom_repo_commit=/p' ${NXS_DOCKER_IMG_LISTS[@]})
392 installer_repo_commit=$(git --git-dir="${LOCAL_PATH}/../.git" rev-parse HEAD)
393 INFO
394
395 # Start the Nexus
396 NEXUS_CONT_ID=$(docker run -d --rm -v ${NEXUS_DATA_DIR}:/nexus-data:rw --name ${NEXUS_DOMAIN} ${PUBLISHED_PORTS} ${NEXUS_IMAGE})
397 echo "Waiting for Nexus to fully start"
398 until curl -su ${NEXUS_USERNAME}:${NEXUS_PASSWORD} http://${NEXUS_DOMAIN}:${NEXUS_PORT}/service/metrics/healthcheck | grep '"healthy":true' > /dev/null ; do
399     printf "."
400     sleep 3
401 done
402 echo -e "\nNexus started"
403
404 # Configure the nexus repository
405 curl -sX POST --header 'Content-Type: application/json' --data-binary "${NEXUS_CONFIG}" http://${NEXUS_USERNAME}:${NEXUS_PASSWORD}@${NEXUS_DOMAIN}:${NEXUS_PORT}/service/rest/v1/script
406 curl -sX POST --header "Content-Type: text/plain" http://${NEXUS_USERNAME}:${NEXUS_PASSWORD}@${NEXUS_DOMAIN}:${NEXUS_PORT}/service/rest/v1/script/configure/run > /dev/null
407
408 ###########################
409 # Populate NPM repository #
410 ###########################
411 if [ $NPM_PUSH == "true" ]; then
412     prepare_npm
413     pushd ${NXS_SRC_NPM_DIR}
414     for NPM_LIST in "${NXS_NPM_LISTS[@]}"; do
415         patch_npm "${NPM_LIST}"
416         push_npm "${NPM_LIST}"
417     done
418     popd
419     # Return default settings
420     npm logout
421     npm config set registry "https://registry.npmjs.org"
422 fi
423
424 ###############################
425 ##  Populate PyPi repository  #
426 ###############################
427 if [ $PYPI_PUSH == "true" ]; then
428     pushd ${NXS_SRC_PYPI_DIR}
429     for PYPI_LIST in "${NXS_PYPI_LISTS[@]}"; do
430         push_pip "${PYPI_LIST}"
431     done
432     popd
433 fi
434
435 ###############################
436 ## Populate Docker repository #
437 ###############################
438
439 # Login to docker registry simulated by Nexus container
440 # Push images to private nexus based on the lists
441 # All images need to be tagged to simulated registry
442 # and those without defined repository in tag use default repository 'library'
443 docker_login
444 for DOCKER_IMG_LIST in "${NXS_DOCKER_IMG_LISTS[@]}"; do
445     push_docker "${DOCKER_IMG_LIST}"
446 done
447
448 ##############################
449 # Stop the Nexus and cleanup #
450 ##############################
451
452 echo "Stopping Nexus and returning backups"
453
454 # Stop the Nexus
455 docker stop ${NEXUS_CONT_ID} > /dev/null
456
457 # Return backed up configuration files
458 mv -f "/etc/${HOSTS_BACKUP}" /etc/hosts
459
460 if [ -f ~/.docker/${DOCKER_CONF_BACKUP} ]; then
461     mv -f ~/.docker/${DOCKER_CONF_BACKUP} ~/.docker/config.json
462 fi
463
464 echo "Nexus blob is built"
465 exit 0