Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate config schema #5816

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
43 changes: 38 additions & 5 deletions docs/developer/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class MyObserver implements TraceObserver {

@Override
void onFlowCreate(Session session) {
final message = session.config.navigate('myplugin.create.message')
final message = session.config.navigate('myplugin.createMessage')
println message
}
}
Expand All @@ -108,16 +108,49 @@ You can then set this option in your config file:

```groovy
// dot syntax
myplugin.create.message = "I'm alive!"
myplugin.createMessage = "I'm alive!"

// closure syntax
// block syntax
myplugin {
create {
message = "I'm alive!"
createMessage = "I'm alive!"
}
```

:::{versionadded} 25.02.0-edge
:::

Plugins can declare their config options by implementing the `ConfigScope` interface and declaring each config option as a field with the `@ConfigOption` annotation:

```groovy
class MyPluginConfig implements ConfigScope {

MyPluginConfig() {}

MyPluginConfig(Map opts) {
this.createMessage = opts.createMessage
}

@Override
String name() {
return 'myplugin'
}

@Override
String description() {
return '''
The `myplugin` scope...
'''
}

@ConfigOption('''
Message to print to standard output when a run is initialized.
''')
String createMessage
}
```

While this approach is not required to support plugin config options, it is used by Nextflow to recognize plugin definitions when validating the config.

### Executors

Plugins can define custom executors that can then be used with the `executor` process directive.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import groovy.util.logging.Slf4j
import nextflow.config.ConfigBuilder
import nextflow.config.ConfigValidator
import nextflow.exception.AbortOperationException
import nextflow.plugin.Plugins
import nextflow.scm.AssetManager
Expand Down Expand Up @@ -81,6 +82,7 @@ class CmdConfig extends CmdBase {
if( args ) base = getBaseDir(args[0])
if( !base ) base = Paths.get('.')

// -- validate command line options
if( profile && showAllProfiles ) {
throw new AbortOperationException("Option `-profile` conflicts with option `-show-profiles`")
}
Expand All @@ -103,6 +105,7 @@ class CmdConfig extends CmdBase {
if( printProperties )
outputFormat = 'properties'

// -- build the config
final builder = new ConfigBuilder()
.setShowClosures(true)
.setStripSecrets(true)
Expand All @@ -113,6 +116,10 @@ class CmdConfig extends CmdBase {

final config = builder.buildConfigObject()

// -- validate config options
new ConfigValidator().validate(config)

// -- print config options
if( printValue ) {
printValue0(config, printValue, stdout)
}
Expand Down
18 changes: 4 additions & 14 deletions modules/nextflow/src/main/groovy/nextflow/cli/CmdRun.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import nextflow.NextflowMeta
import nextflow.SysEnv
import nextflow.config.ConfigBuilder
import nextflow.config.ConfigMap
import nextflow.config.ConfigValidator
import nextflow.exception.AbortOperationException
import nextflow.file.FileHelper
import nextflow.plugin.Plugins
Expand Down Expand Up @@ -334,13 +335,13 @@ class CmdRun extends CmdBase implements HubOptions {
// check DSL syntax in the config
launchInfo(config, scriptFile)

// check if NXF_ variables are set in nextflow.config
checkConfigEnv(config)

// -- load plugins
final cfg = plugins ? [plugins: plugins.tokenize(',')] : config
Plugins.load(cfg)

// -- validate config options
new ConfigValidator().validate(config)

// -- create a new runner instance
final runner = new ScriptRunner(config)
runner.setScript(scriptFile)
Expand Down Expand Up @@ -407,17 +408,6 @@ class CmdRun extends CmdBase implements HubOptions {
}
}

protected checkConfigEnv(ConfigMap config) {
// Warn about setting NXF_ environment variables within env config scope
final env = config.env as Map<String, String>
for( String name : env.keySet() ) {
if( name.startsWith('NXF_') && name!='NXF_DEBUG' ) {
final msg = "Nextflow variables must be defined in the launching environment - The following variable set in the config file is going to be ignored: '$name'"
log.warn(msg)
}
}
}

protected void launchInfo(ConfigMap config, ScriptFile scriptFile) {
// -- determine strict mode
detectStrictFeature(config, sysEnv)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2013-2024, 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.config

import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import nextflow.config.dsl.ConfigSchema
import nextflow.config.dsl.ConfigScope
import nextflow.plugin.Plugins
/**
* Validate the Nextflow configuration
*
* @author Ben Sherman <bentshermann@gmail.com>
*/
@Slf4j
@CompileStatic
class ConfigValidator {

void validate(ConfigMap config) {
validate(config.toConfigObject())
}

void validate(ConfigObject config) {
final schema = getSchema()
final flatConfig = config.flatten()
for( String key : flatConfig.keySet() ) {
final names = key.tokenize('.')
if( names.first() == 'profiles' ) {
if( !names.isEmpty() ) names.remove(0)
if( !names.isEmpty() ) names.remove(0)
}
final scope = names.first()
if( scope == 'env' ) {
checkEnv(names.last())
continue
}
if( scope == 'params' )
continue
final fqName = names.join('.')
if( fqName.startsWith('process.ext.') )
return
if( fqName !in schema ) {
log.warn "Unrecognized config option '${fqName}'"
continue
}
}
}

protected Set<String> getSchema() {
final schema = new HashSet<String>()
schema.addAll(ConfigSchema.OPTIONS.keySet())
for( final scope : Plugins.getExtensions(ConfigScope) )
schema.addAll(ConfigSchema.getConfigOptions(scope).keySet())
// hidden options added by ConfigBuilder
schema.addAll(List.of(
'bucketDir',
'cacheable',
'dumpChannels',
'libDir',
'poolSize',
'plugins',
'preview',
'runName',
'stubRun',
))
return schema
}

/**
* Warn about setting `NXF_*` environment variables in the config.
*
* @param name
*/
protected void checkEnv(String name) {
if( name.startsWith('NXF_') && name!='NXF_DEBUG' )
log.warn "Nextflow environment variables must be defined in the launch environment -- the following environment variable in the config will be ignored: `$name`"
}
}
39 changes: 0 additions & 39 deletions modules/nextflow/src/test/groovy/nextflow/cli/CmdRunTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,15 @@ import nextflow.NextflowMeta
import nextflow.SysEnv
import nextflow.config.ConfigMap
import nextflow.exception.AbortOperationException
import org.junit.Rule
import spock.lang.Specification
import spock.lang.Unroll
import test.OutputCapture

/**
*
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
*/
class CmdRunTest extends Specification {

@Rule
OutputCapture capture = new OutputCapture()

@Unroll
def 'should parse cmd param=#STR' () {

Expand Down Expand Up @@ -390,40 +385,6 @@ class CmdRunTest extends Specification {
CmdRun.detectDslMode(new ConfigMap(), DSL2_SCRIPT, [:]) == '2'
}

def 'should warn for invalid config vars' () {
given:
def ENV = [NXF_ANSI_SUMMARY: 'true']

when:
new CmdRun().checkConfigEnv(new ConfigMap([env:ENV]))

then:
def warning = capture
.toString()
.readLines()
.findResults { line -> line.contains('WARN') ? line : null }
.join('\n')
and:
warning.contains('Nextflow variables must be defined in the launching environment - The following variable set in the config file is going to be ignored: \'NXF_ANSI_SUMMARY\'')
}

def 'should not warn for valid config vars' () {
given:
def ENV = [FOO: '/something', NXF_DEBUG: 'true']

when:
new CmdRun().checkConfigEnv(new ConfigMap([env:ENV]))

then:
def warning = capture
.toString()
.readLines()
.findResults { line -> line.contains('WARN') ? line : null }
.join('\n')
and:
!warning
}

@Unroll
def 'should detect moduleBinaries' () {
given:
Expand Down
Loading