Skip to content

Commit 1b57e68

Browse files
pditommasoclaude
andauthored
Externalize lib-pairing module to libseqera (#958)
* Extract pairing logic into lib-pairing module - Create lib-pairing Gradle submodule for credential federation functionality - Move pairing service, WebSocket transport, and message types to new module - Extract configuration into PairingConfig interface with implementation in Wave - Create LicenseValidator interface for decoupling license validation - Rename package from io.seqera.wave.service.pairing to io.seqera.service.pairing - Update all Wave imports to reference the new library package The lib-pairing module provides: - RSA key pair generation and caching for secure credential sharing - WebSocket-based bidirectional communication for real-time messaging - HTTP request/response proxying over WebSocket connections - Distributed state management via Redis-backed stores Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add unit tests for lib-pairing serialization - Add PairingMessageEncodeStrategyTest: polymorphic message encoding/decoding - Add PairingRecordSerializationTest: record serialization with Moshi - Add PairingExchangeSerializationTest: request/response model tests - Use Spock 'where:' clauses for data-driven tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Externalize lib-pairing module to libseqera Move the lib-pairing subproject out of Wave and consume it as an external dependency from libseqera. This eliminates the wave-utils dependency by inlining the MD5 logic into a new CacheKey utility class. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d59370c commit 1b57e68

33 files changed

+237
-1053
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ dependencies {
4343
implementation 'io.seqera:lib-activator:1.0.0'
4444
implementation project(':wave-api')
4545
implementation project(':wave-utils')
46+
implementation 'io.seqera:lib-pairing:1.0.0'
4647
implementation 'io.seqera:lib-data-store-state-redis:1.0.0'
4748
implementation 'io.seqera:lib-data-store-future-redis:1.0.0'
4849
implementation 'io.seqera:lib-crypto:1.0.0'

src/main/groovy/io/seqera/wave/controller/ContainerController.groovy

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ import io.seqera.wave.service.inclusion.ContainerInclusionService
6767
import io.seqera.wave.service.inspect.ContainerInspectService
6868
import io.seqera.wave.service.mirror.ContainerMirrorService
6969
import io.seqera.wave.service.mirror.MirrorRequest
70-
import io.seqera.wave.service.pairing.PairingService
71-
import io.seqera.wave.service.pairing.socket.PairingChannel
70+
import io.seqera.service.pairing.PairingService
71+
import io.seqera.service.pairing.socket.PairingChannel
7272
import io.seqera.wave.service.persistence.PersistenceService
7373
import io.seqera.wave.service.persistence.WaveContainerRecord
7474
import io.seqera.wave.service.request.ContainerRequest
@@ -86,7 +86,7 @@ import jakarta.inject.Inject
8686
import static io.micronaut.http.HttpHeaders.WWW_AUTHENTICATE
8787
import static io.seqera.wave.service.builder.BuildFormat.DOCKER
8888
import static io.seqera.wave.service.builder.BuildFormat.SINGULARITY
89-
import static io.seqera.wave.service.pairing.PairingService.TOWER_SERVICE
89+
import static io.seqera.service.pairing.PairingService.TOWER_SERVICE
9090
import static io.seqera.wave.util.ContainerHelper.checkContainerSpec
9191
import static io.seqera.wave.util.ContainerHelper.condaFileFromRequest
9292
import static io.seqera.wave.util.ContainerHelper.containerFileFromRequest

src/main/groovy/io/seqera/wave/controller/InspectController.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import io.seqera.wave.api.ContainerInspectResponse
3535
import io.seqera.wave.exception.BadRequestException
3636
import io.seqera.wave.service.UserService
3737
import io.seqera.wave.service.inspect.ContainerInspectService
38-
import io.seqera.wave.service.pairing.PairingService
38+
import io.seqera.service.pairing.PairingService
3939
import io.seqera.wave.tower.PlatformId
4040
import io.seqera.wave.tower.auth.JwtAuth
4141
import jakarta.inject.Inject

src/main/groovy/io/seqera/wave/exchange/PairingResponse.groovy

Lines changed: 0 additions & 33 deletions
This file was deleted.

src/main/groovy/io/seqera/wave/service/CredentialServiceImpl.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import groovy.util.logging.Slf4j
2323
import io.seqera.tower.crypto.AsymmetricCipher
2424
import io.seqera.tower.crypto.EncryptedPacket
2525
import io.seqera.wave.service.aws.AwsEcrService
26-
import io.seqera.wave.service.pairing.PairingService
26+
import io.seqera.service.pairing.PairingService
2727
import io.seqera.wave.tower.PlatformId
2828
import io.seqera.wave.tower.auth.JwtAuth
2929
import io.seqera.wave.tower.client.CredentialsDescription

src/main/groovy/io/seqera/wave/exchange/PairingRequest.groovy renamed to src/main/groovy/io/seqera/wave/service/license/LicenseManValidator.groovy

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* Wave, containers provisioning service
3-
* Copyright (c) 2023-2024, Seqera Labs
3+
* Copyright (c) 2023-2025, Seqera Labs
44
*
55
* This program is free software: you can redistribute it and/or modify
66
* it under the terms of the GNU Affero General Public License as published by
@@ -16,33 +16,37 @@
1616
* along with this program. If not, see <https://www.gnu.org/licenses/>.
1717
*/
1818

19-
package io.seqera.wave.exchange
19+
package io.seqera.wave.service.license
2020

2121
import groovy.transform.CompileStatic
22-
import io.micronaut.core.annotation.Introspected
23-
import jakarta.validation.constraints.NotBlank
24-
import jakarta.validation.constraints.NotNull
22+
import io.micronaut.context.annotation.Requires
23+
import io.seqera.service.pairing.LicenseValidator
24+
import jakarta.inject.Inject
25+
import jakarta.inject.Singleton
2526

2627
/**
27-
* Model the request for a remote service instance to register
28-
* itself as Wave credentials provider
28+
* Bridge implementation that adapts the Wave LicenseManClient to the
29+
* lib-pairing LicenseValidator interface.
2930
*
3031
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
3132
*/
33+
@Singleton
3234
@CompileStatic
33-
@Introspected
34-
class PairingRequest {
35+
@Requires(bean = LicenseManClient)
36+
class LicenseManValidator implements LicenseValidator {
3537

36-
@NotBlank
37-
@NotNull
38-
String service
38+
@Inject
39+
private LicenseManClient licenseManClient
3940

40-
@NotBlank
41-
@NotNull
42-
String endpoint
43-
44-
PairingRequest(String service=null, String endpoint=null) {
45-
this.service = service
46-
this.endpoint = endpoint
41+
@Override
42+
LicenseCheckResult checkToken(String token, String product) {
43+
final response = licenseManClient.checkToken(token, product)
44+
if (response == null) {
45+
return null
46+
}
47+
return new LicenseCheckResult(
48+
id: response.id,
49+
expiration: response.expiration
50+
)
4751
}
4852
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Wave, containers provisioning service
3+
* Copyright (c) 2023-2025, Seqera Labs
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
19+
package io.seqera.wave.service.pairing
20+
21+
import io.seqera.service.pairing.PairingConfig
22+
23+
import java.time.Duration
24+
import jakarta.annotation.PostConstruct
25+
26+
import groovy.transform.CompileStatic
27+
import groovy.util.logging.Slf4j
28+
import io.micronaut.context.annotation.Value
29+
import io.micronaut.core.annotation.Nullable
30+
import jakarta.inject.Singleton
31+
32+
/**
33+
* Implementation of PairingConfig that provides configuration values
34+
* via Micronaut's @Value injection.
35+
*
36+
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
37+
*/
38+
@Slf4j
39+
@Singleton
40+
@CompileStatic
41+
class PairingConfigImpl implements PairingConfig {
42+
43+
@Value('${wave.pairing-key.lease:`1d`}')
44+
private Duration keyLease
45+
46+
@Value('${wave.pairing-key.duration:`30d`}')
47+
private Duration keyDuration
48+
49+
@Value('${wave.pairing.channel.timeout:5s}')
50+
private Duration channelTimeout
51+
52+
@Value('${wave.pairing.channel.awaitTimeout:100ms}')
53+
private Duration channelAwaitTimeout
54+
55+
@Value('${wave.closeSessionOnInvalidLicenseToken:false}')
56+
private boolean closeSessionOnInvalidLicenseToken
57+
58+
@Nullable
59+
@Value('${wave.denyHosts}')
60+
private List<String> denyHosts
61+
62+
@PostConstruct
63+
private void init() {
64+
log.info "Pairing configuration - keyLease=${keyLease}; keyDuration=${keyDuration}; channelTimeout=${channelTimeout}; channelAwaitTimeout=${channelAwaitTimeout}; closeSessionOnInvalidLicenseToken=${closeSessionOnInvalidLicenseToken}; denyHosts=${denyHosts}"
65+
}
66+
67+
@Override
68+
Duration getKeyLease() {
69+
return keyLease
70+
}
71+
72+
@Override
73+
Duration getKeyDuration() {
74+
return keyDuration
75+
}
76+
77+
@Override
78+
Duration getChannelTimeout() {
79+
return channelTimeout
80+
}
81+
82+
@Override
83+
Duration getChannelAwaitTimeout() {
84+
return channelAwaitTimeout
85+
}
86+
87+
@Override
88+
boolean getCloseSessionOnInvalidLicenseToken() {
89+
return closeSessionOnInvalidLicenseToken
90+
}
91+
92+
@Override
93+
List<String> getDenyHosts() {
94+
return denyHosts ?: List.of()
95+
}
96+
}

src/main/groovy/io/seqera/wave/service/pairing/PairingRecord.groovy

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/main/groovy/io/seqera/wave/service/pairing/PairingService.groovy

Lines changed: 0 additions & 65 deletions
This file was deleted.

0 commit comments

Comments
 (0)