Содержание
Nexus
Система хранения артефактов, поддерживает множество форматов: maven, yum, npm, docker, helm, raw и другие.
Типы:
- hosted — располагающиеся локально на собственном хранилище
- proxy — ссылка на удалённый репозиторий, но кеширующиеся при выкачивании
- group — позволяет объединять несколько репозиториев в одну группу
В Nexus при создании репозитория типа maven2 спрашивается, какие типы артефактов будут в нём храниться: Snapshot, Release или Mixed. В мире Maven принято, что для разных этапов готовности продукта артефакт хранится в своём собственном репозитории.
Когда продукт сырой и мы ещё не готовы опубликовать готовую версию, но нужно её запустить и проверить (например, артефакт, который скомпилируется для прогона тестов на прикоммите), мы добавляем к имени версии постфикс -SNAPSHOT
, и тогда maven по умолчанию будет заливать в snapshot-репозиторий. Без этого постфикса все артефакты будут публиковаться в release-репозиторий.
Заливка
These are bare bones bash scripts to import a Nexus 2 Maven, NuGet or npm repository (and likely other file system based repos) into Nexus Repository 3. It imports artifacts into a Nexus Repository 3 Maven2, NuGet or npm hosted repo.
https://github.com/sonatype-nexus-community/nexus-repository-import-scripts/tree/master
Если заливать одну и ту же версию, то поведение Нексуса зависит от политики Deployment policy в настройках репозитория.
- Disable redeploy (по умолчанию) - если версия уже есть, то заливаться ничего не будет, даже если файл отличается.
- Allow redeploy - если файл той же версии, но сам по себе отличается, то он будет залит. Если файл тот же самый - заливаться не будет.
Maven
NEXUS_REPO_USER
, NEXUS_REPO_PASS
и прочие заносятся в переменные Gitlab.
- pom.xml
<distributionManagement> <repository> <id>testapp</id> <name>testapp</name> <url>${env.NEXUS_REPO_URL}/repository/${env.NEXUS_REPO_BACKEND_NAME}</url> </repository> </distributionManagement>
- settings.xml
<settings xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd" xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <servers> <server> <id>testapp</id> <username>${env.NEXUS_REPO_USER}</username> <password>${env.NEXUS_REPO_PASS}</password> </server> </servers> </settings>
- .gitlab-ci.yml
stages: - release # backend upload-backend-release: stage: release only: changes: - backend/**/* needs: - build-backend-code-job script: - cd backend # Вместо mvn package теперь mvn deploy - > mvn deploy -DskipTests -Dmaven.repo.local=${CI_PROJECT_DIR}/.m2/repository -s settings.xml -Dversion.application=${VERSION} # frontend заливается через curl upload-frontend-release: stage: release only: changes: - frontend/**/* needs: - build-frontend-code-job script: - cd frontend/dist - tar czvf testapp-${VERSION}.tar.gz frontend - > curl -v -u "${NEXUS_REPO_USER}:${NEXUS_REPO_PASS}" --upload-file testapp-${VERSION}.tar.gz ${NEXUS_REPO_URL}/${NEXUS_REPO_FRONTEND_NAME}/${VERSION}/testapp-${VERSION}.tar.gz
curl
Curl-ом можно заливать файлы в репозитории Maven2, YUM и RAW. Репозитории NuGet, NPM и Docker не поддерживают такую загрузку.
# В URL maven-releases - название репозитория, org/foo - группа org.foo, lunar-lang - имя компонента, 1.0.0 - версия. curl -u admin:admin -T artifact.jar http://k3.workgroup:8081/repository/maven-releases/org/foo/lunar-lang/1.0.0/lunar-lang-1.0.0.jar
Залить все файлы из каталога, сохранив структуру.
# Здесь: из каталога ~/cka в репозиторий http://k3:8081/repository/test, логин-пароль admin # 2 замены - первая меняет путь на URL, вторая - пробелы на %20 find ~/cka -type f -print0 |xargs -0 bash -c 'for f; do i=$(echo ${f/$PWD/http://k3:8081/repository/test}); i=$(echo ${i// /%20}); curl -u admin:admin --upload-file "$f" "$i" ; done'
По мотивам https://stackoverflow.com/questions/20050910/replacing-a-part-of-file-path-in-exec
Powershell
$folder = '$env:userprofile\Downloads\nexusartifacts' $repo = 'http://k3:8081/repository/test' $cred = 'admin:admin' $files = (dir $folder -Recurse -File).FullName foreach ($f in $files) { $url = $f.Replace("$folder","$repo").Replace('\','/') "$f ->`n$url`n" & curl.exe -ksu $cred -T $f $url }
Docker
Предполагается, что Nexus опубликован просто по HTTP и сам запущен в Докере.
- Cоздать репозиторий
docker (hosted)
, например, с именем docker, и обязательно в настройках прописать ему порт HTTP, отличный от веб-интерфейсного, например, 8082. - Отредактировать docker-compose или Dockerfile, чтобы этот порт был доступен снаружи, и перезапустить контейнер.
- Cоздать в Нексусе пользователя для этого репозитория и дать ему права.
- Чтобы Докер работал с незащищённым репозиторием, нужно ему прописать в конфигурацию
insecure-registries
, например,k3:8082
.- /etc/docker/daemon.json
{ "registry-mirrors": [ "https://mirror.gcr.io" ], "insecure-registries": [ "k3:8082", "k3.workgroup:8082", "k1:8082" ] }
- Далее
# Дать права Докеру на файл sudo chown $USER:docker /etc/docker/daemon.json # Перечитать настройки (может, и restart) sudo systemctl reload docker # Залогиниться (Докер-клиент не поддерживает путей URL, только хост:порт) docker login k3:8082 # Поставить тэг образу, который нужно залить docker tag alpine:3.19 k3:8082/repository/docker/alpine:3.19 # Залить образ в Нексус docker push k3:8082/repository/docker/alpine:3.19
https://www.devopsschool.com/blog/how-to-upload-and-download-docker-images-using-nexus-registry-repository/
https://help.sonatype.com/en/hosted-repository-for-docker--private-registry-for-docker-.html
https://help.sonatype.com/en/ssl-and-repository-connector-configuration.html
Заливка всех образов, имеющихся на машине, в Нексус. С тэгом latest
будет заливаться как есть, так что, возможно, нужно отфильтровать их в awk
и тэгировать руками, чтобы не было перезаписи при заливке.
# Логин в репу Нексуса docker login k3:8082 # Адрес репы (без слэша в конце) repo='k3:8082/repository/docker' # Список образов (кроме <none>) с тэгами. egrep - если понадобится запустить ещё раз, а awk не раскрывает переменные в /.../ # egrep отфильтровывает образы с тэгами, начинающимися на host:port/ images=($(docker image ls | awk '!/REPOSITORY|<none>/ {print $1":"$2}' |egrep -v ^.*:[[:digit:]]*/)) # Тэгировать и залить for i in "${images[@]}" ; do docker tag $i $repo/$i && docker push $repo/$i ; done
Python
Используется twine. В конце URL необходим trailing slash, иначе будет ошибка при загрузке.
# Upload all files in the folder $twine = "$env:localappdata\Programs\Python\Python312\Scripts\twine.exe" # если нет в PATH & $twine upload --repository-url "http://k3:8081/repository/pypi-lib/" ` -u admin -p admin --non-interactive --skip-existing --disable-progress-bar ` C:\temp\pypi\*
Есть вариант прописывать реквизиты входа в файл .pypirc
, например,
- ~/.pypirc
[distutils] index-servers = pypi [pypi] repository: http://k3:8081/repository/pypi-lib/ username: admin password: admin
Тогда команда загрузки будет
# -r - [имя репы], прописанной в .pypirc twine upload -r pypi C:\temp\pypi\*.whl
https://help.sonatype.com/en/pypi-repositories.html
https://twine.readthedocs.io/en/stable/
https://stackoverflow.com/questions/56592918/how-to-upload-the-python-packages-to-nexus-sonartype-private-repo
Скачивание
- testapp-backend.service
[Unit] Description=testapp-backend [Service] User=jarservice Environment=REPORT_PATH=/var/testapp/reports Environment=LOG_PATH=/var/testapp/logs Restart=always ExecStart=/usr/bin/java \ -Dmyserver.basePath='/home/jarservice/' \ -Dmyserver.bindAddr='127.0.0.1' \ -Dmyserver.bindPort='8080' \ -Dmyserver.hostName='testapp' \ -jar '/home/jarservice/testapp.jar' SuccessExitStatus=143 [Install] WantedBy=multi-user.target
- deploy.sh
#!/bin/bash # Скрипт валится при любой ошибке set -xe # Перезалить дескриптор сервиса на ВМ для деплоя sudo cp -rf testapp-backend.service /etc/systemd/system/testapp-backend.service sudo rm -f /home/jarservice/testapp.jar || true # Перенос артефакта в нужную папку curl -u ${NEXUS_REPO_USER}:${NEXUS_REPO_PASS} -o testapp.jar ${NEXUS_REPO_URL}/${NEXUS_REPO_BACKEND_NAME}/ru/company/project/devops/testapp/${VERSION}/testapp-${VERSION}.jar sudo cp ./testapp.jar /home/jarservice/testapp.jar || true # Обновить конфиг systemd sudo systemctl daemon-reload # Перезапустить сервис sudo systemctl restart testapp-backend
- .gitlab-ci.yml
deploy: stage: deploy script: - scp ./backend/testapp-backend.service ${DEV_USER}@${DEV_HOST}:/home/${DEV_USER}/testapp-backend.service - > ssh ${DEV_USER}@${DEV_HOST} " export "CURRENT_VERSION=${VERSION}"; export "VERSION=${VERSION}"; export "DEV_HOST=${DEV_HOST}"; export "NEXUS_REPO_URL=${NEXUS_REPO_URL}"; export "NEXUS_REPO_USER=${NEXUS_REPO_USER}"; export "NEXUS_REPO_PASS=${NEXUS_REPO_PASS}"; setsid /bin/bash -s " < ./backend/deploy.sh
Gradle
Копия дистрибутива в локальном Nexus (raw-репозиторий)
- gradle/wrapper/gradle-wrapper.properties
distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists #distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip distributionUrl=http\://login:P@ssw0rd@URL[:PORT]/repository/gradle/gradle-7.3.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists
Аутентификация для gradlew
- .gitlab-ci.yaml
variables: GRADLE_OPTS: "-Dgradle.wrapperUser=$NEXUS_LOGIN -Dgradle.wrapperPassword=$NEXUS_PASSWORD" # Сборка на воркере .build: &build - cd $CI_PROJECT_DIR # Если нет в вышестоящих переменных # - export GRADLE_OPTS="-Dgradle.wrapperUser=$NEXUS_LOGIN -Dgradle.wrapperPassword=$NEXUS_PASSWORD" - cd test-module - chmod +x ./gradlew - > ./gradlew build -x test-metamodel:build -P gitLabPrivateToken=$REGISTRY_TOKEN # Сборка через ssh, нужно экранировать кавычки, иначе будет ошибка # bash: line 0: export: `-Dgradle.wrapperPassword=[MASKED]': not a valid identifier apiTests: stage: test script: - cd $CI_PROJECT_DIR - ssh dev-server "export "GRADLE_OPTS=\"$GRADLE_OPTS\""; cd /home/user/api-tests/test-module && git init && ./gradlew -x test clean build publishMavenPublicationToMavenLocal -P gitLabPrivateToken=$REGISTRY_TOKEN"
В свойствах пишем URL без логина и пароля, они сами подхватятся из GRADLE_OPTS.
- gradle/wrapper/gradle-wrapper.properties
distributionUrl=http\://URL[:PORT]/repository/gradle/gradle-7.3.3-bin.zip
https://docs.gradle.org/current/userguide/gradle_wrapper.html#customizing_wrapper
https://stackoverflow.com/a/69401685
Для CI/CD. Дело в том, что при локальной сборке разработчиками у них нет данных для доступа в локальный репозиторий.
Это скрипт замены ссылки в файле /gradle/wrapper/gradle-wrapper.properties
, который меняет ссылку на локальную,
если в переменной GRADLE_OPTS
есть значение wrapperUser
.
[[ $GRADLE_OPTS =~ wrapperUser ]] && \ sed -i 's#^\(distributionUrl=*\).*\/\(.*\)$#\1https\\://example.com/nexus/repository/gradle-zip/\2#' gradle/wrapper/gradle-wrapper.properties # Было distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip # Стало distributionUrl=https\://example.com/nexus/repository/gradle-zip/gradle-7.3.3-bin.zip
Python
Репозиторий pypi можно скачать curl-ом, а вот загружать им туда не получится, для этого нужен twine.
С иерархией каталогов, как в случае с maven, можно не заморачиваться и грузить все файлы в одну папку. При последующей выгрузке в nexus с помощью twine необходимая структура будет создана автоматически.
Powershell
$server = 'http://k3:8081' $repo = 'pypi' $cred = 'admin:admin' $urls = "C:\temp\$repo.txt" $dest = "C:\temp\$repo" mkdir $dest -ErrorAction SilentlyContinue > $null $repoUrl = "$server/repository/$repo" # Make URLs list of packages $packageList = curl.exe -ksu $cred "$repoUrl/simple/" $versionsList = ($packageList -match 'a href' -replace '<.*?>').trim() |% { (curl.exe -ksu $cred "$repoUrl/simple/$_/") -match 'a href' } # Packages or not packages # ($versionsList -replace '.*\.\./(.*)#.*','$1') -notmatch '^packages' $packages - $versionsList -replace '.*(packages.*)#.*','$1' -match '^packages/' # filter trash strings like <a href="../../../.."> # If list restriction is needed, e.g. low disk space #$letters = ($([char[]](0..255) -match '[u-z]') |% {"packages/$_"}) -join '|' if ($letters) { $packagesSelected = $packages -match "$letters" } else { $packagesSelected = $packages } $packagesSelected |% { "$repoUrl/$_" } > $urls # Download gc $urls |% { echo $_ curl.exe -ksu $cred -o "$dest\$($_ -replace '.*/')" $_ }
https://help.sonatype.com/en/pypi-repositories.html
https://stackoverflow.com/questions/50348248/creating-a-full-replica-offline-copy-of-the-public-pypi-repository
curl
Скачивание всего репозитория. В зависимости от типа репозитория (здесь пример для maven-репы), регулярку для sed надо корректировать.
server='http://k3:8081' repo='repo' cred='admin:admin' urls='/tmp/repo_urls.txt' # Get assets list assets="$server/service/rest/v1/assets?repository=$repo" i=$(curl -ksu $cred -X GET $assets) grep downloadUrl <<< $i |cut -d\" -f4 > $urls token=$(grep continuationToken <<< $i |cut -d\" -f4) while [[ -n $token ]]; do i=$(curl -ksu $cred -X GET "$assets&continuationToken=$token") grep downloadUrl <<< $i |cut -d\" -f4 >> $urls token=$(grep continuationToken <<< $i |cut -d\" -f4) done # Create subdirs mkdir -p $(sed -E "s#.*$repo/(.*)/.*#\1#" $urls |sort |uniq) # Download for i in $(cat $urls); do echo $i curl -ksu $cred -o $(sed -E "s#.*$repo/##" <<< $i) $i done
# Примеры ссылок в $urls для репозитория Docker # http://k3:8081/repository/docker/v2/-/blobs/sha256:31eec910d005bbfe2c5618507337f97c56a4f9fc0767bd314786d3406d06b5e1 # http://k3:8081/repository/docker/v2/repository/docker/maven/manifests/3.5.2 # Регулярки для sed mkdir -p $(sed -E 's#.*v2/(-/)?(.*)/.*#\2#' $urls |sort |uniq) curl -ksu $cred -o $(sed -E 's#.*v2/(-/)?##' <<< $i) $i
https://www.sonatype.com/blog/nexus-repository-new-beta-rest-api-for-content
https://stackoverflow.com/questions/68055906/how-can-i-get-components-in-repository-via-nexus3-api
https://help.sonatype.com/en/pagination.html
https://www.steventwheeler.com/java/2018/10/30/migrate-artifactory-to-nexus.html
https://github.com/LoadingByte/nexus3-exporter/tree/master
Поиск
# Артефакт lunar-lang, сортировать по версиям curl -qu admin:admin 'http://k3.workgroup:8081/service/rest/v1/search/assets?maven.artifactId=lunar-lang&sort=version' # Определённая версия curl -qu admin:admin 'http://k3.workgroup:8081/service/rest/v1/search/assets?maven.artifactId=lunar-lang&version=1.0.0' # В определённом репозитории curl -qu admin:admin 'http://k3.workgroup:8081/service/rest/v1/search/assets?maven.artifactId=lunar-lang&repository=maven-releases&version=1.0.1'
https://help.sonatype.com/en/search-api.html
Извлечение ссылок, Powershell
($json |ConvertFrom-Json).items.downloadUrl # Вывод информации о найденных пакетах, сортировка по версиям ($json |ConvertFrom-Json).items.maven2 |sort {[version]$_.version} extension groupId artifactId version --------- ------- ---------- ------- jar org.foo lunar-lang 1.0.0 jar org.foo lunar-lang 1.0.1 jar org.foo lunar-lang 1.0.2 jar org.foo lunar-lang 1.0.3
Bash
# jq -r - без кавычек $json |jq .items[].downloadUrl
Получить версию самой последней версии артефакта
Нужно, чтобы проставить версию в описании компонента при деплое. Надо парсить maven-metadata.xml
version=$(curl -qu admin:admin http://k3:8081/repository/maven-public/org/foo/lunar-lang/maven-metadata.xml |grep latest) # В версиях встречаются цифры, точки и дефисы sed 's#[^0-9.-]##g' <<< $version 1.0.12
Удаление
$apiUrl = 'http://k3:8081/service/rest/v1' $cred = 'admin:admin' (curl.exe -ksu $cred "$apiUrl/search/assets?repository=maven-public&version=1.0.?" |ConvertFrom-Json).items |% { curl.exe -ksu $cred -X DELETE "$apiUrl/assets/$($_.id)" }
Docker
Установка Nexus
Пользователь id 200
docker run --rm -d --name nexus sonatype/nexus3 docker exec nexus grep nexus /etc/passwd nexus:x:200:200:Nexus Repository Manager user:/opt/sonatype/nexus:/bin/false
Gitlab Authentication Realm
FROM sonatype/nexus3 USER root RUN mkdir -p /opt/sonatype/nexus/system/com/github/oassuncao/ && \ curl -Lo /opt/sonatype/nexus/system/com/github/oassuncao/nexus-gitlab-plugin-1.6.3.jar https://github.com/oassuncao/nexus-gitlab-plugin/releases/download/v1.6.3/nexus-gitlab-plugin-1.6.3.jar && \ echo "reference\:file\:com/github/oassuncao/nexus-gitlab-plugin-1.6.3.jar = 200" >> /opt/sonatype/nexus/etc/karaf/startup.properties USER nexus
Миграция OrientDB > H2
Nexus 3.70 - последняя версия, где поддерживается OrientDB. Чтобы обновить Nexus, нужно сконвертировать OrientDB в H2.
# Забэкапить базы из GUI, скопировать в папку backup. # Остановить Nexus. # Скачать мигратор curl -L https://download.sonatype.com/nexus/nxrm3-migrator/nexus-db-migrator-3.70.1-03.jar -o backup/nexus-db-migrator.jar # Запустить Nexus в командной строке docker run -it --rm -v ./backup:/nexus-data -u root --entrypoint bash nexus # В контейнере: cd /nexus-data java -Xmx16G -Xms1G -XX:+UseG1GC -XX:MaxDirectMemorySize=28672M -jar nexus-db-migrator.jar --migration_type=h2 # На хосте cp backup/nexus.mv.db nexus-data/db/ echo "nexus.datastore.enabled=true" >> nexus-data/etc/nexus.properties
https://help.sonatype.com/en/migrating-to-a-new-database.html#migrating-from-orientdb-to-h2-162010
https://f3l1x-io.translate.goog/blog/2024/sonatype-nexus-repository-orientdb-a-h2?_x_tr_sl=auto&_x_tr_tl=ru&_x_tr_hl=ru&_x_tr_pto=wapp&_x_tr_hist=true
SSL
Исходный сертификат с ключом должен быть в формате PCKS12.
# Если исходник в формате PEM, его надо сконвертировать openssl pkcs12 -export -in cert.pem -out cert.pkcs12 # Если исходник - это crt + key, то openssl pkcs12 -export -in cert.crt -inkey private.key -out cert.pkcs12
Если исходник в виде .pfx, то конвертировать ничего не надо.
# Импортировать сертификат в хранилище ключей jks # Важно: пароль оставить тем же, что и в сертификате # Возможно, потребуется сначала создать каталог <nexus-install-location>/nexus-<version>/etc/ssl keytool -v -importkeystore -srckeystore cert.pkcs12 -srcstoretype PKCS12 \ -destkeystore <nexus-install-location>/nexus-<version>/etc/ssl/keystore.jks -deststoretype JKS nano <nexus-install-location>/sonatype-work/nexus3/etc/nexus.properties # Добавить строку application-port-ssl=443 # Раскомментировать строку с nexus-args и добавить туда ${jetty.etc}/jetty-https.xml nano <nexus-install-location>/nexus-<version>/etc/jetty/https-config.xml # Заменить password на пароль сертификата (в 3 местах)
Перезапустить Nexus.
Литература
Документация
Getting Started with Sonatype: The Best Way to Manage Your Java Releases (Daniel Persson on Youtube)