Merge "Remove git repos list and simulation"
[oom/offline-installer.git] / build / build_nexus_blob.sh
1 #! /usr/bin/env bash
2
3 #   COPYRIGHT NOTICE STARTS HERE
4 #
5 #   Copyright 2018-2019 © 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 location
39 NEXUS_DOMAIN="nexus"
40 NEXUS_PORT="8081"
41 NEXUS_DOCKER_PORT="8082"
42 NPM_REGISTRY="http://${NEXUS_DOMAIN}:${NEXUS_PORT}/repository/npm-private/"
43 PYPI_REGISTRY="http://${NEXUS_DOMAIN}:${NEXUS_PORT}/repository/pypi-private/"
44 DOCKER_REGISTRY="${NEXUS_DOMAIN}:${NEXUS_DOCKER_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     "
101     exit 1
102 }
103
104 publish_ports () {
105     for REGISTRY in $(sed -n '/\.[^/].*\//p' ${1} | sed -e 's/\/.*$//' | sort -u | grep -v ${DEFAULT_REGISTRY} || true) ${NEXUS_PORT}; do
106         if [[ ${REGISTRY} != *":"* ]]; then
107             if [[ ${PUBLISHED_PORTS} != *"80:${NEXUS_DOCKER_PORT}"* ]]; then
108                 PUBLISHED_PORTS="${PUBLISHED_PORTS} -p 80:${NEXUS_DOCKER_PORT}"
109             fi
110         else
111             REGISTRY_PORT="$(sed 's/^.*\:\([[:digit:]]*\)$/\1/' <<< ${REGISTRY})"
112             if [[ ${PUBLISHED_PORTS} != *"${REGISTRY_PORT}:${NEXUS_DOCKER_PORT}"* ]]; then
113                 PUBLISHED_PORTS="${PUBLISHED_PORTS} -p ${REGISTRY_PORT}:${NEXUS_DOCKER_PORT}"
114             fi
115         fi
116     done
117 }
118
119 simulated_hosts () {
120     SIMUL_HOSTS=($(sed -n '/\.[^/].*\//p' ${1} | sed -e 's/\/.*$// ; s/:.*$//' | sort -u | grep -v ${DEFAULT_REGISTRY} || true ) ${NEXUS_DOMAIN})
121     for HOST in "${SIMUL_HOSTS[@]}"; do
122         if ! grep -wq ${HOST} /etc/hosts; then
123             echo "127.0.0.1 ${HOST}" >> /etc/hosts
124         fi
125     done
126 }
127
128 load_docker_images () {
129     for ARCHIVE in $(sed $'s/\r// ; /^#/d ; s/\:/\_/g ; s/\//\_/g ; s/$/\.tar/g' ${1} | awk '{ print $1 }'); do
130         docker load -i ${NXS_SRC_DOCKER_IMG_DIR}/${ARCHIVE}
131     done
132 }
133
134 prepare_npm () {
135     # Configure NPM registry to our Nexus repository
136     echo "Configure NPM registry to ${NPM_REGISTRY}"
137     npm config set registry "${NPM_REGISTRY}"
138
139     # Login to NPM registry
140     /usr/bin/expect <<- EOF
141         spawn npm login
142         expect "Username:"
143         send "${NEXUS_USERNAME}\n"
144         expect "Password:"
145         send "${NEXUS_PASSWORD}\n"
146         expect Email:
147         send "${NEXUS_EMAIL}\n"
148         expect eof
149         EOF
150 }
151
152 patch_npm () {
153     # Patch problematic package
154     PATCHED_NPM="$(grep tsscmp ${1} | sed $'s/\r// ; s/\\@/\-/ ; s/$/\.tgz/')"
155     if [[ ! -z "${PATCHED_NPM}" ]] && ! zgrep -aq "${NPM_REGISTRY}" "${PATCHED_NPM}" 2>/dev/null
156     then
157         tar xzf "${PATCHED_NPM}"
158         rm -f "${PATCHED_NPM}"
159         sed -i 's|\"registry\":\ \".*\"|\"registry\":\ \"'"${NPM_REGISTRY}"'\"|g' package/package.json
160         tar -zcf "${PATCHED_NPM}" package
161         rm -rf package
162     fi
163 }
164
165 push_npm () {
166     for ARCHIVE in $(sed $'s/\r// ; s/\\@/\-/g ; s/$/\.tgz/g' ${1}); do
167         npm publish --access public ${ARCHIVE} > /dev/null
168         echo "NPM ${ARCHIVE} pushed to Nexus"
169     done
170 }
171
172 push_pip () {
173     for PACKAGE in $(sed $'s/\r//; s/==/-/' ${1}); do
174         twine upload -u "${NEXUS_USERNAME}" -p "${NEXUS_PASSWORD}" --repository-url ${PYPI_REGISTRY} ${PACKAGE}* > /dev/null
175         echo "PYPI ${PACKAGE} pushed to Nexus"
176     done
177 }
178
179 docker_login () {
180     for REGISTRY in $(sed -n '/\.[^/].*\//p' ${1} | sed -e 's/\/.*$//' | sort -u | grep -v ${DEFAULT_REGISTRY}) ${DOCKER_REGISTRY}; do
181         if ! grep -wqs ${REGISTRY} ~/.docker/config.json; then
182                 echo "Docker login to ${REGISTRY}"
183             echo -n "${NEXUS_PASSWORD}" | docker login -u "${NEXUS_USERNAME}" --password-stdin ${REGISTRY} > /dev/null
184             fi
185     done
186 }
187
188 push_docker () {
189     for IMAGE in $(sed $'s/\r// ; /^#/d' ${1} | awk '{ print $1 }'); do
190         PUSH=""
191         if [[ ${IMAGE} != *"/"* ]]; then
192             PUSH="${DOCKER_REGISTRY}/library/${IMAGE}"
193         elif [[ ${IMAGE} == *"${DEFAULT_REGISTRY}"* ]]; then
194             if [[ ${IMAGE} == *"/"*"/"* ]]; then
195                 PUSH="$(sed 's/'"${DEFAULT_REGISTRY}"'/'"${DOCKER_REGISTRY}"'/' <<< ${IMAGE})"
196             else
197                 PUSH="$(sed 's/'"${DEFAULT_REGISTRY}"'/'"${DOCKER_REGISTRY}"'\/library/' <<< ${IMAGE})"
198             fi
199         elif [[ -z $(sed -n '/\.[^/].*\//p' <<< ${IMAGE}) ]]; then
200             PUSH="${DOCKER_REGISTRY}/${IMAGE}"
201         fi
202         if [[ ! -z ${PUSH} ]]; then
203             docker tag ${IMAGE} ${PUSH}
204         else
205             PUSH="${IMAGE}"
206         fi
207             docker push ${PUSH}
208         echo "${IMAGE} pushed as ${PUSH} to Nexus"
209     done
210 }
211
212 while [ "${1}" != "" ]; do
213     case ${1} in
214         -d | --docker )                    shift
215                                            NXS_DOCKER_IMG_LISTS+=("${1}")
216                                            ;;
217         -i | --input-directory )           shift
218                                            DATA_DIR="${1}"
219                                            ;;
220         -ld | --load-docker-images )       DOCKER_LOAD="true"
221                                            ;;
222         -n | --npm )                       NPM_PUSH="true"
223                                            COMMANDS+=(expect npm)
224                                            shift
225                                            NXS_NPM_LISTS+=("${1}")
226                                            ;;
227         -o | --output-directory )          shift
228                                            NEXUS_DATA_DIR="${1}"
229                                            ;;
230         -p | --pypi )                      PYPI_PUSH="true"
231                                            COMMANDS+=(twine)
232                                            shift
233                                            NXS_PYPI_LISTS+=("${1}")
234                                            ;;
235         -rl | --resource-list-directory )  shift
236                                            LISTS_DIR="${1}"
237                                            ;;
238         -h | --help )                      usage
239                                            ;;
240         *)                                 usage
241     esac
242     shift
243 done
244
245 # Verify all dependencies are available in PATH
246 FAILED_COMMANDS=()
247 for cmd in ${COMMANDS[*]}; do
248     command -v $cmd >/dev/null 2>&1 || FAILED_COMMANDS+=($cmd)
249 done
250
251 if [ ${#FAILED_COMMANDS[*]} -gt 0 ]; then
252     echo "Following commands where not found in PATH and are required:"
253     echo ${FAILED_COMMANDS[*]}
254     echo "Aborting."
255     exit 1
256 fi
257
258 # Setup directories with resources for docker, npm and pypi
259 NXS_SRC_DOCKER_IMG_DIR="${DATA_DIR}/offline_data/docker_images_for_nexus"
260 NXS_SRC_NPM_DIR="${DATA_DIR}/offline_data/npm_tar"
261 NXS_SRC_PYPI_DIR="${DATA_DIR}/offline_data/pypi"
262
263 # Setup specific resources lists
264 NXS_INFRA_LIST="${LISTS_DIR}/infra_docker_images.list"
265 NXS_DOCKER_IMG_LIST="${LISTS_DIR}/onap_docker_images.list"
266 NXS_RKE_DOCKER_IMG_LIST="${LISTS_DIR}/rke_docker_images.list"
267
268 # Setup Nexus image used for build and install infra
269 NEXUS_IMAGE="$(grep sonatype/nexus3 ${NXS_INFRA_LIST})"
270 NEXUS_IMAGE_TAR="${DATA_DIR}/offline_data/docker_images_infra/$(sed 's/\//\_/ ; s/$/\.tar/ ; s/\:/\_/' <<< ${NEXUS_IMAGE})"
271
272 # Set default lists if nothing specific defined by user
273 if [ ${#NXS_DOCKER_IMG_LISTS[@]} -eq 0 ]; then
274     NXS_DOCKER_IMG_LISTS=("${NXS_DOCKER_IMG_LIST}" "${NXS_RKE_DOCKER_IMG_LIST}")
275 fi
276
277 # Backup /etc/hosts
278 HOSTS_BACKUP="$(eval ${TIMESTAMP}_hosts.bk)"
279 cp /etc/hosts /etc/${HOSTS_BACKUP}
280
281 # Backup the current docker registry settings
282 if [ -f ~/.docker/config.json ]; then
283     DOCKER_CONF_BACKUP="$(eval ${TIMESTAMP}_config.json.bk)"
284     mv ~/.docker/config.json ~/.docker/${DOCKER_CONF_BACKUP}
285 fi
286
287 # Setup default ports published to host as docker registry
288 PUBLISHED_PORTS="-p ${NEXUS_PORT}:${NEXUS_PORT} -p ${NEXUS_DOCKER_PORT}:${NEXUS_DOCKER_PORT}"
289
290 # Setup additional ports published to host based on simulated docker registries
291 # Setup simulated domain names to be able to push all to private Nexus repository
292 for DOCKER_IMG_LIST in "${NXS_DOCKER_IMG_LISTS[@]}"; do
293     publish_ports "${DOCKER_IMG_LIST}"
294     simulated_hosts "${DOCKER_IMG_LIST}"
295 done
296
297 # Nexus repository configuration setup
298 NEXUS_CONFIG_GROOVY='import org.sonatype.nexus.security.realm.RealmManager
299 import org.sonatype.nexus.repository.attributes.AttributesFacet
300 import org.sonatype.nexus.security.user.UserManager
301 import org.sonatype.nexus.repository.manager.RepositoryManager
302 import org.sonatype.nexus.security.user.UserNotFoundException
303 /* Use the container to look up some services. */
304 realmManager = container.lookup(RealmManager.class)
305 userManager = container.lookup(UserManager.class, "default") //default user manager
306 repositoryManager = container.lookup(RepositoryManager.class)
307 /* Managers are used when scripting api cannot. Note that scripting api can only create mostly, and that creation methods return objects of created entities. */
308 /* Perform cleanup by removing all repos and users. Realms do not need to be re-disabled, admin and anonymous user will not be removed. */
309 userManager.listUserIds().each({ id ->
310     if (id != "anonymous" && id != "admin")
311         userManager.deleteUser(id)
312 })
313 repositoryManager.browse().each {
314     repositoryManager.delete(it.getName())
315 }
316 /* Add bearer token realms at the end of realm lists... */
317 realmManager.enableRealm("NpmToken")
318 realmManager.enableRealm("DockerToken")
319 realmManager.enableRealm("PypiToken")
320 /* Create the docker user. */
321 security.addUser("docker", "docker", "docker", "docker@example.com", true, "docker", ["nx-anonymous"])
322 /* Create docker, npm and pypi repositories. Their default configuration should be compliant with our requirements, except the docker registry creation. */
323 repository.createNpmHosted("npm-private")
324 repository.createPyPiHosted("pypi-private")
325 def r = repository.createDockerHosted("onap", 8082, 0)
326 /* force basic authentication true by default, must set to false for docker repo. */
327 conf=r.getConfiguration()
328 conf.attributes("docker").set("forceBasicAuth", false)
329 repositoryManager.update(conf)'
330
331 # Prepare the Nexus configuration
332 NEXUS_CONFIG=$(echo "${NEXUS_CONFIG_GROOVY}" | jq -Rsc  '{"name":"configure", "type":"groovy", "content":.}')
333
334 #################################
335 # Docker repository preparation #
336 #################################
337
338 if [ "${DOCKER_LOAD}" == "true" ]; then
339     # Load predefined Nexus image
340     docker load -i ${NEXUS_IMAGE_TAR}
341     # Load all necessary images
342     for DOCKER_IMG_LIST in "${NXS_DOCKER_IMG_LISTS[@]}"; do
343         load_docker_images "${DOCKER_IMG_LIST}"
344     done
345 fi
346
347 ################################
348 # Nexus repository preparation #
349 ################################
350
351 # Prepare nexus-data directory
352 if [ -d ${NEXUS_DATA_DIR} ]; then
353    if [ "$(docker ps -q -f name="${NEXUS_DOMAIN}")" ]; then
354        echo "Removing container ${NEXUS_DOMAIN}"
355        docker rm -f $(docker ps -aq -f name="${NEXUS_DOMAIN}")
356    fi
357    pushd ${NEXUS_DATA_DIR}/..
358    NXS_BACKUP="$(eval ${TIMESTAMP})_$(basename ${NEXUS_DATA_DIR})_bk"
359    mv ${NEXUS_DATA_DIR} "${NXS_BACKUP}"
360    echo "${NEXUS_DATA_DIR} already exists - backing up to ${NXS_BACKUP}"
361    popd
362 fi
363
364 mkdir -p ${NEXUS_DATA_DIR}
365 chown 200:200 ${NEXUS_DATA_DIR}
366 chmod 777 ${NEXUS_DATA_DIR}
367
368 # Save Nexus version to prevent/catch data incompatibility
369 # Adding commit informations to have link to data from which the blob was built
370 cat >> ${NEXUS_DATA_DIR}/nexus.ver << INFO
371 nexus_image=$(docker image ls ${NEXUS_IMAGE} --no-trunc --format "{{.Repository}}:{{.Tag}}\nnexus_image_digest={{.ID}}")
372 $(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)
373 $(sed -n 's/^.*OOM\ commit\ /oom_repo_commit=/p' ${NXS_DOCKER_IMG_LISTS[@]})
374 installer_repo_commit=$(git --git-dir="${LOCAL_PATH}/../.git" rev-parse HEAD)
375 INFO
376
377 # Start the Nexus
378 NEXUS_CONT_ID=$(docker run -d --rm -v ${NEXUS_DATA_DIR}:/nexus-data:rw --name ${NEXUS_DOMAIN} ${PUBLISHED_PORTS} ${NEXUS_IMAGE})
379 echo "Waiting for Nexus to fully start"
380 until curl -su ${NEXUS_USERNAME}:${NEXUS_PASSWORD} http://${NEXUS_DOMAIN}:${NEXUS_PORT}/service/metrics/healthcheck | grep '"healthy":true' > /dev/null ; do
381     printf "."
382     sleep 3
383 done
384 echo -e "\nNexus started"
385
386 # Configure the nexus repository
387 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
388 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
389
390 ###########################
391 # Populate NPM repository #
392 ###########################
393 if [ $NPM_PUSH == "true" ]; then
394     prepare_npm
395     pushd ${NXS_SRC_NPM_DIR}
396     for NPM_LIST in "${NXS_NPM_LISTS[@]}"; do
397         patch_npm "${NPM_LIST}"
398         push_npm "${NPM_LIST}"
399     done
400     popd
401     # Return default settings
402     npm logout
403     npm config set registry "https://registry.npmjs.org"
404 fi
405
406 ###############################
407 ##  Populate PyPi repository  #
408 ###############################
409 if [ $PYPI_PUSH == "true" ]; then
410     pushd ${NXS_SRC_PYPI_DIR}
411     for PYPI_LIST in "${NXS_PYPI_LISTS[@]}"; do
412         push_pip "${PYPI_LIST}"
413     done
414     popd
415 fi
416
417 ###############################
418 ## Populate Docker repository #
419 ###############################
420
421 # Login to simulated docker registries
422 # Push images to private nexus based on the lists
423 # Images from default registry need to be tagged to private registry
424 # and those without defined repository in tag uses default repository 'library'
425 for DOCKER_IMG_LIST in "${NXS_DOCKER_IMG_LISTS[@]}"; do
426     docker_login "${DOCKER_IMG_LIST}"
427     push_docker "${DOCKER_IMG_LIST}"
428 done
429
430 ##############################
431 # Stop the Nexus and cleanup #
432 ##############################
433
434 echo "Stopping Nexus and returning backups"
435
436 # Stop the Nexus
437 docker stop ${NEXUS_CONT_ID} > /dev/null
438
439 # Return backed up configuration files
440 mv -f "/etc/${HOSTS_BACKUP}" /etc/hosts
441
442 if [ -f ~/.docker/${DOCKER_CONF_BACKUP} ]; then
443     mv -f ~/.docker/${DOCKER_CONF_BACKUP} ~/.docker/config.json
444 fi
445
446 echo "Nexus blob is built"
447 exit 0