role: user

type: text

text:
<user_query>

Артем Нагибин
27 минут назад

@lmru-devops-bot привет. нужна помощь.

Можно ли в конфигурации Jenkins для проекта добавить как-то зеркала для maven в конфигурации?

Зависимости тянутся из https://art.lmru.tech/mvn , но нужно чтоб тянулись из https://art.lmru.tech/mvn-replenishment
https://jenkins.lmru.tech/job/replenishment/job/backend/job/lmru--replenishment--parameterization-api/job/REPLAN-3052/103/execution/node/102/log/

В pom указан именно mvn-replenishment и локально все ок.
в дженкинсе этот источник в сборках не учитывается
</user_query>


role: assistant

text:
Понял задачу: нужно разобраться, почему в Jenkins игнорируется `mvn-replenishment`, хотя в `pom.xml` он указан, и как это поправить через конфигурацию. Сейчас быстро проверю `Jenkinsfile`/Maven-настройки в репозитории и подскажу точный рабочий вариант.

**Inspecting Jenkins config and Maven files**


text: **Preparing to analyze build files**


text:
Проверил репозиторий: в `pom.xml` у вас действительно прописан только `https://art.lmru.tech/mvn-replenishment` для `repositories` и `pluginRepositories`. Значит проблема почти наверняка в Jenkins `settings.xml` (или глобальном managed settings), где зеркало перекрывает репозиторий из `pom`.
Да, можно — и в вашем случае это как раз правильное место фикса.

`pom.xml` у вас уже корректный (`mvn-replenishment` есть), а в Jenkins обычно это «перебивается» через `settings.xml` с `<mirrors>`, где стоит что-то вроде `mirrorOf=*` на `https://art.lmru.tech/mvn`.

Что нужно сделать в Jenkins:

- В **Managed files** (Config File Provider) найти Maven `settings.xml`, который использует джоба/папка/глобал.
- Исправить секцию `<mirrors>` так, чтобы зеркало указывало на `mvn-replenishment`:
```xml
<mirrors>
  <mirror>
    <id>artifactory-replenishment</id>
    <mirrorOf>*</mirrorOf>
    <url>https://art.lmru.tech/mvn-replenishment</url>
  </mirror>
</mirrors>
```
- Убедиться, что в `<servers>` есть креды для этого `id` (или для id, который реально используется вашим artifactory).
- Перезапустить билд.

Почему так: при наличии mirror в `settings.xml` Maven берёт его в приоритет, и `repositories` из `pom.xml` фактически игнорируются.

Быстрая проверка в Jenkins (для подтверждения):
- запустить шаг с `mvn -X help:effective-settings`
- в логе увидеть активный mirror URL (должен быть `.../mvn-replenishment`)

Если хотите, могу подготовить точный текст для `lmru-devops-bot`/DevOps с формулировкой «что поменять и где проверить», чтобы быстрее согласовали.

**Advising Maven mirror configuration**
**Clarifying Jenkinsfile constraints**


text:
<user_query>
svPipeline
import groovy.transform.Field

// settings shared across pipeline methods (excluding job parameters)
@Field
final PipelineOptions opts = new PipelineOptions()
// mostly for notifications
@Field
final JobInfo jobInfo = new JobInfo()
// copy of jenkins 'params' object to be able to modify it at runtime
@Field
final JobParameters jobParams = new JobParameters()

@Field
final Map<ExtensionPointEnum, ExtensionExecutable> extensionPointsMap = [:]

@Field
final ExtensionPointData extensionPointData = new ExtensionPointData()

@Field
final String PIPELINE_SOURCE_FOLDER = 'pipeline-sources'

@Field
final String SV_PIPELINE_GITHUB_URL = 'https://github.lmru.tech/adeo/lmru--sv-jvm-k8s-pipeline'

void run(Map argsMap) {
    node('lmru-dockerhost') {
        configureExtensionPoints(argsMap)
        executeExtensionIfExists(ExtensionPointEnum.PRE_PIPELINE)
        setup(argsMap)
        executeExtensionIfExists(ExtensionPointEnum.POST_SETUP)

        LoopOptions loopOpts = opts.notificationOptions.loopOptions

        try {
            loopUtilsSV.notify(JobStatus.START.name(), loopOpts, opts.yandexTrackerCredentialsId, jobInfo)
            if (jobParams.runCheckmarxScan) {
                runCheckmarxScan()
            }
            if (jobParams.runSecretsScan) {
                runSecretsScan()
            }
            if (jobParams.shouldPerformGitflowAction) {
                executeExtensionIfExists(ExtensionPointEnum.PRE_GITFLOW)
                runGitflow()
                executeExtensionIfExists(ExtensionPointEnum.POST_GITFLOW)
                if (jobInfo.shouldRelease || jobInfo.shouldFinishHotfix || jobInfo.shouldFinishRelease) {
                    jobInfo.releaseTasksList = jiraUtils.findTasksByCommitsFromTagToPreviousTag(jobInfo.targetVersion)
                }
            }
            if (jobParams.shouldBuild) {
                executeExtensionIfExists(ExtensionPointEnum.PRE_BUILD)
                buildAppAndImage(!jobParams.shouldPerformGitflowAction) // do not build with maven second time, if gitflow = true
                executeExtensionIfExists(ExtensionPointEnum.POST_BUILD)
            }
            if (jobParams.shouldDeploy) {
                executeExtensionIfExists(ExtensionPointEnum.PRE_DEPLOY)
                deployToManyClustersWithRollback()
                notifyVoyager()
                executeExtensionIfExists(ExtensionPointEnum.POST_DEPLOY)
            }
            cleanWs()
            if (jobParams.shouldLaunchAutoTests) {
                executeExtensionIfExists(ExtensionPointEnum.PRE_AUTO_TESTS)
                launchAutoTests()
                executeExtensionIfExists(ExtensionPointEnum.POST_AUTO_TESTS)
            }
            executeExtensionIfExists(ExtensionPointEnum.POST_PIPELINE)
            loopUtilsSV.notify(currentBuild.currentResult, loopOpts, opts.yandexTrackerCredentialsId, jobInfo)
        } catch (err) {
            printException(err)
            currentBuild.result = 'FAILED'
            extensionPointData.error = err
            executeExtensionIfExists(ExtensionPointEnum.ON_ERROR)
            loopUtilsSV.notify(currentBuild.currentResult, loopOpts, opts.yandexTrackerCredentialsId, jobInfo)
            error(err)
        }
    }
}

void setup(Map argsMap) {
    stage("Setup") {
        cleanWs()
        buildPipelineOptions(argsMap)
        buildJobParametersDefinitions()
        readJobParametersValues()
        overrideJobParamsOnGithubEvent()
        checkoutSources()
        setupEnvVariables()
        setJobName PROJECT_NAME_PARAM
        buildJobInfo()
        checkDeprecations()
    }
}

void buildPipelineOptions(Map argsMap) {
    List<String> requiredProperties = [ 'kubernetes', 'vault', 'lmSaCredentialsId' ]
    if (!argsMap.keySet().containsAll(requiredProperties)) {
        echo "argsMap: $argsMap"
        error "svPipeline.run(argsMap) -- argsMap must contain following properties: $requiredProperties"
    }

    // single options
    if (argsMap.profiles != null) {
        opts.profiles = argsMap.profiles
    }
    if (argsMap.githubCredentialsId != null) {
        opts.githubCredentialsId = argsMap.githubCredentialsId
    }
    opts.lmSaCredentialsId = argsMap.lmSaCredentialsId
    opts.yandexTrackerCredentialsId = argsMap.yandexTrackerCredentialsId

    // build
    if (argsMap.build?.mavenImageTag != null) {
        opts.buildOptions.mavenImageTag = argsMap.build.mavenImageTag
    }
    if (argsMap.build?.baseImageTag != null) {
        opts.buildOptions.baseImageTag = argsMap.build.baseImageTag
    }
    if (argsMap.build?.baseImagePath != null) {
        opts.buildOptions.baseImagePath = argsMap.build.baseImagePath
    }
    opts.buildOptions.artifactoryDomain = argsMap.build?.artifactoryDomain
    opts.buildOptions.artifactoryAppImagePath = argsMap.build?.artifactoryAppImagePath
    opts.buildOptions.imageCustomLibraryList = argsMap.build?.imageCustomLibraryList

    // voyager/sputnik/rms
    if (argsMap.notifications?.containsKey('voyager')) {
        opts.notificationOptions.voyagerOptions.productId = argsMap.notifications.voyager.productId
        opts.notificationOptions.voyagerOptions.serviceName = argsMap.notifications.voyager.serviceName
        opts.notificationOptions.voyagerOptions.productName = argsMap.notifications.voyager.productName
    } else {
        error "svPipeline.run(argsMap) -- argsMap must contain property notifications.voyager"
    }

    // kubernetes
    if (argsMap.kubernetes.helmChartVersion != null) {
        opts.kubernetesOptions.helmChartVersion = argsMap.kubernetes.helmChartVersion
    }
    opts.kubernetesOptions.clusterName = argsMap.kubernetes.clusterName
    opts.kubernetesOptions.baseNamespace = argsMap.kubernetes.baseNamespace
    if (argsMap.kubernetes.helmReleaseNameBuilder != null) {
        opts.kubernetesOptions.helmReleaseNameBuilder = argsMap.kubernetes.helmReleaseNameBuilder
    }
    opts.kubernetesOptions.stageClusters = argsMap.kubernetes.stageClusters
    opts.kubernetesOptions.prodClusters = argsMap.kubernetes.prodClusters
    if (argsMap.kubernetes.containsKey('autoRollbackMulticlusterErrors')) {
        opts.kubernetesOptions.autoRollbackMulticlusterErrors = argsMap.kubernetes.autoRollbackMulticlusterErrors
    }

    // gitflow
    if (argsMap.gitflow?.developmentBranch != null) {
        opts.gitflowOptions.developmentBranch = argsMap.gitflow.developmentBranch
    }
    if (argsMap.gitflow?.shouldQualityGateFailRelease != null) {
        opts.gitflowOptions.shouldQualityGateFailRelease = argsMap.gitflow.shouldQualityGateFailRelease
    }
    // vault
    if (argsMap.vault.enabled != null) {
        opts.vaultOptions.enabled = argsMap.vault.enabled
    }
    opts.vaultOptions.credentialsId = argsMap.vault.credentialsId
    opts.vaultOptions.namespace = argsMap.vault.namespace
    opts.vaultOptions.approleSecretLocation = argsMap.vault.approleSecretLocation
    opts.vaultOptions.applicationApproleCredentialsId = argsMap.vault.applicationApproleCredentialsId
    // notifications
    if (argsMap.notifications?.containsKey('loop')) {
        def loopOptionsMap = argsMap.notifications.loop
        if (!loopOptionsMap.containsKey('channelIds')) {
            error("You must provide notifications.loop.channelIds if you want to use loop")
        }
        if (loopOptionsMap.containsKey('credentialsId')) {
            opts.notificationOptions.loopOptions.credentialsId = loopOptionsMap.credentialsId
        }
        opts.notificationOptions.loopOptions.channelIds = loopOptionsMap.channelIds
        if (loopOptionsMap.containsKey('watchEnvironments')) {
            opts.notificationOptions.loopOptions.watchEnvironments = loopOptionsMap.watchEnvironments
        }
        if (loopOptionsMap.containsKey('watchReleases')) {
            opts.notificationOptions.loopOptions.watchReleases = loopOptionsMap.watchReleases
        }
    }

    // overrides
    if (argsMap.overrideProperties?.containsKey("domainNameParam")) {
        opts.overridePropertiesOptions.domainNameParam = argsMap.overrideProperties.domainNameParam
    }
    if (argsMap.overrideProperties?.containsKey("brickNameParam")) {
        opts.overridePropertiesOptions.brickNameParam = argsMap.overrideProperties.brickNameParam
    }
    // auto tests
    if (argsMap.containsKey("autoTests")) {
        opts.autoTestsOptions.jobUrl = argsMap.autoTests.jobUrl
        opts.autoTestsOptions.suiteNames = argsMap.autoTests.suiteNames
    }
    // github
    if (argsMap.github?.onPullRequest != null) {
        opts.githubOptions.onPullRequest = argsMap.github.onPullRequest
    }
    if (argsMap.github?.onPushEvent != null) {
        opts.githubOptions.onPushEvent = argsMap.github.onPushEvent
    }

    echo "PipelineOptions:"
    printProperties(opts)
}

void checkDeprecations() {
    // Only "official" tags. Teams may still use their own image paths.
    Set<String> deprecatedJavaImages = [ 'jre-12', 'liberica-14', 'liberica-8', 'openjdk8', 'openjdk8-fonts', 'openjdk8-runtime' ]
    if (opts.buildOptions.baseImageTag in deprecatedJavaImages) {
        unstable(
            'Your java base image does not fully support cgroup v2, ' +
            'so it will not see available resources in kubernetes on ubuntu 22+ application nodes. ' +
            'Please use LTS JDK/JRE: 11 or 17. See https://bugs.openjdk.org/browse/JDK-8230305'
        )
    }
}

void buildJobParametersDefinitions() {
    def jobParametersDefinitions = [
        separator(name: 'build', sectionHeader: 'Build'),
        booleanParam(name: 'shouldPerformGitflowAction', description: 'Finish feature / finish hotfix / build release?', defaultValue: false),
        booleanParam(name: 'shouldBuild', description: 'Build microservice?', defaultValue: false),
        separator(name: 'deployment', sectionHeader: 'Deployment'),
        booleanParam(name: 'shouldDeploy', description: 'Deploy?', defaultValue: false),
        choice(name: 'profile', description: 'Profile for spring and helm-chart/values.yml', choices: opts.profiles),
        // clustersForDeploy param here if needed
        string(name: 'customDeployEnvironment', description: 'For feature testing or whatever you want', defaultValue: ''),
        string(
            name: 'helmRollbackReleaseAfter',
            description: 'Raise this value if your deployment lasts longer than 5 minutes. ' +
                'E.g. when applying liquibase changeset or when k8s cluster needs to raise yet another node.',
            defaultValue: '5m'
        ),
        separator(name: 'testing', sectionHeader: 'Testing'),
        booleanParam(name: 'shouldLaunchAutoTests', description: 'Start job with auto-tests after deploy?', defaultValue: false),
        [
            $class: 'CascadeChoiceParameter',
            choiceType: 'PT_CHECKBOX',
            description: 'Test suites\' names',
            filterLength: 1,
            filterable: false,
            name: 'testsSuiteNames',
            script: [
                $class: 'GroovyScript',
                script: [
                    classpath: [],
                    sandbox: true,
                    script: "return ${opts.autoTestsOptions.suiteNames}"
                ]
            ]
        ],
        separator(name: 'securityScanners', sectionHeader: 'Security Scanners'),
        booleanParam(name: 'runSecretsScan', description: 'Run Secrets scanner and download report?', defaultValue: false),
        booleanParam(name: 'runAquaScan', description: 'Run Aqua scanner and download report? (use with shouldBuild)', defaultValue: false),
        booleanParam(name: 'runCheckmarxScan', description: 'Run Checkmarx scanner and download report?', defaultValue: false)
    ]
    if (opts.kubernetesOptions.stageClusters || opts.kubernetesOptions.prodClusters) {
        jobParametersDefinitions.add(
            6, // index in list, where put at
            [
                $class: 'CascadeChoiceParameter',
                choiceType: 'PT_CHECKBOX',
                description: 'Which clusters should deploy to?',
                filterLength: 1,
                filterable: false,
                name: 'clustersForDeploy',
                multiSelectDelimiter: ',',
                referencedParameters: 'shouldDeploy, profile',
                script: [
                    $class: 'GroovyScript',
                    script: [
                        classpath: [],
                        sandbox: true,
                        script: """
                            if (shouldDeploy) {
                                if (profile == 'prod') {
                                    return ${opts.kubernetesOptions.prodClusters.collect { "\"$it:selected\"" }.toString().replaceAll(" ", "")}
                                } else {
                                    return ${opts.kubernetesOptions.stageClusters.collect { "\"$it:selected\"" }.toString().replaceAll(" ", "")}
                                }
                            }
                            return ''
                        """
                    ]
                ]
            ]
        )
    }
    properties([
        parameters(jobParametersDefinitions),
        disableConcurrentBuilds()
    ])
}

void readJobParametersValues() {
    jobParams.shouldPerformGitflowAction = params.shouldPerformGitflowAction
    jobParams.shouldBuild = params.shouldBuild
    jobParams.runCheckmarxScan = params.runCheckmarxScan
    jobParams.runSecretsScan = params.runSecretsScan
    jobParams.runAquaScan = params.runAquaScan
    jobParams.shouldDeploy = params.shouldDeploy
    jobParams.profile = params.profile
    if (params.customDeployEnvironment != null && params.customDeployEnvironment.trim().length() > 0) {
        jobParams.customDeployEnvironment = params.customDeployEnvironment
    }
    jobParams.clustersForDeploy = getKubernetesClustersToDeploy(params, jobParams.profile)
    jobParams.helmRollbackReleaseAfter = params.helmRollbackReleaseAfter
    jobParams.shouldLaunchAutoTests = params.shouldLaunchAutoTests
    jobParams.testsSuiteNames = params.testsSuiteNames
}

void checkoutSources() {
    if (jobParams.shouldPerformGitflowAction) {
        gitHubUtils.checkout()
    } else {
        checkout scm
    }
}

void setupEnvVariables() {
    parseProjectPropertiesToEnv()
    env.MVN_IMAGE = "docker.art.lmru.tech/maven:${opts.buildOptions.mavenImageTag}"
    if (opts.overridePropertiesOptions.brickNameParam != "keep") {
        env.BRICK_NAME_PARAM = opts.overridePropertiesOptions.brickNameParam
    }
    if (opts.overridePropertiesOptions.domainNameParam != "keep") {
        env.DOMAIN_NAME_PARAM = opts.overridePropertiesOptions.domainNameParam
    }
}

void runGitflow() {
    echo "================ START GITFLOW ================"
    echo "branch = $BRANCH_NAME"

    if (jobInfo.shouldRelease) {
        setJobName "$PROJECT_NAME_PARAM: release"
        echo "mode = release"
    } else if (jobInfo.shouldFinishFeature) {
        setJobName "$PROJECT_NAME_PARAM: finish feature"
        echo "mode = finish feature"
    } else if (jobInfo.shouldFinishHotfix) {
        setJobName "$PROJECT_NAME_PARAM: finish hotfix"
        echo "mode = finish hotfix"
    } else if (jobInfo.shouldFinishRelease) {
        setJobName "$PROJECT_NAME_PARAM: finish release"
        echo "mode = finish release"
    }

    gitflowNextStep([
        githubCredentialsId: opts.githubCredentialsId,
        mvnPhaseAfterGitflow: jobParams.shouldBuild ? 'package' : 'test',
        isGithubFlow: opts.gitflowOptions.developmentBranch == 'master',
        shouldQualityGateFailRelease: opts.gitflowOptions.shouldQualityGateFailRelease
    ]) // this checks out new branch and updates env variables

    echo "branch = $BRANCH_NAME"
    echo "tag = ${env.TAG_NAME ? TAG_NAME : 'null'}"
    echo "projectVersionParam = $PROJECT_VERSION_PARAM"
    echo "lastCommitSha = ${sh(script: "git log -n 1 --pretty=format:'%H'", returnStdout: true)}"
    echo "================ END GITFLOW ================"
}

void buildAppAndImage(boolean needMavenBuild) {
    echo "================ START BUILD ================"
    String imageTag = resolveDockerImageTag(BRANCH_NAME)
    boolean shouldReleaseImage = env.TAG_NAME != null || BRANCH_NAME in ['develop', 'master']
    String shortImageName = resolveAppImageName(false)
    echo "shortImageName = $shortImageName"
    echo "imageTag = $imageTag"
    echo "shouldReleaseImage = $shouldReleaseImage"
    setJobName "$PROJECT_NAME_PARAM:$imageTag build"
    if (needMavenBuild) {
        stage('Maven: package') {
            mavenUtils.runPhase('package')
        }
        waitQualityGate(false) // do not fail job
    }
    stage('Build & push docker image') {
        dockerUtils.jvmBuild(
            baseImagePath: "${opts.buildOptions.baseImagePath}:${opts.buildOptions.baseImageTag}",
            artifactoryImageName: shortImageName,
            projectName: opts.buildOptions.artifactoryDomain ?: DOMAIN_NAME_PARAM,
            imageTags: [ imageTag ],
            shouldPromoteImageTagsToRelease: shouldReleaseImage,
            customLibraryList: opts.buildOptions.imageCustomLibraryList,
            applicationFilesDir: 'target/application',
            needUnzip: true,
            securityScan: jobParams.runAquaScan
        )
    }
    echo "================ END BUILD ================"
}

void deployToManyClustersWithRollback() {
    echo "================ START DEPLOY ================"
    boolean shouldDeployWithTag = env.TAG_NAME // job started from tag (or app was released) or not

    if (!jobParams.clustersForDeploy) {
        error "params.clustersForDeploy must not be empty when params.shouldDeploy = true"
    }
    if (jobParams.profile == "prod") {
        if (!(BRANCH_NAME == "master" || shouldDeployWithTag)) {
            error "You can deploy on PROD only from master branch or tag. Current git reference is '$BRANCH_NAME'"
        }
        if (jobParams.customDeployEnvironment) {
            error 'You should not use customDeployEnvironment for PROD profile'
        }
        if (!shouldDeployWithTag) {
            unstable(
                'You are going to deploy branch version on PROD. ' +
                'This is unstable and thus not recommended approach. ' +
                'Create and deploy release of your application if possible.'
            )
        }
    }

    String deployEnvironment = jobParams.profile
    if (jobParams.customDeployEnvironment) {
        deployEnvironment = jobParams.customDeployEnvironment
    }

    String kuberNamespace = "${opts.kubernetesOptions.baseNamespace}-$deployEnvironment"
    String helmReleaseName = opts.kubernetesOptions.helmReleaseNameBuilder(
        env.DOMAIN_NAME_PARAM ?: '',
        env.BRICK_NAME_PARAM ?: '',
        env.PROJECT_NAME_PARAM,
        deployEnvironment
    )
    if (helmReleaseName.length() > 53) {
        error "Too long helm release name: '$helmReleaseName'. It must be up to 53 characters long. Provide custom helmReleaseNameBuilder."
    }

    String imageName = resolveAppImageName(true)

    if (opts.vaultOptions.enabled) {
        buildValuesYmlUsingVault()
    }

    String imageTag = resolveDockerImageTag(BRANCH_NAME)
    def commitSha = sh(script: "git log -n 1 --pretty=format:'%H'", returnStdout: true)

    echo "kuberClusterNames = ${jobParams.clustersForDeploy}"
    echo "kuberNamespace = $kuberNamespace"
    echo "helmReleaseName = $helmReleaseName"
    echo "profile = ${jobParams.profile}"
    echo "imageName = $imageName"
    echo "imageTag = $imageTag"
    echo "commitSha = $commitSha"

    setJobName "$PROJECT_NAME_PARAM:$imageTag on $deployEnvironment"

    if (shouldDeployWithTag) {
        jobInfo.releaseTasksList = jiraUtils.findTasksByCommitsFromTagToPreviousTag(jobInfo.targetVersion)
    }

    dir(PIPELINE_SOURCE_FOLDER) {
        git(
            branch: opts.kubernetesOptions.helmChartVersion,
            changelog: false,
            credentialsId: opts.githubCredentialsId,
            url: SV_PIPELINE_GITHUB_URL
        )
    }

    // some monkey-job to set helm's appVersion field
    String chartFileLocation = "$PIPELINE_SOURCE_FOLDER/helm/Chart.yaml"
    LinkedHashMap<String, String> chartValuesMap = readYaml(file: chartFileLocation)
    if (chartValuesMap.containsKey("appVersion")) { // may be false, when using older chart version
        String tempLocation = "$PIPELINE_SOURCE_FOLDER/helm/Chart_tmp.yaml"
        chartValuesMap.appVersion = imageTag
        writeYaml(file: tempLocation, data: chartValuesMap)
        sh "rm $chartFileLocation && mv $tempLocation $chartFileLocation"
    }

    // Random string to force helm perform real redeploy even if the same exact commit is deployed
    String randomString = sh(script: "head /dev/urandom | tr -dc a-z0-9 | head -c12", returnStdout: true)

    Set<String> successfullyDeployedToClusters = []

    try {
        jobParams.clustersForDeploy.each { clusterName ->
            String ingressHostname = "${kuberNamespace}-${clusterName}.apps.lmru.tech"

            echo "ClusterName = $clusterName"
            echo "ingressHostname = $ingressHostname"

            // main deployment stage
            deployWithEmbeddedHelmChart(
                clusterName,
                kuberNamespace,
                helmReleaseName,
                ingressHostname,
                imageName,
                imageTag,
                jobParams.helmRollbackReleaseAfter,
                randomString
            )

            successfullyDeployedToClusters.add(clusterName)
            echo "Successfully deployed to $clusterName"
        }
    } catch (Exception ex) {
        echo "Error during deploy to kubernetes"
        printException(ex)
        if (opts.kubernetesOptions.autoRollbackMulticlusterErrors) {
            successfullyDeployedToClusters.each {
                rollbackDeploy(it, kuberNamespace, helmReleaseName)
            }
        } else {
            echo "autoRollbackMulticlusterErrors is disabled"
        }
        error("Error during deploy to kubernetes: ${ex.message}")
    }
    echo "================ END DEPLOY ================"
}

void buildValuesYmlUsingVault() {
    if (opts.vaultOptions.applicationApproleCredentialsId == null) { // old way
        vaultUtils.secretsToEnv(
            namespace: opts.vaultOptions.namespace,
            vaultPath: opts.vaultOptions.approleSecretLocation,
            appRoleCredential: opts.vaultOptions.credentialsId
        )
        envSubstituteValuesYml()
    } else { // new, better way
        withCredentials([usernamePassword(
            credentialsId: opts.vaultOptions.applicationApproleCredentialsId,
            usernameVariable: 'VAULT_APP_ROLE_ID',
            passwordVariable: 'VAULT_APP_SECRET_ID'
        )]) {
            String valuesStr = readFile 'values.yml'
            if (!valuesStr.contains('default: ${VAULT_APP_ROLE_ID}') || !valuesStr.contains('default: ${VAULT_APP_SECRET_ID}')) {
                error(
                    'Using vault.applicationApproleCredentialsId, your values.yml must contain environment placeholders ' +
                    'default: ${VAULT_APP_ROLE_ID} and default: ${VAULT_APP_SECRET_ID}. ' +
                    'Check helm/values_example.yaml if you want. Don\'t forget to check your bootstrap/application.yml also.'
                )
            }
            envSubstituteValuesYml()
        }
    }
}

void envSubstituteValuesYml() {
    sh "envsubst < values.yml > values_full.yml"
    sh "mv -f values_full.yml values.yml"
}

void buildJobInfo() {
    jobInfo.buildUrl = BUILD_URL
    jobInfo.buildNumber = BUILD_NUMBER as int
    jobInfo.shouldBuild = jobParams.shouldBuild
    jobInfo.shouldDeploy = jobParams.shouldDeploy
    jobInfo.clustersForDeploy = jobParams.clustersForDeploy
    if (jobParams.shouldPerformGitflowAction) {
        if (BRANCH_NAME == opts.gitflowOptions.developmentBranch) {
            jobInfo.shouldRelease = true
        } else if (BRANCH_NAME.startsWith('feature/')) {
            jobInfo.shouldFinishFeature = true
        } else if (BRANCH_NAME.startsWith('release/')) {
            jobInfo.shouldFinishRelease = true
        } else if (BRANCH_NAME.startsWith('hotfix/')) {
            jobInfo.shouldFinishHotfix = true
        } else {
            error "Unsupported branch for gitflow: $BRANCH_NAME"
        }
    }

    jobInfo.startVersion = BRANCH_NAME
    if (jobInfo.shouldRelease || jobInfo.shouldFinishHotfix || jobInfo.shouldFinishRelease) {
        // after gitflow, created tag will be checked out and used
        jobInfo.targetVersion = PROJECT_VERSION_PARAM.minus("-SNAPSHOT")
    } else if (jobInfo.shouldFinishFeature) {
        jobInfo.targetVersion = opts.gitflowOptions.developmentBranch
    } else { // maybe tag, master, develop or other branch
        jobInfo.targetVersion = jobInfo.startVersion
    }

    jobInfo.deployEnvironment = jobParams.profile
    if (jobParams.customDeployEnvironment) {
        jobInfo.deployEnvironment = jobParams.customDeployEnvironment
    }
    jobInfo.author = userLaunchedJob.getNameWithoutLdap()

    echo "JobInfo:"
    printProperties(jobInfo)
}

void overrideJobParamsOnGithubEvent() {
    // push to PR-branch
    if (env.CHANGE_ID && userLaunchedJob.isSystem()) {
        opts.githubOptions.onPullRequest(jobParams)
        return // do not execute yet another script
    }
    // push to non-PR-branch
    if (opts.githubOptions.onPushEvent && userLaunchedJob.isSystem()) {
        Map.Entry<String, Closure<Void>> matchedRule = opts.githubOptions.onPushEvent.find { String branchPrefix, Closure<Void> script ->
            env.BRANCH_NAME.startsWith(branchPrefix)
        }
        if (matchedRule) {
            echo "Git reference matched onPushEvent branch prefix [${matchedRule.key}]. Executing provided user script."
            matchedRule.value.call(jobParams)
        }
    }
}

void setJobName(String name) {
    currentBuild.displayName = name
}

void deployWithEmbeddedHelmChart(
    String fullClusterName,
    String kuberNamespace, // domain-(brick|microservice)-(dev/test/preprod/prod or branch)
    String helmReleaseName, // domain.brick?.microservice.(dev/test/preprod/prod or branch)
    String ingressHostname,
    String imageName,
    String imageTag,
    String timeout,
    String helmSha
) {
    stage("Deploy to $fullClusterName, ${jobParams.profile}") {
        kubernetesUtils.withKuberDeployer(clusterName: fullClusterName, lmSaCredentialsId: opts.lmSaCredentialsId) {
            // create empty values-*cluster*.yml if not exist to avoid error in helm3 invocation
            sh("touch /pwd/values-${fullClusterName}.yml")
            sh(
                "helm3 upgrade --install --atomic --wait --create-namespace " +
                    "-f /pwd/values.yml " +
                    "-f /pwd/values-${fullClusterName}.yml " +
                    "--set profile=${jobParams.profile} " +
                    "--set ingress.hostName=$ingressHostname " +
                    "--set imageName=$imageName " +
                    "--set imageTag=$imageTag " +
                    "--set sha=$helmSha " +
                    "--timeout $timeout " +
                    "--namespace $kuberNamespace " +
                    "$helmReleaseName $PIPELINE_SOURCE_FOLDER/helm"
            )
        }
    }
}

void rollbackDeploy(String fullClusterName, String kuberNamespace, String helmReleaseName) {
    stage("Rollback deploy on $fullClusterName") {
        String getRevision = "helm3 history --max=1 $helmReleaseName --namespace $kuberNamespace -ojson | jq '.[0].revision'"
        kubernetesUtils.withKuberDeployer(clusterName: fullClusterName, lmSaCredentialsId: opts.lmSaCredentialsId) {
            String revision = sh(script: getRevision, returnStdout: true).trim()
            if (revision != "1") {
                echo "Rolling back to previous helm release. fullClusterName=$fullClusterName"
                sh("helm3 rollback $helmReleaseName 0 --namespace $kuberNamespace")
            } else {
                echo "Uninstalling helm release. fullClusterName=$fullClusterName"
                sh("helm3 uninstall $helmReleaseName --namespace $kuberNamespace")
            }
        }
    }
}

String resolveAppImageName(boolean fullName) {
    String firstPart = ""
    if (fullName) {
        if (opts.buildOptions.artifactoryDomain) {
            firstPart = "docker-${opts.buildOptions.artifactoryDomain}.art.lmru.tech/"
        } else {
            firstPart = "docker-${DOMAIN_NAME_PARAM}.art.lmru.tech/"
        }
    }
    String shortName = ""
    if (opts.buildOptions.artifactoryAppImagePath) { // new way
        shortName = opts.buildOptions.artifactoryAppImagePath
    } else { // old way
        if (BRICK_NAME_PARAM) {
            shortName += BRICK_NAME_PARAM + "/"
        }
        shortName += PROJECT_NAME_PARAM
    }
    return firstPart + shortName
}

// possible return values: 0.1, master, develop, LB-123, other-branch, hotfix-1.2.3.4
String resolveDockerImageTag(String gitRef) {
    return gitRef.replace("feature/", "").replace("/", "-")
}

List<String> getKubernetesClustersToDeploy(Map params, String profile) {
    if (profile == 'prod') {
        Integer nProdClusterOptions = opts.kubernetesOptions.prodClusters?.size() ?: 0
        if (nProdClusterOptions > 1) {
            return getKubernetesClustersToDeployFromParams(params)
        } else if (nProdClusterOptions == 1) {
            return opts.kubernetesOptions.prodClusters.toList()
        } else {
            return [ opts.kubernetesOptions.clusterName ]
        }
    } else { // stage
        Integer nStageClusterOptions = opts.kubernetesOptions.stageClusters?.size() ?: 0
        if (nStageClusterOptions > 1) {
            return getKubernetesClustersToDeployFromParams(params)
        } else if (nStageClusterOptions == 1) {
            return opts.kubernetesOptions.stageClusters.toList()
        } else {
            return [ opts.kubernetesOptions.clusterName + '-stage' ]
        }
    }
}

List<String> getKubernetesClustersToDeployFromParams(Map params) {
    String paramStr = params.clustersForDeploy
    if (paramStr == null || !paramStr.replaceAll(",", "").trim()) {
        return null
    }
    return paramStr.split(',').toList()
}

void printProperties(obj) {
    obj.properties.each {
        if (it.key != "class") {
            def keyWithValue = it.key + ": " + it.value
            String objClassName = obj.getClass().getName()
            if (objClassName.contains('Options') && !objClassName.contains('Pipeline')) {
                echo("        " + keyWithValue)
            } else {
                if (it.value.getClass().getName().contains('Options')) {
                    echo("    " + it.key + ":")
                    printProperties(it.value)
                } else {
                    echo("    " + keyWithValue)
                }
            }
        }
    }
}

void notifyVoyager() {
    stage("Notify Voyager") {
        voyagerUtils.sendDeployResults( // safe to call, it catches possible exceptions
            serviceName: opts.notificationOptions.voyagerOptions.serviceName,
            version: jobInfo.targetVersion,
            app: opts.notificationOptions.voyagerOptions.productName,
            productId: opts.notificationOptions.voyagerOptions.productId,
            envName: jobInfo.deployEnvironment,
            issues: jobInfo.releaseTasksList.join(' ')
        )
    }
}

void launchAutoTests() {
    def envName = jobParams.profile
    def testsSuiteNames = jobParams.testsSuiteNames
    def stageName = "Auto tests on $envName"

    stage(stageName) {
        echo "Launching auto-tests suite(s) '$testsSuiteNames' on '$envName'"
        build(
            job: opts.autoTestsOptions.jobUrl,
            parameters: [
                string(name: 'suiteNames', value: testsSuiteNames),
                string(name: 'environment', value: envName)
            ]
        )
    }
}

void configureExtensionPoints(Map argsMap) {
    if (!argsMap.containsKey('extensionPoints')) {
        return
    }
    try {
        argsMap.extensionPoints.each { pointName, value ->
            ExtensionPointEnum extensionPointEnum = ExtensionPointEnum.valueOfName(pointName)
            extensionPointsMap.put(
                extensionPointEnum,
                new ExtensionExecutable(
                    value.customStageName,
                    extensionPointEnum,
                    value.script
                )
            )
        }
    } catch (Exception ex) {
        error("Looks like extensionPoints section has incorrect format: ${ex.message}")
    }
    extensionPointData.pipelineOpts = opts
    extensionPointData.jobParams = jobParams
    extensionPointData.jobInfo = jobInfo
}

void executeExtensionIfExists(ExtensionPointEnum extensionPoint) {
    ExtensionExecutable ee = extensionPointsMap.get(extensionPoint)
    if (ee != null) {
        executeExtensionPoint(ee)
    }
}

// may throw exception
void executeExtensionPoint(ExtensionExecutable ee) {
    String stageName = ee.extensionPoint.stageName
    if (ee.customStageName) {
        stageName = ee.customStageName
    }
    if (stageName != ExtensionPointEnum.DISABLED_STAGE_LABEL) {
        stage(stageName) {
            ee.script(extensionPointData)
        }
    } else {
        ee.script(extensionPointData)
    }
}

void runCheckmarxScan() {
    stage("Checkmarx scan") {
        checkmarxUtils.runCheckmarxScan_ZeroLicense(getCXReport: true)
    }
}

void runSecretsScan() {
    stage("Secrets scan") {
        securityUtils.secretsScan([:])
    }
}

void printException(Exception ex) {
    echo 'Caught exception. Trimmed stacktrace:'
    def sw = new StringWriter()
    def pw = new PrintWriter(sw)
    ex.printStackTrace(pw)
    String fullStackTrace = sw.toString()
    List<String> usefulLines = fullStackTrace.readLines().findAll {
        !it.startsWith("\tat ") || it.contains(".groovy:") // keep only exception class, message and groovy methods
    }
    echo usefulLines.join("\n")
}

</user_query>


text:
Да, этот кусок подтверждает гипотезу: в `svPipeline` **нет явной настройки Maven mirror/repository** на уровне джобы.

Критичное место — только это:

- `buildAppAndImage()` → `mavenUtils.runPhase('package')`

То есть источник зависимостей определяется **внутри `mavenUtils` / Jenkins Maven settings**, а не этим `Jenkinsfile`.
Поэтому `pom.xml` с `mvn-replenishment` может игнорироваться, если в Jenkins settings стоит зеркало `mirrorOf=*` на `.../mvn`.

Что из этого следует:

- В текущем пайплайне нет параметра вида `mavenSettingsConfig`, `settingsXml`, `mirrorUrl`.
- `artifactoryDomain: 'replenishment'` здесь относится к Docker image path, не факт что к Maven (скорее всего — не к Maven).

Что просить у DevOps/владельцев shared library:

- Проверить, какой `settings.xml` использует `mavenUtils.runPhase`.
- Либо поменять mirror в этом settings на `https://art.lmru.tech/mvn-replenishment`,
- Либо сделать per-project settings для этой джобы и прокинуть в `mavenUtils` (если в lib есть такой параметр).
- Быстрая верификация: запуск с `mvn -X help:effective-settings` и проверка активного mirror URL.

Если хотите, могу сформулировать короткий технический комментарий для треда Артема (готовый текст «что именно проверить в shared library + Jenkins Managed files»).

**Explaining Maven mirror setup**


text:
<user_query>
[<- back to README](/README.md)
### `executeCommand(String command)`
Method to run arbitrary maven commands with:
* SonarQube
* settings.xml from jenkins
* docker environment (often needed to run integration tests (e.g. with testcontainers))

#### Need to work:
Maven settings.xml file

#### Optional:
MVN_VERSION env for choice version of maven container (for example if you use java 11 or custom mvn image)

#### Parameters
* **command** – e.g. `clean test` or call to some maven plugin. You may pass additional arguments like `-DmyArg=ololo`

#### Example usage
```groovy
// Load the library to your pipeline
@Library('common-utils') _

mavenUtils.executeCommand('clean package -Dmaven.test.skip=true')
mavenUtils.executeCommand('install')
```
---
### `invoke(Map args)`
Method to run arbitrary maven commands with:
* settings.xml from jenkins
* docker environment (often needed to run integration tests (e.g. with testcontainers))

#### Parameters
##### Required params:
* **cmd** – maven full command, e.g. `mvn clean test`. Should start from `mvn`.

##### Optional params:
* **settingsFile** - `settings.xml` file path. If empty used from `MVN_SETTINGS_FILE_ID`
environment variable. Uses `settings.xml` by default.
* **image** - docker image. Uses `maven:3.6.3-jdk-11` by default.
* **repositoryCacheEnabled** - use maven repository cache. `true` by default.
* **privileged** - privileged rights for docker image. `true` by default.
* **returnStdOut** - return stdout string. `false` by default.

#### Example usage
```groovy
// Load the library to your pipeline
@Library('common-utils') _

mavenUtils.invoke(cmd: 'mvn clean package -Dmaven.test.skip=true')
mavenUtils.invoke(cmd: 'mvn install')
```
---
### `runPhase(String mvnPhase, boolean shouldSkipTests)`
Method to run maven phases (test, package, deploy etc.)

#### Need to work (only when shouldSkipTests = false):
Global env (you can get them from parseProjectPropertiesToEnv method):
* `DOMAIN_NAME_PARAM` with name of your domain (for example Solution/Delivery/Service-platform is domain name)
* `BRICK_NAME_PARAM` with name of your brick (if exist)
* `PROJECT_NAME_PARAM` with name of your application
* `PROJECT_VERSION_PARAM` with version of your application

#### Optional:
MVN_VERSION env for choice version of maven container (for example if you use java 11 or custom mvn image)

#### Parameters
* **mvnPhase** – e.g. test, package, verify. You may pass additional arguments like `-DmyArg=ololo`
* **shouldSkipTests** (default: `false`) - skip tests, if you have already run it. Sonar plugin will not be executed, too.

#### Example usage
```groovy
// Load the library to your pipeline
@Library('common-utils') _

mavenUtils.runPhase('deploy')
mavenUtils.runPhase('package', true)
```
---
### `getMvnImageName(String mavenImageTag)`
Return Docker image name for Maven

#### Parameters
* **mavenImageTag** (default: `"latest"`) - tag of the desired image (for example, one from the
[official maven images](https://hub.docker.com/_/maven) such as `3.6.3-jdk-8`, `3.8.6-jdk-11`, `3.9.0-eclipse-temurin-17` or even `3.9.0`)

#### Example usage
```groovy
// Load the library to your pipeline
@Library('common-utils') _

mavenUtils.getMvnImageName('3.6.3-jdk-11')
```
</user_query>


text:
Отлично, это уже прямое подтверждение причины.

`mavenUtils.runPhase()` в вашем пайплайне берёт `settings.xml` из Jenkins (через дефолт/`MVN_SETTINGS_FILE_ID`), поэтому `pom.xml` с `mvn-replenishment` может игнорироваться зеркалом из settings.

Что это значит practically:

- проблема **не в `pom.xml`**;
- проблема в Jenkins `settings.xml`/`MVN_SETTINGS_FILE_ID`, который подсовывает `mavenUtils`.

Что нужно сделать:

- создать/обновить managed Maven settings для проекта с mirror на `https://art.lmru.tech/mvn-replenishment`;
- привязать его к джобе через `MVN_SETTINGS_FILE_ID` (или через параметр `settingsFile`, если вызов идёт через `mavenUtils.invoke`).

Критичный фрагмент в settings:

```xml
<mirrors>
  <mirror>
    <id>artifactory-replenishment</id>
    <mirrorOf>*</mirrorOf>
    <url>https://art.lmru.tech/mvn-replenishment</url>
  </mirror>
</mirrors>
```

Текст для треда Артема (можно копипастить):
> Проверил пайплайн: сборка идёт через `mavenUtils.runPhase`, он использует `settings.xml` из Jenkins (`MVN_SETTINGS_FILE_ID`/default), а не репозитории из `pom.xml` напрямую. Поэтому `mvn-replenishment` из pom может перекрываться mirror’ом на `.../mvn`. Нужно обновить managed Maven settings для джобы (или global, если он общий) и выставить mirror на `https://art.lmru.tech/mvn-replenishment`.

**Summarizing Jenkins settings fix**


text:
<user_query>
@lmru--sv-jvm-k8s-pipeline вот еще либа sv-pipeline
</user_query>


text:
Принял, сейчас посмотрю в `sv-pipeline`, где именно прокидывается Maven `settings.xml`/`MVN_SETTINGS_FILE_ID`, чтобы дать точную точку изменения, а не общий совет.

**Preparing to explore new library**


text: **Refining glob pattern to locate file**


text:
Нашёл `sv-pipeline` прямо в репозитории: там действительно только вызовы `mavenUtils.runPhase(...)`, без передачи `settingsFile`. Сейчас доберу ещё один файл (`svPipelineForLibrary`), чтобы точно исключить скрытый override.

**Considering inspection of pipeline class**


text: **Verifying presence of Maven utility**


text:
Да, в `sv-pipeline` картина та же: там Maven запускается только через `mavenUtils.runPhase(...)` и нигде не передаётся `settingsFile`/custom mirror.

Итог по цепочке:
- `Jenkinsfile` → `svPipeline.run(...)`
- `svPipeline` → `mavenUtils.runPhase('package')`
- `mavenUtils` по документации берёт `settings.xml` из Jenkins (`MVN_SETTINGS_FILE_ID`/default)

Значит точка исправления всё равно в Jenkins managed `settings.xml` (или в `common-utils` `mavenUtils`, если хотите поддержать per-project settings параметром).

Если нужно, следующим шагом могу помочь составить точный запрос в DevOps в формате: **что проверить в job/folder/global, какой `MVN_SETTINGS_FILE_ID`, и какой mirror должен быть в XML**.

**Mentioning nested pipeline setup**


text:
<user_query>
@lmru--devops--pipeline-shared-library вот common-utils
</user_query>


text:
Отлично, тогда добьём до конца: сейчас посмотрю `common-utils` (`lmru--devops--pipeline-shared-library`) и найду точное место, где выбирается `settings.xml`/mirror, чтобы дать вам уже конкретный фикс.

**Planning repo path inspection**


text:
Теперь картина полностью закрыта: причина найдена в `common-utils`.

В `lmru--devops--pipeline-shared-library/vars/mavenUtils.groovy`:

- `invoke()` берёт settings file id так:
  - `args.settingsFile ?: env.MVN_SETTINGS_FILE_ID ?: 'settings-xml'`
- дальше всегда подставляет его через:
  - `configFileProvider([configFile(fileId: mvnSettingsFile, variable: 'MAVEN_SETTINGS')])`
  - и запускает Maven с `-s "$MAVEN_SETTINGS"`.

То есть у вас **жёстко используется Jenkins Managed settings**, а не `pom.xml`-репы напрямую.
Если `settings-xml` (или `MVN_SETTINGS_FILE_ID`) указывает mirror на `https://art.lmru.tech/mvn`, то `mvn-replenishment` из `pom.xml` будет перекрыт.

Что менять:
- либо обновить managed file с id `settings-xml`,
- либо для этой джобы задать `MVN_SETTINGS_FILE_ID` на отдельный settings с mirror `https://art.lmru.tech/mvn-replenishment`,
- либо (если хотите кодом) вызывать `mavenUtils.invoke(..., settingsFile: '...')`, но в вашем `svPipeline` сейчас используется `runPhase`, без передачи `settingsFile`.

Если хотите, предложу минимальный PR-вариант в `sv-pipeline`: добавить параметр `build.mvnSettingsFileId` и выставлять `env.MVN_SETTINGS_FILE_ID` в `setupEnvVariables()`. Это даст per-project контроль без правок Jenkins global settings.

**Advising Maven settings env setup**


text:
<user_query>
как для этой джобы задать MVN_SETTINGS_FILE_ID на отдельный settings с mirror https://art.lmru.tech/mvn-replenishment,
</user_query>


text:
Для вашей джобы самый практичный способ — через `Jenkinsfile` (чтобы не зависеть от UI-прав на folder/job).

1) В Jenkins создать отдельный Managed file (`Config File Provider`):
- тип: **Maven settings.xml**
- `File ID`, например: `settings-xml-replenishment`
- в `<mirrors>` поставить `https://art.lmru.tech/mvn-replenishment`

2) В `Jenkinsfile` этой джобы перед `svPipeline.run(...)` задать env:

```groovy
@Library(['common-utils', 'sv-pipeline']) _

env.MVN_SETTINGS_FILE_ID = 'settings-xml-replenishment'

svPipeline.run(
  // ...
)
```

Почему это сработает: `mavenUtils.invoke()` читает `env.MVN_SETTINGS_FILE_ID`, и если он задан — использует его вместо дефолтного `settings-xml`.

Альтернатива без правки `Jenkinsfile`:
- в конфиге job/folder добавить environment variable `MVN_SETTINGS_FILE_ID=settings-xml-replenishment` (если у вас включён соответствующий плагин/раздел Env Vars).

Проверка:
- в логе Maven с `-X` / `help:effective-settings` должен появиться активный mirror `.../mvn-replenishment`.

**Explaining Jenkins env var setup**


text:
<user_query>
@Jenkinsfile как можно сделать в дженкинс файле джобы?
</user_query>


text:
Сделаю прямо в `Jenkinsfile` самый безопасный вариант: оберну `svPipeline.run(...)` в `withEnv`, чтобы для этой джобы подставлялся отдельный `MVN_SETTINGS_FILE_ID` без влияния на другие пайплайны.

**Planning Jenkinsfile env var patch**


text:
В `Jenkinsfile` это делается так — через `withEnv`, чтобы только эта джоба использовала отдельный Maven settings:

```groovy
withEnv(['MVN_SETTINGS_FILE_ID=settings-xml-replenishment']) {
    svPipeline.run(
        // ...
    )
}
```

Я уже внес это в ваш `Jenkinsfile` (`Jenkinsfile` в корне репозитория).

Что важно:
- В Jenkins должен существовать managed Maven settings с `fileId = settings-xml-replenishment`.
- В этом settings должен быть mirror на `https://art.lmru.tech/mvn-replenishment`.

Если нужно, следующим шагом дам готовый XML для этого settings (минимальный рабочий шаблон с `mirrors` + `servers`).


text:
<user_query>
а нельзя вызовом в sv-pipeline сделать, чтобы pom.xml который уже есть использовался?
</user_query>


text:
Коротко: **в текущем `sv-pipeline` — нет**, отдельного флага/параметра “используй только `pom.xml`” нет.

Почему:
- `svPipeline` вызывает `mavenUtils.runPhase(...)`
- `mavenUtils` всегда запускает Maven с `-s <settings from Jenkins>`
- значит Jenkins `settings.xml` всегда участвует

Но есть нюанс: `pom.xml` **всё равно используется**, пока его не перекрывает `<mirrors>` (обычно `mirrorOf=*`) в settings.

То есть варианты такие:

- **Без правок библиотек:** использовать `MVN_SETTINGS_FILE_ID` на settings, где нет агрессивного mirror (`*`) — тогда репы из `pom.xml` будут работать.
- **С правкой `sv-pipeline`:** добавить параметр типа `build.mvnSettingsFileId` и прокидывать его в `env.MVN_SETTINGS_FILE_ID`.
- **Радикально:** менять `mavenUtils`, чтобы можно было запускать без `-s` (сейчас так не реализовано).

Для вашей задачи самый быстрый и безопасный путь — тот, что уже сделали: `withEnv(['MVN_SETTINGS_FILE_ID=...'])` + правильный managed settings.

**Explaining Maven settings impact**


text:
<user_query>
нашел, settings xml
<?xml version="1.0" encoding="UTF-8"?>
<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>snapshots</id>
            <username>lm-sa-replenishment</username>
            <password>pass</password>
        </server>
        <server>
            <id>releases</id>
            <username>lm-sa-replenishment</username>
            <password>pass</password>
        </server>
    </servers>
    <mirrors>
        <mirror>
            <id>mvn-common</id>
            <name>LMRU artifactory - common mirror</name>
           <url>https://art.lmru.tech/mvn</url>
          <mirrorOf>*</mirrorOf>
       </mirror>
    </mirrors>
</settings>
</user_query>


text:
Да, вот это и есть корень проблемы:

- у вас `mirrorOf=*`
- и URL зеркала `https://art.lmru.tech/mvn`

Из-за этого **все** репозитории из `pom.xml` принудительно уходят в `mvn`, включая ваш `mvn-replenishment`.

Что нужно поменять в этом `settings.xml`:

```xml
<mirrors>
    <mirror>
        <id>mvn-replenishment</id>
        <name>LMRU artifactory - replenishment mirror</name>
        <url>https://art.lmru.tech/mvn-replenishment</url>
        <mirrorOf>*</mirrorOf>
    </mirror>
</mirrors>
```

И желательно добавить `server` с тем же `id`, если репо требует auth на чтение:

```xml
<server>
    <id>mvn-replenishment</id>
    <username>lm-sa-replenishment</username>
    <password>...</password>
</server>
```

---

У вас сейчас два пути (любой один):

- **Либо** правите текущий managed file (`settings-xml`) и оставляете всё как есть.
- **Либо** создаёте новый (`settings-xml-replenishment`) и используете его через
  `withEnv(['MVN_SETTINGS_FILE_ID=settings-xml-replenishment'])` в `Jenkinsfile` (мы это уже сделали).

Если хотите, могу дать готовый полный `settings.xml` под ваш кейс (минимальный и безопасный шаблон).

**Explaining Maven mirror and credentials setup**