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