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