Содержание

Nexus

Система хранения артефактов, поддерживает множество форматов: maven, yum, npm, docker, helm, raw и другие.

Типы:

В 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 в настройках репозитория.

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

https://support.sonatype.com/hc/en-us/articles/115006744008-How-can-I-programmatically-upload-files-into-Nexus-3

Залить все файлы из каталога, сохранив структуру.

# Здесь: из каталога ~/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 и сам запущен в Докере.

  1. Cоздать репозиторий docker (hosted), например, с именем docker, и обязательно в настройках прописать ему порт HTTP, отличный от веб-интерфейсного, например, 8082.
  2. Отредактировать docker-compose или Dockerfile, чтобы этот порт был доступен снаружи, и перезапустить контейнер.
  3. Cоздать в Нексусе пользователя для этого репозитория и дать ему права.
  4. Чтобы Докер работал с незащищённым репозиторием, нужно ему прописать в конфигурацию insecure-registries, например, k3:8082.
    /etc/docker/daemon.json
    {
        "registry-mirrors": [
            "https://mirror.gcr.io"
        ],
        "insecure-registries": [
            "k3:8082",
            "k3.workgroup:8082",
            "k1:8082"
        ]
    }
  5. Далее
    # Дать права Докеру на файл
    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

Удаление

$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

How to delete docker images from Nexus Repository 3

Установка 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

https://github.com/oassuncao/nexus-gitlab-plugin

Миграция 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

Литература

Документация
Getting Started with Sonatype: The Best Way to Manage Your Java Releases (Daniel Persson on Youtube)