Skip to content

CI CD Setup

James Maes edited this page Mar 12, 2026 · 1 revision

CI/CD Setup with qqq-orb

This page covers how to configure CircleCI in any QQQ ecosystem repository using the qqq-orb. The orb handles version calculation, building, testing, artifact publishing, and GitHub release creation automatically based on which branch you push to.

Quick Links

Prerequisites

Before configuring a repo, ensure these are in place:

CircleCI Contexts

Your CircleCI organization must have these contexts configured:

Context Secrets Used by
qqq-maven-registry-credentials MAVEN_USERNAME, MAVEN_PASSWORD, GPG_PRIVATE_KEY_B64, GPG_KEYNAME, GPG_PASSPHRASE All Maven projects
qqq-security-credentials (same as above, plus optional scanner tokens) Security scan jobs
build-qqq-sample-app (project-specific env vars) Projects that build with sample data

Individual secrets:

  • GITHUB_TOKEN: Set as a project environment variable in CircleCI. Used by publish_github_release to create GitHub Releases on production and hotfix builds.
  • NPM_TOKEN: Required for Node.js package publishing (set in project or context).

Repository Setup

  • The repo follows GitFlow branching with develop as the default branch.
  • For Maven projects: pom.xml uses <revision>X.Y.Z-SNAPSHOT</revision> for version management.
  • For Node projects: package.json has a standard version field.
  • The repo is followed in CircleCI (Settings > Projects > Set Up Project).

How It Works

The orb maps each GitFlow branch type to a specific CI behavior:

Trigger Orb Job What Happens Artifact Published
Push to feature/* mvn_test_only Build + test, no publish None
Push to develop mvn_publish (snapshot) Build + test + deploy X.Y.Z-SNAPSHOT to Maven Central
Push to release/X.Y mvn_publish (release_candidate) Build + test + deploy X.Y.0-RC.N to Maven Central
Push vX.Y.Z tag mvn_publish (release) Build + test + deploy + GitHub Release X.Y.Z to Maven Central
Push to hotfix/* mvn_publish (hotfix) Build + test + deploy + GitHub Release X.Y.Z+1 to Maven Central

The branch_type parameter tells the orb which version strategy to use. The orb's calculate_version.sh script reads the current pom.xml version and branch name, then calculates the correct next version automatically.


Configuration Templates

Standard Maven QBit / qqq-* Repo

This is the most common configuration. Copy this into .circleci/config.yml:

version: 2.1

orbs:
  qqq-orb: kingsrook/qqq-orb@0.6.5

workflows:

  test_only:
    jobs:
      - qqq-orb/mvn_test_only:
          context: [qqq-maven-registry-credentials]
          filters:
            branches:
              ignore: /(develop|main|release\/.*|hotfix\/.*|integration.*)/
            tags:
              only: []

  publish_snapshot:
    jobs:
      - qqq-orb/mvn_publish:
          context: [qqq-maven-registry-credentials]
          branch_type: snapshot
          filters:
            branches:
              only: [develop]

  publish_release_candidate:
    jobs:
      - qqq-orb/mvn_publish:
          context: [qqq-maven-registry-credentials]
          branch_type: release_candidate
          filters:
            branches:
              only: [/release\/.*/]

  publish_release:
    jobs:
      - qqq-orb/mvn_publish:
          context: [qqq-maven-registry-credentials]
          branch_type: release
          filters:
            branches:
              ignore: /.*/
            tags:
              only: [/v.*/]

  publish_hotfix_release:
    jobs:
      - qqq-orb/mvn_publish:
          context: [qqq-maven-registry-credentials]
          branch_type: hotfix
          filters:
            branches:
              only: [/hotfix\/.*/]

  # Optional: feature branch publishing for cross-repo testing
  # Trigger with: git tag publish-my-feature && git push origin publish-my-feature
  #publish_feature:
  #  jobs:
  #    - qqq-orb/mvn_publish:
  #        context: [qqq-maven-registry-credentials]
  #        branch_type: feature
  #        filters:
  #          branches:
  #            only: [feature/.*]
  #          tags:
  #            only: [/publish.*/]

Note on publish_release filters: The branches: ignore: /.*/ ensures this workflow only runs when a vX.Y.Z tag is pushed, not on the branch merge itself. This is important because calculate_version.sh reads the git tag to determine the release version. See Finalizing a Production Release for the correct tag workflow.

Node.js NPM Package (e.g., qqq-frontend-core)

For repos that publish to NPM:

version: 2.1

orbs:
  qqq-orb: kingsrook/qqq-orb@0.6.5

workflows:

  test_only:
    jobs:
      - qqq-orb/node_test_only:
          context: [qqq-maven-registry-credentials]
          filters:
            branches:
              ignore: /(develop|main|release\/.*|hotfix\/.*|integration.*)/
            tags:
              only: []

  publish_snapshot:
    jobs:
      - qqq-orb/node_publish:
          context: [qqq-maven-registry-credentials]
          branch_type: snapshot
          filters:
            branches:
              only: [develop]

  publish_release_candidate:
    jobs:
      - qqq-orb/node_publish:
          context: [qqq-maven-registry-credentials]
          branch_type: release_candidate
          filters:
            branches:
              only: [/release\/.*/]

  publish_release:
    jobs:
      - qqq-orb/node_publish:
          context: [qqq-maven-registry-credentials]
          branch_type: release
          filters:
            branches:
              ignore: /.*/
            tags:
              only: [/v.*/]

  publish_hotfix_release:
    jobs:
      - qqq-orb/node_publish:
          context: [qqq-maven-registry-credentials]
          branch_type: hotfix
          filters:
            branches:
              only: [/hotfix\/.*/]

Maven + Frontend Hybrid (e.g., qqq-frontend-material-dashboard)

For repos that build a Node.js frontend and package it into a Maven JAR:

version: 2.1

orbs:
  qqq-orb: kingsrook/qqq-orb@0.6.5

workflows:

  test_only:
    jobs:
      - qqq-orb/mvn_frontend_test_only:
          context: [qqq-maven-registry-credentials]
          filters:
            branches:
              ignore: /(develop|main|release\/.*|hotfix\/.*)/
            tags:
              only: []

  publish_snapshot:
    jobs:
      - qqq-orb/mvn_frontend_publish:
          context: [qqq-maven-registry-credentials]
          branch_type: snapshot
          filters:
            branches:
              only: [develop]

  publish_release_candidate:
    jobs:
      - qqq-orb/mvn_frontend_publish:
          context: [qqq-maven-registry-credentials]
          branch_type: release_candidate
          filters:
            branches:
              only: [/release\/.*/]

  publish_release:
    jobs:
      - qqq-orb/mvn_frontend_publish:
          context: [qqq-maven-registry-credentials]
          branch_type: release
          filters:
            branches:
              ignore: /.*/
            tags:
              only: [/v.*/]

  publish_hotfix_release:
    jobs:
      - qqq-orb/mvn_frontend_publish:
          context: [qqq-maven-registry-credentials]
          branch_type: hotfix
          filters:
            branches:
              only: [/hotfix\/.*/]

Node.js Application (e.g., qqq-frontend-next)

For deployed applications that need CI but do NOT publish to NPM:

version: 2.1

orbs:
  qqq-orb: kingsrook/qqq-orb@0.6.5

workflows:

  test_only:
    jobs:
      - qqq-orb/node_app_test_only:
          filters:
            branches:
              ignore: /(develop|main|release\/.*|hotfix\/.*|integration.*)/
            tags:
              only: []

  publish_snapshot:
    jobs:
      - qqq-orb/node_app_build:
          filters:
            branches:
              only: [develop]

  publish_release_candidate:
    jobs:
      - qqq-orb/node_app_build:
          filters:
            branches:
              only: [/release\/.*/]

  publish_release:
    jobs:
      - qqq-orb/node_app_build:
          filters:
            branches:
              ignore: /.*/
            tags:
              only: [/v.*/]

  publish_hotfix_release:
    jobs:
      - qqq-orb/node_app_build:
          filters:
            branches:
              only: [/hotfix\/.*/]

To add Playwright e2e tests after the build:

  publish_snapshot:
    jobs:
      - qqq-orb/node_app_build:
          name: build
          filters:
            branches:
              only: [develop]
      - qqq-orb/node_app_e2e:
          requires: [build]
          filters:
            branches:
              only: [develop]

Developer Workflows

Day-to-Day: Feature Branch Development

# Create feature branch from develop
git checkout develop && git pull
git checkout -b feature/add-new-widget

# Make changes, commit, push
git add .
git commit -m "feat(widget): add configurable chart widget"
git push origin feature/add-new-widget

What CI does: Runs mvn_test_only (or node_test_only / node_app_test_only). Builds and tests your code. No artifacts are published. If tests fail, the CI check on your PR will be red.

Publishing a Snapshot from Develop

# Merge your feature PR into develop (via GitHub PR)
# CI triggers automatically on the develop push

What CI does: Runs mvn_publish with branch_type: snapshot. Calculates a X.Y.Z-SNAPSHOT version, builds, tests, and publishes to Maven Central's snapshot repository (or NPM with --tag snapshot). Other repos can depend on this SNAPSHOT for integration testing.

Cutting a Release

# 1. Ensure develop is green and all scope is merged
git checkout develop && git pull

# 2. Create the release branch
git checkout -b release/1.5
git push origin release/1.5

What CI does: Runs mvn_publish with branch_type: release_candidate. Calculates 1.5.0-RC.1, builds, tests, and publishes the RC to Maven Central. Each subsequent push to the release branch increments the RC number (RC.2, RC.3, etc.).

Testing the RC:

<!-- In a downstream project's pom.xml -->
<dependency>
    <groupId>com.kingsrook.qqq</groupId>
    <artifactId>qqq-backend-core</artifactId>
    <version>1.5.0-RC.1</version>
</dependency>

Fixing issues on the release branch:

git checkout release/1.5
git commit -m "fix: resolve null pointer in query filter"
git push origin release/1.5
# CI automatically creates RC.2

Finalizing a Production Release

# 1. Merge the release branch into main
git checkout main && git pull
git merge release/1.5

# 2. Create the version tag and push everything
git tag v1.5.0
git push origin main v1.5.0

# 3. Merge back into develop so it gets any RC fixes
git checkout develop && git pull
git merge release/1.5
git push origin develop

# 4. Clean up the release branch
git branch -d release/1.5
git push origin --delete release/1.5

What CI does: The v1.5.0 tag push triggers the publish_release workflow. The orb's calculate_version.sh reads the tag, sets pom.xml to 1.5.0, builds, tests, deploys to Maven Central, and creates a GitHub Release with auto-generated notes.

Important: The tag must be created before pushing, because the publish_release workflow is triggered by the tag push (not the branch push). The calculate_version.sh script reads the tag name to determine the release version.

Hotfix Workflow

# 1. Branch from main
git checkout main && git pull
git checkout -b hotfix/1.5.1

# 2. Fix the issue, push to trigger CI on the hotfix branch
git commit -m "fix: critical auth token validation bypass"
git push origin hotfix/1.5.1
# CI builds and publishes 1.5.1 to Maven Central

# 3. When verified, merge to main and tag
git checkout main && git merge hotfix/1.5.1
git tag v1.5.1
git push origin main v1.5.1
# Tag push triggers publish_release -> creates GitHub Release

# 4. Merge back to develop
git checkout develop && git merge hotfix/1.5.1
git push origin develop

# 5. Clean up
git branch -d hotfix/1.5.1
git push origin --delete hotfix/1.5.1

What CI does: The hotfix branch push triggers mvn_publish with branch_type: hotfix, which bumps the patch version (1.5.0 -> 1.5.1), builds, tests, and publishes. The v1.5.1 tag push later triggers publish_release, which creates the GitHub Release.

Optional: Feature Branch Publishing

For testing unreleased features in downstream projects. Requires the publish_feature workflow in your config (commented out by default in the templates above -- uncomment it to enable).

# Tag the feature branch commit with a publish- prefix
git tag publish-my-feature
git push origin publish-my-feature

What CI does: Runs mvn_publish with branch_type: feature. Creates a version like 1.5.0-my-feature-abc1234-SNAPSHOT that downstream projects can temporarily depend on. The version commit is skipped for feature branches (versions are ephemeral).


Version Calculation Reference

The orb's calculate_version.sh automatically determines the version from the branch name and current pom.xml:

Branch Input Version (pom.xml) Output Version Logic
develop 1.5.0-SNAPSHOT 1.5.0-SNAPSHOT (no change) Keeps current SNAPSHOT
develop (after release merge) 1.5.0-RC.3 1.6.0-SNAPSHOT Detects RC/stable version, bumps minor
release/1.5 (first push) 1.5.0-SNAPSHOT 1.5.0-RC.1 Creates first RC
release/1.5 (subsequent push) 1.5.0-RC.1 1.5.0-RC.2 Increments RC number
main (tagged v1.5.0) 1.5.0-RC.3 1.5.0 Reads version from git tag
hotfix/* 1.5.0 1.5.1 Bumps patch
feature/new-widget 1.5.0-SNAPSHOT 1.5.0-new-widget-abc1234-SNAPSHOT Appends feature name + commit hash

You never need to manually edit versions. The orb handles it.


Common Customizations

Java Version

Default is Java 21. To use Java 17:

- qqq-orb/mvn_publish:
    java_version: "17"
    branch_type: snapshot

Node Version

- qqq-orb/node_publish:
    node_version: "20"
    branch_type: snapshot

Middleware API Version Checks

For the main qqq repo, enable API compatibility checks:

- qqq-orb/mvn_test_only:
    check_middleware_api_versions: true

Security Scanning

Add a security scan workflow (typically on develop only):

  security_scan:
    jobs:
      - qqq-orb/security_scan:
          context: [qqq-maven-registry-credentials, qqq-security-credentials]
          filters:
            branches:
              only: [develop]

Static Analysis

- qqq-orb/static_analysis:
    context: [qqq-maven-registry-credentials]

Updating the Orb Version

When a new orb version is released, update the version in your .circleci/config.yml:

orbs:
  qqq-orb: kingsrook/qqq-orb@0.6.5  # Update this version

You can also pin to a major version to get automatic patches:

orbs:
  qqq-orb: kingsrook/qqq-orb@0.6

Or use the development snapshot for testing unreleased orb changes:

orbs:
  qqq-orb: kingsrook/qqq-orb@dev:snapshot

Important: CircleCI resolves the orb version at pipeline creation time. If you rerun a pipeline, it uses the orb version from the original run. To pick up a new orb version, you must trigger a new pipeline (push a commit or use the CircleCI API).


Troubleshooting

Build passes locally but fails in CI

  • Check that the context includes all required secrets.
  • Verify the orb version in your config supports the features you're using.
  • Ensure pom.xml uses <revision> for version management.

Version not calculated correctly

  • Confirm your branch name matches GitFlow conventions exactly (release/X.Y, not release/X.Y.Z).
  • Check that existing git tags follow vX.Y.Z format.
  • The script reads CIRCLE_BRANCH in CI -- verify it's set correctly in your CircleCI dashboard.

GitHub Release not created

  • GITHUB_TOKEN must be set as a project environment variable.
  • GitHub Releases are only created for release and hotfix branch types, not for snapshots or RCs.

Orb version not updating

  • You must trigger a new pipeline (new commit or API trigger). Reruns use the old orb version.

See Also

Clone this wiki locally