Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2013-2025, Seqera Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package nextflow.script

import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString

/**
* Models Seqera Platform metadata for Nextflow execution
*
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
*/
@CompileStatic
@ToString(includeNames = true, includePackage = false)
@EqualsAndHashCode
class PlatformMetadata {

String workflowId

PlatformMetadata() {}

PlatformMetadata(String workflowId) {
this.workflowId = workflowId
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import nextflow.exception.WorkflowScriptErrorException
import nextflow.trace.WorkflowStats
import nextflow.util.Duration
import nextflow.util.TestOnly
import org.codehaus.groovy.runtime.InvokerHelper
/**
* Models workflow metadata properties and notification handler
*
Expand Down Expand Up @@ -218,6 +217,12 @@ class WorkflowMetadata {
*/
FusionMetadata fusion

/**
* Metadata specific to Seqera Platform, including:
* <li>workflowId: the Platform-assigned workflow identifier
*/
PlatformMetadata platform

/**
* The list of files that concurred to create the config object
*/
Expand Down Expand Up @@ -497,4 +502,14 @@ class WorkflowMetadata {
session.statsObserver.getStats()
}

PlatformMetadata getPlatform() {
if( platform!=null )
return platform
synchronized (this) {
if( platform!=null )
return platform
platform = new PlatformMetadata()
}
return platform
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2013-2025, Seqera Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package nextflow.script

import spock.lang.Specification

/**
* Tests for {@link PlatformMetadata}
*
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
*/
class PlatformMetadataTest extends Specification {

def 'should create with default constructor'() {
when:
def meta = new PlatformMetadata()

then:
meta.workflowId == null
}

def 'should create with workflowId'() {
when:
def meta = new PlatformMetadata('abc123')

then:
meta.workflowId == 'abc123'
}

def 'should allow setting workflowId after construction'() {
given:
def meta = new PlatformMetadata()

when:
meta.workflowId = 'xyz789'

then:
meta.workflowId == 'xyz789'
}
}
4 changes: 2 additions & 2 deletions plugins/nf-seqera/src/main/io/seqera/executor/Labels.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ class Labels {
if( workflow.sessionId )
entries.put('nextflow.io/sessionId', workflow.sessionId.toString())
entries.put('nextflow.io/resume', String.valueOf(workflow.resume))
if( workflow.sessionId && workflow.runName )
entries.put('seqera.io/runId', runId(workflow.sessionId.toString(), workflow.runName))
if( workflow.revision )
entries.put('nextflow.io/revision', workflow.revision)
if( workflow.commitId )
Expand All @@ -61,6 +59,8 @@ class Labels {
entries.put('nextflow.io/manifestName', workflow.manifest.name)
if( NextflowMeta.instance.version )
entries.put('nextflow.io/runtimeVersion', NextflowMeta.instance.version.toString())
if( workflow.platform?.workflowId )
entries.put('seqera.io/platform/workflowId', workflow.platform.workflowId)
return this
}

Expand Down
42 changes: 41 additions & 1 deletion plugins/nf-seqera/src/test/io/seqera/executor/LabelsTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package io.seqera.executor

import nextflow.NextflowMeta
import nextflow.config.Manifest
import nextflow.script.PlatformMetadata
import nextflow.script.WorkflowMetadata
import spock.lang.Specification

Expand Down Expand Up @@ -59,7 +60,6 @@ class LabelsTest extends Specification {
labels.entries['nextflow.io/repository'] == 'https://github.com/nf-core/rnaseq'
labels.entries['nextflow.io/manifestName'] == 'nf-core/rnaseq'
labels.entries['nextflow.io/runtimeVersion'] == NextflowMeta.instance.version.toString()
labels.entries['seqera.io/runId'] == Labels.runId(sessionId.toString(), 'crazy_darwin')
}

def 'should compute stable runId from sessionId and runName'() {
Expand Down Expand Up @@ -150,6 +150,46 @@ class LabelsTest extends Specification {
labels.entries['nextflow.io/projectName'] == 'hello'
}

def 'should include platform workflowId when available'() {
given:
def workflow = Mock(WorkflowMetadata) {
getProjectName() >> 'hello'
getUserName() >> 'user1'
getRunName() >> 'happy_turing'
getSessionId() >> UUID.randomUUID()
isResume() >> false
getManifest() >> new Manifest([:])
getPlatform() >> new PlatformMetadata('wf-abc123')
}

when:
def labels = new Labels()
.withWorkflowMetadata(workflow)

then:
labels.entries['seqera.io/platform/workflowId'] == 'wf-abc123'
}

def 'should omit platform workflowId when not set'() {
given:
def workflow = Mock(WorkflowMetadata) {
getProjectName() >> 'hello'
getUserName() >> 'user1'
getRunName() >> 'happy_turing'
getSessionId() >> UUID.randomUUID()
isResume() >> false
getManifest() >> new Manifest([:])
getPlatform() >> new PlatformMetadata()
}

when:
def labels = new Labels()
.withWorkflowMetadata(workflow)

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

def 'should handle null user labels'() {
when:
def labels = new Labels()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ class TowerClient implements TraceObserverV2 {
this.workflowId = ret.workflowId
if( !workflowId )
throw new AbortOperationException("Invalid Seqera Platform API response - Missing workflow Id")
session.workflowMetadata.platform.workflowId = workflowId
if( ret.message )
log.warn(ret.message.toString())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import nextflow.cloud.types.PriceModel
import nextflow.container.DockerConfig
import nextflow.container.resolver.ContainerMeta
import nextflow.exception.AbortOperationException
import nextflow.script.PlatformMetadata
import nextflow.script.ScriptBinding
import nextflow.script.WorkflowMetadata
import nextflow.trace.TraceRecord
Expand Down Expand Up @@ -378,9 +379,11 @@ class TowerClientTest extends Specification {
def 'should post create request' () {
given:
def uuid = UUID.randomUUID()
def platform = new PlatformMetadata()
def meta = Mock(WorkflowMetadata) {
getProjectName() >> 'the-project-name'
getRepository() >> 'git://repo.com/foo'
getPlatform() >> platform
}
def session = Mock(Session) {
getUniqueId() >> uuid
Expand All @@ -403,6 +406,8 @@ class TowerClientTest extends Specification {
and:
client.workflowId == 'xyz123'
!client.towerLaunch
and:
platform.workflowId == 'xyz123'

}

Expand Down
Loading