종속성 유지

작성자

카테고리:

← 피드로
DEV Community · Brett Ryan · 2026-08-21 개발(SW)
Cover image for Keeping Dependencies Current

Brett Ryan

Working on a project of almost any size will almost always require the use of external dependencies that can often put us in a position of alert fatigue where we might have to chase our projects for CVE’s that have been announced where it can feel like constantly plugging a dam. Using a technique to automate this chore which can be reviewed at a later date is extremely helpful.

There are widely available tools in the ecosystem, however; I’m presenting this as a fun exercise to demonstrate how we can get in touch with the machinery that we can implement to do this.

What we will do:

  • Use start.spring.io to create a simple project.
  • Configure pom project with specific dependency rules.
  • Create dependencies.sh script.
  • Add GitLab CI job to perform automatic dependency checks on a chore/version-updates branch.

Full Script

You can obtain a copy of the full script which has been published as a public GitLab snippet.

Create Your Project

Using the spring starter at start.spring.io, create a maven based project. As we’re using the versions-maven-plugin we will require this to be a maven project.

You can use this template which is a bookmark of a spring 4.0.6 project using maven and JDK 26.

Configure Project to Ignore pre-releases

I’ve found that in maven central, some projects will release release candidates, milestones, alpha and other releases that I do not want to have my dependency management script to try an upgrade to. My original approach to solve this was to set the maven.version.ignore maven property to ignore these
patterns, like the following:

<properties>
  <maven.version.ignore>(?i).*[\-\.](m|rc|dev|alpha|beta)[\-\.]?[0-9]*</maven.version.ignore>
  <processDependencyManagementTransitive>${processDependencyManagementTransitive}</processDependencyManagementTransitive>
</properties>

Enter fullscreen mode Exit fullscreen mode

This works well, however; if you want to have rules where you may want to be using pre-releases or milestones from specific vendors, i.e. you may be testing the newest spring milestones, you will need to extend this by adding a ruleSet to the build/plugins section of your pom.

<build>
  <plugins>
    <plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>versions-maven-plugin</artifactId>
      <configuration>
        <ruleSet>
          <rules>
            <rule>
              <ignoreVersions>
                <ignoreVersion>
                  <type>regex</type>
                  <version>(?i).*<a href="m|rc|dev|alpha|beta">\-\.</a>[\-\.]?[0-9]*</version>
                </ignoreVersion>
              </ignoreVersions>
            </rule>
            <rule>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-parent</artifactId>
            </rule>
            <rule>
              <groupId>org.springframework.modulith</groupId>
              <artifactId>spring-modulith-bom</artifactId>
            </rule>
          </rules>
        </ruleSet>
        <processDependencyManagementTransitive>${processDependencyManagementTransitive}</processDependencyManagementTransitive>
      </configuration>
    </plugin>
  </plugins>
</build>

Enter fullscreen mode Exit fullscreen mode

Note that I’ve also set the property processDependencyManagementTransitive, keep this in your properties section as our script will set this later.

Dependency Update Script

We will now create a script that will perform the dependency update check and optionally perform a git commit if updates were found.

There are two sets of maven goals we could use

To show only:

  • versions:display-dependency-updates
  • versions:display-plugin-updates
  • versions:display-property-updates

Or, to perform the updates my modifying the pom.

  • versions:update-parent
  • versions:update-properties
  • versions:use-latest-versions

Refer to the versions-maven-plugin documentation for more on what these goals do.

We will write a script that gives us the following capabilities:

Usage: dependencies.sh [-cCsStu]

-c  : Perform git commit opening editor for review
-C  : Perform git commit
-s  : Don't perform snapshot updates
-S  : Force update snapshot updates
-t  : Process transitive dependencies
-u  : Perform update

Enter fullscreen mode Exit fullscreen mode

For this script, we will begin with the setup and a getopts block that takes these command line arguments.

#!/usr/bin/env bash

goals="
  versions:display-dependency-updates
  versions:display-plugin-updates
  versions:display-property-updates
"

snaps=""
margs=""

while getopts cChsStu OPTION; do
  case $OPTION in
    h)  echo "
Usage: $0 [-cCsStu]

-c  : Perform git commit opening editor for review
-C  : Perform git commit
-s  : Don't perform snapshot updates
-S  : Force update snapshot updates
-t  : Process transitive dependencies
-u  : Perform update
" >&2
        exit 1
        ;;
    c)  commit_mode=edit ;;
    C)  commit_mode=perform ;;
    s)  snaps="-nsu" ;;
    S)  snaps="-U" ;;
    t)  margs="${margs} -DprocessDependencyManagementTransitive=true" ;;
    u)  goals="
          versions:update-parent
          versions:update-properties
          versions:use-latest-versions
        "
        ;;
  esac
done
shift $((OPTIND - 1))

Enter fullscreen mode Exit fullscreen mode

This allows the user to either view (default) or update (-u). When viewing the caller can test for transitive dependency updates with -t. We can also commit changes automatically with -c to open an editor or have -C perform the commit.

In order to capture the dependencies that were updated, we will tee the output from maven (using ./mvnw here) to allow us to parse the content when complete.

mvn_log="$(mktemp)" || {
  echo "Couldn't create temp file" >&2
  exit 1
}
trap 'rm -f "$mvn_log"' EXIT

echo ./mvnw $snaps $margs $goals
./mvnw $snaps $margs $goals | tee $mvn_log

Enter fullscreen mode Exit fullscreen mode

Here we will parse the output file to list off all our changes. A regular expression here formats the content into the following form:

- {{ DEPENDENCY }}: {{ FROM }} -> {{ TO }}

Enter fullscreen mode Exit fullscreen mode

This format will strip out property managed versions such as ${spring-modulith.version} and replace it with spring-modulith to make our git commit more readable.

changes="$(
  grep "Updated " "$mvn_log"|\
    sed -E 's/^.* Updated (\$\{)?([^.}]*)(.version)?\}? from (.*) to (.*)$/- \2: \4 -> \5/')"

Enter fullscreen mode Exit fullscreen mode

Regular Expression Deep Dive

We’re creating an expression that looks for lines that have the following core pattern:

  • ^.* Updated: Looks from the start of line until we reach the word Updated with a leading and trailing space.
  • (\$\{)?: May contain the sequence ${ which we want to ignore so must remember to skip this capture group.
  • ([^.}]): Capture all characters that are not '.' or '}', there is a slight issue with this in that if you use a property that doesn’t end in .version but does have a dot '.' in it’s name, it might not be processed correctly.
  • (.version)?\}?: skip the optional phrase .version and the optional } character.
  • from (.*) to (.*)$ capture the from and to versions.

If all these sequences match the line, it will be replaced with the capture groups we are interested in - \2: \4 -> \5.

We can now test for this to see if there were any changes detected, if there are, we can then perform the determined git commit option chosen by the caller.

if [ -z "$changes" ]; then
  echo "No changes detected to commit."
else
  commit_msg="[chore] Dependency and plugin updates

$changes"

  echo -e "$commit_msg" > .git/COMMIT_EDITMSG
  if [ -z "$commit_mode" ]; then
    echo -e "\n --- Changes staged for commit ---"
  else
    echo -e "\n--- Committing Changes ---"
    git add pom.xml **/pom.xml 2>/dev/null
    if [ "$commit_mode" == "edit" ]; then
      git commit -m "$commit_msg" -e
    else
      git commit -m "$commit_msg"
    fi
  fi
fi

Enter fullscreen mode Exit fullscreen mode

Setup GitLab Automation

This could now be setup on an automated chore/version-updates branch to be performed once a week or on a schedule of your choosing.

Configure your .gitlab-ci.yml with blocks similar to the following, your configuration may look different, however; in my case I use GraalVM CE edition for my use-cases.

.maven-base:
  image:
    name: ghcr.io/graalvm/jdk-community:25
    entrypoint: [""]
  cache:
    key: "maven-shared-cache"
    paths:
      - .m2/repository
      - .sonar/cache

version_updates:
  extends: .maven-base
  stage: test
  script: |
    microdnf install -y git

    git config --global user.name "GitLab CI Bot"
    git config --global user.email "[email protected]"
    git remote set-url origin https://oauth2:${GITLAB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git

    ./dependencies.sh -u -C
    if [[ "$(git rev-list --count @{u}..HEAD)" != "0" ]]; then
      git pull origin ${CI_COMMIT_REF_NAME} --rebase
      git push origin HEAD:${CI_COMMIT_REF_NAME}
    fi

  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule" && $CI_COMMIT_BRANCH == "chore/version-updates"'
      when: always
    - when: never

Enter fullscreen mode Exit fullscreen mode

GITLAB_TOKEN Best Practices

Ensure that you create a masked Group Access Token or Project Access Token for GITLAB_TOKEN. This token will need to have write_repository access.

In this script we take advantage of the script using the -C option to perform the automated commit, which; can then be detected with git rev-list --count @{u}..HEAD to detect if changes were made. If they were, then we can then push the commits.

Next Steps

There are several ways you could progress this by having full regression tests performed, notifications to slack or teams to notify the development team when the branch has been updated.

You could now integrate this into a release cycle where a QA manager performs validation and owns the chore branch, and; what should be done in the negative scenario where there may be versions that are not desired? Would you make it a practice to include them into the ruleSet? This could even be automated based on build failures.

원문에서 계속 ↗