Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/nf-seqera/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.18.0
0.19.0
3 changes: 3 additions & 0 deletions plugins/nf-seqera/changelog.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
nf-seqera changelog
====================
0.19.0 -
Comment thread
pditommaso marked this conversation as resolved.
Outdated
- Add workspaceId and computeEnvId to autoLabels for cost aggregation

0.18.0 - 20 Apr 2026
- Honour process.resourceLabels in nf-seqera executor (#7048) [979f684ff]
- Filter autoLabels to selected workflow-metadata fields (#7049) [ddc974fe6]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class ExecutorOpts implements ConfigScope {
static final Set<String> VALID_AUTO_LABELS = Collections.unmodifiableSet(new LinkedHashSet<>([
'projectName', 'userName', 'runName', 'sessionId', 'resume',
'revision', 'commitId', 'repository', 'manifestName',
'runtimeVersion', 'workflowId'
'runtimeVersion', 'workflowId', 'workspaceId', 'computeEnvId'
]))

final RetryOpts retryPolicy
Expand Down Expand Up @@ -87,7 +87,7 @@ class ExecutorOpts implements ConfigScope {
`['runName', 'projectName']` or `'runName,projectName'`
Valid names: `projectName`, `userName`, `runName`, `sessionId`, `resume`,
`revision`, `commitId`, `repository`, `manifestName`, `runtimeVersion`,
`workflowId`.
`workflowId`, `workspaceId`, `computeEnvId`.
""")
final Set<String> autoLabels

Expand Down
17 changes: 16 additions & 1 deletion plugins/nf-seqera/src/main/io/seqera/executor/Labels.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class Labels {
static final Set<String> ALL_AUTO_LABELS = Collections.unmodifiableSet(new LinkedHashSet<>([
'projectName', 'userName', 'runName', 'sessionId', 'resume',
'revision', 'commitId', 'repository', 'manifestName',
'runtimeVersion', 'workflowId'
'runtimeVersion', 'workflowId', 'workspaceId', 'computeEnvId'
]))

private final Map<String,String> entries = new LinkedHashMap<>(20)
Expand Down Expand Up @@ -81,6 +81,21 @@ class Labels {
return this
}

/**
* Add {@code seqera.io/platform/*} labels derived from the Platform
* execution context (workspace and compute environment). Used to
* aggregate Scheduler-backed runs by workspace and compute environment
* for cost reporting. Filtered by the {@code include} set of short names.
*/
Labels withPlatformContext(Long workspaceId, String computeEnvId, Set<String> include) {
if( !include ) return this
if( include.contains('workspaceId') && workspaceId != null )
entries.put('seqera.io/platform/workspaceId', workspaceId.toString())
if( include.contains('computeEnvId') && computeEnvId )
entries.put('seqera.io/platform/computeEnvId', computeEnvId)
return this
}
Comment thread
pditommaso marked this conversation as resolved.
Outdated

/**
* Add {@code seqera:sched:*} scheduler labels
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,10 @@ class SeqeraExecutor extends Executor implements ExtensionPoint {

computeRunResourceLabels()
final labels = new Labels()
if( seqeraConfig.autoLabels )
if( seqeraConfig.autoLabels ) {
labels.withWorkflowMetadata(session.workflowMetadata, seqeraConfig.autoLabels)
labels.withPlatformContext(workspaceId, computeEnvId, seqeraConfig.autoLabels)
}
labels.withProcessResourceLabels(runResourceLabels)
final predictionModel = seqeraConfig.predictionModel ? PredictionModel.fromValue(seqeraConfig.predictionModel) : null
final pipeline = new PipelineSpec()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,29 @@ class ExecutorOptsTest extends Specification {
config.autoLabels == ['runName', 'projectName'] as Set
}

def 'should accept workspaceId and computeEnvId in auto labels' () {
when:
def config = new ExecutorOpts([
endpoint: 'https://sched.example.com',
autoLabels: ['workspaceId', 'computeEnvId']
])

then:
config.autoLabels == ['workspaceId', 'computeEnvId'] as Set
}

def 'should include workspaceId and computeEnvId when auto labels is true' () {
when:
def config = new ExecutorOpts([
endpoint: 'https://sched.example.com',
autoLabels: true
])

then:
'workspaceId' in config.autoLabels
'computeEnvId' in config.autoLabels
}

def 'should trim whitespace in auto labels list entries' () {
when:
def config = new ExecutorOpts([
Expand Down
52 changes: 52 additions & 0 deletions plugins/nf-seqera/src/test/io/seqera/executor/LabelsTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,58 @@ class LabelsTest extends Specification {
!labels.entries.containsKey('seqera:sched:clusterId')
}

def 'should add platform workspace and compute env labels when included'() {
when:
def labels = new Labels()
.withPlatformContext(42L, 'ce-abc', ['workspaceId', 'computeEnvId'] as Set)

then:
labels.entries['seqera.io/platform/workspaceId'] == '42'
labels.entries['seqera.io/platform/computeEnvId'] == 'ce-abc'
}

def 'should skip platform labels when include set does not mention them'() {
when:
def labels = new Labels()
.withPlatformContext(42L, 'ce-abc', ['runName'] as Set)

then:
!labels.entries.containsKey('seqera.io/platform/workspaceId')
!labels.entries.containsKey('seqera.io/platform/computeEnvId')
}

def 'should skip platform labels when values are null or blank'() {
when:
def labels = new Labels()
.withPlatformContext(null, null, ['workspaceId', 'computeEnvId'] as Set)
.withPlatformContext(null, '', ['workspaceId', 'computeEnvId'] as Set)

then:
!labels.entries.containsKey('seqera.io/platform/workspaceId')
!labels.entries.containsKey('seqera.io/platform/computeEnvId')
}

def 'should emit only the filtered platform label'() {
when:
def wsOnly = new Labels()
.withPlatformContext(42L, 'ce-abc', ['workspaceId'] as Set)
def ceOnly = new Labels()
.withPlatformContext(42L, 'ce-abc', ['computeEnvId'] as Set)

then:
wsOnly.entries.keySet() == ['seqera.io/platform/workspaceId'] as Set
ceOnly.entries.keySet() == ['seqera.io/platform/computeEnvId'] as Set
}

def 'should emit nothing for an empty include set in withPlatformContext'() {
when:
def labels = new Labels()
.withPlatformContext(42L, 'ce-abc', [] as Set)

then:
labels.entries.isEmpty()
}
Comment thread
pditommaso marked this conversation as resolved.

def 'should include platform workflowId when available'() {
given:
def workflow = Mock(WorkflowMetadata) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,153 @@ class SeqeraExecutorTest extends Specification {
executor.batchSubmitter?.shutdown()
}

def 'createRun emits workspaceId and computeEnvId labels when autoLabels is enabled'() {
given:
SysEnv.push([
TOWER_WORKSPACE_ID: '1234',
TOWER_COMPUTE_ENV_ID: 'ce-abc'
])
CreateRunRequest captured = null
def mockClient = Mock(SchedClient) {
createRun(_) >> { args ->
captured = args[0] as CreateRunRequest
new CreateRunResponse().runId('run-1')
}
}
def workflowMeta = Mock(WorkflowMetadata) {
getProjectName() >> 'my-project'
getRunName() >> 'test-run'
getSessionId() >> UUID.fromString('00000000-0000-0000-0000-000000000001')
getResume() >> false
getManifest() >> null
getPlatform() >> null
}
def sessionConfig = [
seqera: [executor: [endpoint: 'https://sched.example.com', provider: 'aws', region: 'us-east-1', autoLabels: true]],
tower: [:]
]
def session = Mock(Session) {
getConfig() >> sessionConfig
getWorkflowMetadata() >> workflowMeta
getWorkDir() >> java.nio.file.Paths.get('/work')
getRunName() >> 'test-run'
}
def seqeraOpts = new ExecutorOpts(endpoint: 'https://sched.example.com', provider: 'aws', region: 'us-east-1', autoLabels: true)
def executor = new SeqeraExecutor()
executor.session = session
executor.@seqeraConfig = seqeraOpts
executor.@client = mockClient

when:
executor.createRun()

then:
captured != null
captured.getLabels()['seqera.io/platform/workspaceId'] == '1234'
captured.getLabels()['seqera.io/platform/computeEnvId'] == 'ce-abc'

cleanup:
executor.batchSubmitter?.shutdown()
}

def 'createRun omits workspaceId and computeEnvId labels when autoLabels filter excludes them'() {
given:
SysEnv.push([
TOWER_WORKSPACE_ID: '1234',
TOWER_COMPUTE_ENV_ID: 'ce-abc'
])
CreateRunRequest captured = null
def mockClient = Mock(SchedClient) {
createRun(_) >> { args ->
captured = args[0] as CreateRunRequest
new CreateRunResponse().runId('run-1')
}
}
def workflowMeta = Mock(WorkflowMetadata) {
getProjectName() >> 'my-project'
getRunName() >> 'test-run'
getSessionId() >> UUID.fromString('00000000-0000-0000-0000-000000000001')
getResume() >> false
getManifest() >> null
getPlatform() >> null
}
def sessionConfig = [
seqera: [executor: [endpoint: 'https://sched.example.com', provider: 'aws', region: 'us-east-1', autoLabels: ['runName']]],
tower: [:]
]
def session = Mock(Session) {
getConfig() >> sessionConfig
getWorkflowMetadata() >> workflowMeta
getWorkDir() >> java.nio.file.Paths.get('/work')
getRunName() >> 'test-run'
}
def seqeraOpts = new ExecutorOpts(endpoint: 'https://sched.example.com', provider: 'aws', region: 'us-east-1', autoLabels: ['runName'])
def executor = new SeqeraExecutor()
executor.session = session
executor.@seqeraConfig = seqeraOpts
executor.@client = mockClient

when:
executor.createRun()

then:
captured != null
!captured.getLabels().containsKey('seqera.io/platform/workspaceId')
!captured.getLabels().containsKey('seqera.io/platform/computeEnvId')
captured.getLabels()['nextflow.io/runName'] == 'test-run'

cleanup:
executor.batchSubmitter?.shutdown()
}

def 'createRun omits workspaceId and computeEnvId labels when autoLabels is disabled'() {
given:
SysEnv.push([
TOWER_WORKSPACE_ID: '1234',
TOWER_COMPUTE_ENV_ID: 'ce-abc'
])
CreateRunRequest captured = null
def mockClient = Mock(SchedClient) {
createRun(_) >> { args ->
captured = args[0] as CreateRunRequest
new CreateRunResponse().runId('run-1')
}
}
def workflowMeta = Mock(WorkflowMetadata) {
getRunName() >> 'test-run'
getSessionId() >> UUID.fromString('00000000-0000-0000-0000-000000000001')
getResume() >> false
getManifest() >> null
getPlatform() >> null
}
def sessionConfig = [
seqera: [executor: [endpoint: 'https://sched.example.com', provider: 'aws', region: 'us-east-1']],
tower: [:]
]
def session = Mock(Session) {
getConfig() >> sessionConfig
getWorkflowMetadata() >> workflowMeta
getWorkDir() >> java.nio.file.Paths.get('/work')
getRunName() >> 'test-run'
}
def seqeraOpts = new ExecutorOpts(endpoint: 'https://sched.example.com', provider: 'aws', region: 'us-east-1')
def executor = new SeqeraExecutor()
executor.session = session
executor.@seqeraConfig = seqeraOpts
executor.@client = mockClient

when:
executor.createRun()

then:
captured != null
!captured.getLabels().containsKey('seqera.io/platform/workspaceId')
!captured.getLabels().containsKey('seqera.io/platform/computeEnvId')

cleanup:
executor.batchSubmitter?.shutdown()
}

Comment thread
pditommaso marked this conversation as resolved.
Outdated
/**
* Builds a SchedClientConfig using the same logic as {@link SeqeraExecutor#createClient()}
*/
Expand Down
Loading