Merge "[GENERAL] Add Andreas Geissler as committer."
[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 NXS_PROMETHEUS_DOCKER_IMG_LIST="${LISTS_DIR}/kube_prometheus_stack_docker_images.list"
271
272 # Setup Nexus image used for build and install infra
273 NEXUS_IMAGE="$(grep sonatype/nexus3 ${NXS_INFRA_LIST})"
274 NEXUS_IMAGE_TAR="${DATA_DIR}/offline_data/docker_images_infra/$(sed 's/\//\_/ ; s/$/\.tar/ ; s/\:/\_/' <<< ${NEXUS_IMAGE})"
275
276 # Set default lists if nothing specific defined by user
277 if [ ${#NXS_DOCKER_IMG_LISTS[@]} -eq 0 ]; then
278     NXS_DOCKER_IMG_LISTS=("${NXS_DOCKER_IMG_LIST}" "${NXS_RKE_DOCKER_IMG_LIST}" "${NXS_K8S_DOCKER_IMG_LIST}" "${NXS_PROMETHEUS_DOCKER_IMG_LIST}")
279 fi
280
281 # Create Docker client config dir
282 DOCKER_CONFIG_DIR=$(mktemp -p /tmp -d .docker.XXXXXXXX)
283
284 # Setup default ports published to host as docker registry
285 PUBLISHED_PORTS="-p ${NEXUS_PORT}:${NEXUS_EXPOSED_PORT} -p ${NEXUS_DOCKER_PORT}:${NEXUS_DOCKER_EXPOSED_PORT}"
286
287 # Nexus repository configuration setup
288 NEXUS_CONFIG_GROOVY='import org.sonatype.nexus.security.realm.RealmManager
289 import org.sonatype.nexus.repository.attributes.AttributesFacet
290 import org.sonatype.nexus.security.user.UserManager
291 import org.sonatype.nexus.repository.manager.RepositoryManager
292 import org.sonatype.nexus.security.user.UserNotFoundException
293 /* Use the container to look up some services. */
294 realmManager = container.lookup(RealmManager.class)
295 userManager = container.lookup(UserManager.class, "default") //default user manager
296 repositoryManager = container.lookup(RepositoryManager.class)
297 /* Managers are used when scripting api cannot. Note that scripting api can only create mostly, and that creation methods return objects of created entities. */
298 /* Perform cleanup by removing all repos and users. Realms do not need to be re-disabled, admin and anonymous user will not be removed. */
299 userManager.listUserIds().each({ id ->
300     if (id != "anonymous" && id != "admin")
301         userManager.deleteUser(id)
302 })
303 repositoryManager.browse().each {
304     repositoryManager.delete(it.getName())
305 }
306 /* Add bearer token realms at the end of realm lists... */
307 realmManager.enableRealm("NpmToken")
308 realmManager.enableRealm("DockerToken")
309 realmManager.enableRealm("PypiToken")
310 /* Create the docker user. */
311 security.addUser("docker", "docker", "docker", "docker@example.com", true, "docker", ["nx-anonymous"])
312 /* Create docker, npm and pypi repositories. Their default configuration should be compliant with our requirements, except the docker registry creation. */
313 repository.createNpmHosted("npm-private")
314 repository.createPyPiHosted("pypi-private")
315 def r = repository.createDockerHosted("onap", 8082, 0)
316 /* force basic authentication true by default, must set to false for docker repo. */
317 conf=r.getConfiguration()
318 conf.attributes("docker").set("forceBasicAuth", false)
319 repositoryManager.update(conf)'
320
321 # Prepare the Nexus configuration
322 NEXUS_CONFIG=$(echo "${NEXUS_CONFIG_GROOVY}" | jq -Rsc  '{"name":"configure", "type":"groovy", "content":.}')
323
324 #################################
325 # Docker repository preparation #
326 #################################
327
328 if [ "${DOCKER_LOAD}" == "true" ]; then
329     # Load predefined Nexus image
330     docker load -i ${NEXUS_IMAGE_TAR}
331     # Load all necessary images
332     for DOCKER_IMG_LIST in "${NXS_DOCKER_IMG_LISTS[@]}"; do
333         load_docker_images "${DOCKER_IMG_LIST}"
334     done
335 fi
336
337 ################################
338 # Nexus repository preparation #
339 ################################
340
341 # Prepare nexus-data directory
342 if [ -d ${NEXUS_DATA_DIR} ]; then
343    if [ "$(docker ps -q -f name="${NEXUS_DOMAIN}")" ]; then
344        echo "Removing container ${NEXUS_DOMAIN}"
345        docker rm -f $(docker ps -aq -f name="${NEXUS_DOMAIN}")
346    fi
347    pushd ${NEXUS_DATA_DIR}/..
348    NXS_BACKUP="$(eval ${TIMESTAMP})_$(basename ${NEXUS_DATA_DIR})_bk"
349    mv ${NEXUS_DATA_DIR} "${NXS_BACKUP}"
350    echo "${NEXUS_DATA_DIR} already exists - backing up to ${NXS_BACKUP}"
351    popd
352 fi
353
354 mkdir -p ${NEXUS_DATA_DIR}
355 chown 200:200 ${NEXUS_DATA_DIR}
356 chmod 777 ${NEXUS_DATA_DIR}
357
358 # Save Nexus version to prevent/catch data incompatibility
359 # Adding commit informations to have link to data from which the blob was built
360 cat >> ${NEXUS_DATA_DIR}/nexus.ver << INFO
361 nexus_image=$(docker image ls ${NEXUS_IMAGE} --no-trunc --format "{{.Repository}}:{{.Tag}}\nnexus_image_digest={{.ID}}")
362 $(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)
363 $(sed -n 's/^.*OOM\ commit\ /oom_repo_commit=/p' ${NXS_DOCKER_IMG_LISTS[@]})
364 installer_repo_commit=$(git --git-dir="${LOCAL_PATH}/../.git" rev-parse HEAD)
365 INFO
366
367 # Start the Nexus
368 NEXUS_CONT_ID=$(docker run -d --rm -v ${NEXUS_DATA_DIR}:/nexus-data:rw --name ${NEXUS_DOMAIN} ${PUBLISHED_PORTS} ${NEXUS_IMAGE})
369 echo "Waiting for Nexus to fully start"
370 until curl -su ${NEXUS_USERNAME}:${NEXUS_PASSWORD} http://${NEXUS_HOST}:${NEXUS_PORT}/service/metrics/healthcheck | grep '"healthy":true' > /dev/null ; do
371     printf "."
372     sleep 3
373 done
374 echo -e "\nNexus started"
375
376 # Configure the nexus repository
377 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
378 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
379
380 ###########################
381 # Populate NPM repository #
382 ###########################
383 if [ $NPM_PUSH == "true" ]; then
384     prepare_npm
385     pushd ${NXS_SRC_NPM_DIR}
386     for NPM_LIST in "${NXS_NPM_LISTS[@]}"; do
387         patch_npm "${NPM_LIST}"
388         push_npm "${NPM_LIST}"
389     done
390     popd
391     # Return default settings
392     npm logout
393     npm config set registry "https://registry.npmjs.org"
394 fi
395
396 ###############################
397 ##  Populate PyPi repository  #
398 ###############################
399 if [ $PYPI_PUSH == "true" ]; then
400     pushd ${NXS_SRC_PYPI_DIR}
401     for PYPI_LIST in "${NXS_PYPI_LISTS[@]}"; do
402         push_pip "${PYPI_LIST}"
403     done
404     popd
405 fi
406
407 ###############################
408 ## Populate Docker repository #
409 ###############################
410
411 # Login to docker registry simulated by Nexus container
412 # Push images to private nexus based on the lists
413 # All images need to be tagged to simulated registry
414 # and those without defined repository in tag use default repository 'library'
415 docker_login
416 for DOCKER_IMG_LIST in "${NXS_DOCKER_IMG_LISTS[@]}"; do
417     push_docker "${DOCKER_IMG_LIST}"
418 done
419
420 ##############################
421 # Stop the Nexus and cleanup #
422 ##############################
423
424 echo "Stopping Nexus"
425
426 # Stop the Nexus
427 docker stop ${NEXUS_CONT_ID} > /dev/null
428
429 # Drop temporary Docker client config dir
430 rm -rf ${DOCKER_CONFIG_DIR}
431
432 echo "Nexus blob is built"
433 exit 0