rundeck icon indicating copy to clipboard operation
rundeck copied to clipboard

Upgrade to Grails 7.0.0, Spring Boot 3, Groovy 4

Open fdevans opened this issue 4 months ago • 0 comments

RUN-3427: Upgrade to Grails 7.0.4, Spring Boot 3.5.8, Groovy 4.0.29

Current State (Last Updated: December 9, 2024, 11:45 PM)

Compilation: Complete with ZERO errors
Tests: 5,057 total, 4,836 passing (95.6%), 221 failing
OpenAPI: 95.74% complete (14,552/15,200 lines), zero warnings
Status: Ready for engineering review and systematic test fixing


Current Status

Build & Compilation

  • rundeckapp compiles successfully
  • All core plugins compile successfully
  • Zero compilation errors
  • Zero compilation warnings
  • Full clean build succeeds

Test Results

  • Total: 5,057 tests
  • Passing: 4,836 (95.6%)
  • Failing: 221 (4.4%)
  • Categories: Property access (Groovy 4), tag library issues, complex mocking

OpenAPI Specification

  • Completion: 14,552 / 15,200 lines (95.74%)
  • Endpoints: 250 (all present)
  • Warnings: 0 (all eliminated)
  • Examples: 85 response examples transformed for Micronaut OpenAPI 6.x

Major Version Updates

Component From To
Grails 6.1.2 7.0.4
Spring Boot 2.7.18 3.5.8
Spring 5.3.39 6.2.14
Groovy 3.0.25 4.0.29
Gradle 7.6.2 8.14.3
Jetty 11.x 12.0.17 (EE10)
Micronaut 3.10.4 4.9.2

Major Technical Changes

1. Complete JAAS Refactoring

Issue: Jetty 12 removed all org.eclipse.jetty.jaas.* packages

Solution: Complete refactoring to standard Java JAAS APIs

Custom Classes Created (in org.rundeck.jaas):

  • UserInfo.java - Replaces Jetty UserInfo
  • PasswordCredential.java - Replaces Jetty Credential
  • ObjectCallback.java - Replaces Jetty ObjectCallback
  • RundeckPrincipal.java - Replaces JAASPrincipal
  • RundeckRole.java - Replaces JAASRole
  • AbstractLoginModule.java - Base class with common logic
  • PropertyFileLoginModule.java - Property file authentication

Modules Refactored (8 authentication modules):

  • JNDILoginModule.java
  • JettyCachingLdapLoginModule.java
  • JettyCombinedLdapLoginModule.java
  • ReloadablePropertyFileLoginModule.java
  • JettyRolePropertyFileLoginModule.java
  • JettyAuthPropertyFileLoginModule.java
  • JettySupport.java

Result: Zero Jetty dependencies, standard Java JAAS throughout

Feature Added: Case-insensitive username normalization via rundeck.security.auth.caseInsensitiveUsername


2. Jakarta EE Migration (javax → jakarta)

Scope: All packages migrated

  • javax.servlet.*jakarta.servlet.*
  • javax.validation.*jakarta.validation.*
  • javax.xml.bind.*jakarta.xml.bind.*
  • javax.cache.*jakarta.cache.*
  • Configuration properties updated
  • Build dependencies migrated

Files Affected: 7,500+ files

Approach: OpenRewrite for automation + manual verification for constants


3. Micronaut OpenAPI 6.x Migration

Version Jump: 4.8.7 → 6.16.0 introduced breaking annotation pattern changes

Key Changes Required:

  1. @ApiResponse moved outside @Operation (was nested before)
  2. Added produces = MediaType.APPLICATION_JSON to HTTP verb annotations
  3. MediaType constants instead of strings
  4. Response examples require array of named @ExampleObject instances

Controllers Updated:

  • 24 endpoints transformed across 11 controllers
  • 62 response examples fixed
  • 7 missing endpoints added (2 project export, 5 metrics)
  • 20 missing response codes added

Pattern Documented: .github/copilot-instructions.md updated with Micronaut 6.x patterns


4. Groovy 4 Compatibility

Syntax Changes Applied:

  • Ternary operator: .? before ?: no longer works, split into separate expressions
  • Boolean properties: isEnabled no longer generates getEnabled(), use isEnabled() directly
  • Type inference: Stricter, requires explicit casts in some contexts
  • XML parser imports: groovy.util.XmlParsergroovy.xml.XmlParser
  • Direct field access: Use this.@id to avoid recursion in custom getters

Build Files Updated:

  • Forced Groovy 4 for all transitive dependencies
  • Resolution strategy configured in all build.gradle files

5. Asset Pipeline Migration

Migration: org.grails.pluginscloud.wondrify coordinates

Changes:

// Old
plugins {
    id "com.bertramlabs.asset-pipeline" version "3.4.7"
}
dependencies {
    implementation "org.grails.plugins:asset-pipeline-grails:3.4.7"
}

// New
plugins {
    id "cloud.wondrify.asset-pipeline" version "5.0.12"
}
dependencies {
    implementation "cloud.wondrify:asset-pipeline-grails:5.0.12"
}

Files Updated: 15+ plugins


6. Java 17 Module System for Testing

Issue: NoClassDefFoundError for CGLIB mocking in tests

Solution: Add --add-opens JVM arguments

test {
    useJUnitPlatform()
    jvmArgs += [
        '--add-opens=java.base/java.lang=ALL-UNNAMED',
        '--add-opens=java.base/java.lang.reflect=ALL-UNNAMED',
        '--add-opens=java.base/java.util=ALL-UNNAMED'
    ]
}

Applied To: rundeckapp + core plugins


7. Jakarta Validation Provider

Issue: jakarta.validation.NoProviderFoundException during tests

Solution: Add validation implementation dependencies

testImplementation "org.hibernate.validator:hibernate-validator:8.0.1.Final"
testImplementation "org.glassfish.expressly:expressly:5.0.0"

Applied To: rundeckapp build.gradle


Test Fixing Patterns

Pattern #1: @Integration Conflicts

Issue: @Integration incompatible with unit test traits in Grails 7
Symptom: IllegalStateException during test initialization
Solution: Remove @Integration annotation from unit tests
Applied: AsyncImportServiceSpec (17 fixes)

Pattern #2: Wrong Test Traits

Issue: Service specs using ControllerUnitTest instead of ServiceUnitTest
Symptom: MissingFieldException, missing methods, mocked domains broken
Solution:

  • Change to correct trait (ServiceUnitTest for services)
  • Remove controller-only methods (e.g., mockCodec())
  • Ensure mocked domains have explicit IDs in Mock closures

Example:

// ❌ WRONG
class ApiServiceSpec implements ControllerUnitTest<ApiController>, DataTest {
    void setup() {
        mockCodec(JSONCodec)  // Doesn't exist!
        provider.userDataProvider = Mock(...){
            findUser(_) >> new User(login: 'auser').save()  // id not set!
        }
    }
}

// ✅ CORRECT
class ApiServiceSpec implements ServiceUnitTest<ApiService>, DataTest {
    void setup() {
        // No mockCodec
        provider.userDataProvider = Mock(...){
            findUser(_) >> {
                def user = new User(login: 'auser')
                user.id = 1L  // Explicit!
                user.save()
                return user
            }
        }
    }
}

Applied: ApiServiceSpec (17 fixes)


Dependency Changes

Added

  • org.apache.grails:grails-micronaut - Grails 7 Micronaut integration
  • org.apache.grails:grails-layout - Sitemesh 2.6.x for Grails 7
  • org.hibernate.validator:hibernate-validator:8.0.1.Final - Jakarta Validation
  • org.glassfish.expressly:expressly:5.0.0 - Expression Language
  • Java 17 --add-opens JVM arguments for CGLIB/Spock

Removed

  • All Jetty JAAS dependencies (org.eclipse.jetty.jaas.*)
  • grails-test-mixins (obsolete in Grails 7)
  • Legacy Hibernate cache configuration (SingletonEhCacheRegionFactory)

Updated

  • Asset Pipeline → cloud.wondrify:asset-pipeline-grails:5.0.12
  • All Grails dependencies → org.apache.grails namespace
  • All Spring dependencies → Spring 6.2.14
  • Hibernate cache → Jakarta JCache (Ehcache 3)

Breaking Changes

API Changes

  • None expected - all public APIs preserved

Configuration Properties

  • hibernate.cache.region.factory_class = "jcache" (Jakarta cache properties)
  • New feature flag: rundeck.security.auth.caseInsensitiveUsername (optional)

Runtime Requirements

  • Java 17+ required (was Java 11)
  • Spring Boot 3.5.8 (Jakarta EE namespace)
  • Groovy 4.0.29 (syntax changes in some contexts)

Related PRs

Pro PR: rundeckpro/rundeckpro #4515 (grails7-upgrade branch)

  • Uses this Core commit via submodule
  • Must be reviewed/merged in parallel

Migration Notes

See GRAILS7_MIGRATION_NOTES.md in Pro repository for:

  • Detailed upgrade patterns
  • Test fixing patterns discovered
  • OpenAPI migration details
  • Quick reference for common issues

Next Steps

  1. Engineering Review: Review upgrade approach and remaining test failures
  2. Test Fixing: Systematic fixes for remaining 221 failures
  3. Integration Testing: Run full integration test suite
  4. Functional Testing: Run functional tests
  5. QA Cycle: Complete QA testing
  6. Performance Testing: Validate no regressions

Testing Notes

How to Build

export JAVA_HOME="/path/to/java17"
./gradlew clean build

How to Run Tests

./gradlew :rundeckapp:test --continue

Known Test Patterns

  • Some specs need Jakarta Validation provider (added to rundeckapp)
  • Java 17 module access configured for CGLIB/Spock
  • Wrong test trait usage causes MissingFieldException
  • See GRAILS7_MIGRATION_NOTES.md for detailed patterns

Related Ticket: RUN-3427
Branch: grails7-upgrade
Target: main

fdevans avatar Dec 04 '25 10:12 fdevans