Skip to content
Draft
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
2 changes: 1 addition & 1 deletion docs/reference/cli/auth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Remove Seqera authentication and revoke the Seqera Cloud access token, if applic

##### `config`

Set the Seqera primary compute environment, monitoring, and workspace.
Set the Seqera primary compute environment, monitoring, and workspace. If you do not set a default workspace here, Nextflow uses the default workspace configured in your Seqera Platform account (if any), otherwise your personal workspace.

##### `status`

Expand Down
8 changes: 8 additions & 0 deletions docs/reference/cli/launch.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ The directory where intermediate result files are stored.

The Seqera Platform workspace name.

The workspace is resolved in the following order:

1. This `-workspace` option
2. The `tower.workspaceId` configuration setting
3. The `TOWER_WORKSPACE_ID` environment variable
4. The default workspace configured in your Seqera Platform account
5. Your personal workspace

##### `-workspace-secret`

<AddedInVersion version="26.04" />
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/config/tower.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@ The HTTP read timeout for Seqera Platform API requests (default: `'60s'`).

##### `tower.workspaceId`

The workspace ID in Seqera Platform in which to save the run (default: the launching user's personal workspace).
The workspace ID in Seqera Platform in which to save the run (default: the user's default workspace configured in Seqera Platform, or the personal workspace if no default is set).

The workspace ID can also be specified using the environment variable `TOWER_WORKSPACE_ID` (config file has priority over the environment variable).
The workspace ID can also be specified using the environment variable `TOWER_WORKSPACE_ID` (config file has priority over the environment variable). When neither is set, Nextflow uses the default workspace configured in your Seqera Platform account, if any.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package nextflow.platform

import java.util.function.Supplier

import groovy.transform.CompileStatic
import nextflow.Global
import nextflow.Session
Expand All @@ -29,6 +31,52 @@ import nextflow.SysEnv
@CompileStatic
class PlatformHelper {

/**
* Extract the `tower` scope options from a flattened config map, stripping the
* `tower.` prefix so the result can be passed to the accessors in this class.
*
* @param flatConfig a flattened config map, e.g. `['tower.endpoint': '...']`
* @return the `tower` options keyed without the prefix, e.g. `[endpoint: '...']`
*/
static Map towerOpts(Map flatConfig) {
return flatConfig
.findAll { it.key.toString().startsWith('tower.') }
.collectEntries { k, v -> [(k.toString().substring('tower.'.length())): v] }
}

/**
* A run made by Seqera Platform is signalled by the {@code TOWER_WORKFLOW_ID}
* environment variable. In that case the settings must be taken from the
* environment only, because Platform has already decided them for this run.
*
* @param env the applicable environment variables
* @return {@code true} when the current run was launched by Platform
*/
static boolean isPlatformRun(Map<String,String> env) {
return env.get('TOWER_WORKFLOW_ID') as boolean
}

/**
* Resolve the workspace effectively used for a run: the locally configured
* value wins, otherwise fall back to the user's server-side default workspace
* in Seqera Platform.
*
* The Platform lookup is supplied by the caller so that this class stays free
* of any I/O and of any dependency on the Platform API client.
*
* @param opts the configuration options for Platform (e.g. `session.config.navigate('tower')`)
* @param env the applicable environment variables
* @param platformDefault supplies the Platform default workspace ID, queried only when needed
* @return the workspace ID to use, or null to use the personal workspace
*/
static String getEffectiveWorkspaceId(Map opts, Map<String,String> env, Supplier<String> platformDefault) {
final local = getWorkspaceId(opts, env)
// a local setting always wins; for a Platform-driven run the environment is authoritative
if( local || isPlatformRun(env) )
return local
return platformDefault.get()
}

/**
* Get the configured Platform API endpoint: if the endpoint is not provided in the configuration, we fallback to the
* environment variable `TOWER_API_ENDPOINT`. If neither is provided, we fallback to the default endpoint.
Expand Down Expand Up @@ -114,7 +162,7 @@ class PlatformHelper {
* @return the Platform access token
*/
static String getAccessToken(Map opts, Map<String,String> env) {
final token = env.get('TOWER_WORKFLOW_ID')
final token = isPlatformRun(env)
? env.get('TOWER_ACCESS_TOKEN')
: opts.containsKey('accessToken') ? opts.accessToken as String : env.get('TOWER_ACCESS_TOKEN')
return token
Expand All @@ -130,7 +178,7 @@ class PlatformHelper {
* @return the Platform refresh token
*/
static String getRefreshToken(Map opts, Map<String,String> env) {
final token = env.get('TOWER_WORKFLOW_ID')
final token = isPlatformRun(env)
? env.get('TOWER_REFRESH_TOKEN')
: opts.containsKey('refreshToken') ? opts.refreshToken as String : env.get('TOWER_REFRESH_TOKEN')
return token
Expand All @@ -140,13 +188,21 @@ class PlatformHelper {
* Return the Platform Workspace ID: if `TOWER_WORKFLOW_ID` is provided in the environment, it means we are running
* in a Platform-made run and we should ONLY retrieve the workspace ID from the environment. Otherwise, check the
* configuration or fallback to the environment. If no workspace ID is found, return null.
*
* Note for callers passing the `tower` scope of a live session config: when the user set no
* workspace of their own, the workspace configured as default in their Seqera Platform
* account is resolved during session init and written into that map, so this can return a
* value that appears nowhere in the user's configuration files. That is deliberate -- it is
* what keeps the run, Wave, the Fusion licence and the Seqera executor in the same
* workspace. See {@code TowerFactory.publishDefaultWorkspaceId}.
*
* @param opts
* @param env
* @return
*/
static String getWorkspaceId(Map opts, Map<String,String> env) {
try {
final workspaceId = env.get('TOWER_WORKFLOW_ID')
final workspaceId = isPlatformRun(env)
? env.get('TOWER_WORKSPACE_ID')
: opts.workspaceId as Long ?: env.get('TOWER_WORKSPACE_ID') as Long
return workspaceId
Expand All @@ -167,7 +223,7 @@ class PlatformHelper {
* @return the Platform compute environment ID, or null
*/
static String getComputeEnvId(Map opts, Map<String,String> env) {
final computeEnvId = env.get('TOWER_WORKFLOW_ID')
final computeEnvId = isPlatformRun(env)
? env.get('TOWER_COMPUTE_ENV_ID')
: opts.containsKey('computeEnvId') ? opts.computeEnvId as String : env.get('TOWER_COMPUTE_ENV_ID')
return computeEnvId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package nextflow.platform

import nextflow.SysEnv
import spock.lang.Specification
import spock.lang.Unroll

/**
* Test PlatformHelper functionality
Expand Down Expand Up @@ -123,4 +124,39 @@ class PlatformHelperTest extends Specification {
expect:
PlatformHelper.getComputeEnvId([:], [:]) == null
}

def 'should detect a Platform-driven run'() {
expect:
!PlatformHelper.isPlatformRun([:])
!PlatformHelper.isPlatformRun([TOWER_WORKSPACE_ID: '100'])
PlatformHelper.isPlatformRun([TOWER_WORKFLOW_ID: 'wf-1'])
}

@Unroll
def 'should resolve effective workspace id from #SOURCE'() {
expect:
PlatformHelper.getEffectiveWorkspaceId(OPTS, ENV, () -> DEFAULT) == EXPECTED

where:
SOURCE | OPTS | ENV | DEFAULT | EXPECTED
'config' | [workspaceId: '200']| [:] | '999' | '200'
'env var' | [:] | [TOWER_WORKSPACE_ID: '100'] | '999' | '100'
'platform default' | [:] | [:] | '999' | '999'
'nothing set' | [:] | [:] | null | null
// a Platform-driven run is authoritative: never override with the account default
'platform run' | [:] | [TOWER_WORKFLOW_ID: 'wf-1'] | '999' | null
'platform run env' | [:] | [TOWER_WORKFLOW_ID: 'wf-1', TOWER_WORKSPACE_ID: '100'] | '999' | '100'
}

def 'should not query the platform default when a local workspace is set'() {
given:
def queried = false

when:
final result = PlatformHelper.getEffectiveWorkspaceId([workspaceId: '200'], [:], () -> { queried = true; '999' })

then:
result == '200'
!queried
}
}
5 changes: 2 additions & 3 deletions modules/nf-cli-v1/src/main/groovy/nextflow/cli/CmdAuth.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,7 @@ class CmdAuth extends CmdBase implements UsageAware {
// Read config to get the actual resolved endpoint value
final builder = new ConfigCmdAdapter().setHomeDir(Const.APP_HOME_DIR).setCurrentDir(Const.APP_HOME_DIR)
final config = builder.buildConfigObject().flatten()
final towerConfig = config.findAll { it.key.toString().startsWith('tower.') }
.collectEntries { k, v -> [(k.toString().substring(6)): v] }
final towerConfig = PlatformHelper.towerOpts(config)
def defaultEndpoint = PlatformHelper.getEndpoint(towerConfig, SysEnv.get())

result << 'Authenticate with Seqera Platform'
Expand Down Expand Up @@ -407,7 +406,7 @@ class CmdAuth extends CmdBase implements UsageAware {
* <tr>
* <td>Default workspace</td>
* <td>Configured workspace ID and name</td>
* <td>nextflow config, env var $TOWER_WORKSPACE_ID, default (Personal)</td>
* <td>nextflow config, env var $TOWER_WORKSPACE_ID, platform (account default), default (Personal)</td>
* </tr>
* <tr>
* <td>Primary compute env</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ class SeqeraExecutor extends Executor implements ExtensionPoint {
final workflowId = session.workflowMetadata?.platform?.workflowId
this.workflowId = workflowId
final workflowUrl = session.workflowMetadata?.platform?.workflowUrl
// note: the workspace may have been resolved during session init -- see the
// PlatformHelper.getWorkspaceId javadoc
final workspaceId = PlatformHelper.getWorkspaceId(towerConfig, SysEnv.get()) as Long
final computeEnvId = PlatformHelper.getComputeEnvId(towerConfig, SysEnv.get()) ?: seqeraConfig.computeEnvId

Expand Down
Loading
Loading