Skip to content

Commit

Permalink
Add inspect command (nextflow-io#4069)
Browse files Browse the repository at this point in the history
This commit introduces a new nextflow command named `inspect`. 

The inspect command allows resolving a pipeline script or project reporting 
all container images used by the pipeline execution. 

The main advantage of this command over  the existing `config` command is that
it's able to resolve container names defined "dynamically" or Wave containers that 
are only determined at execution time. 

The command option `-concretise` when used along with the Wave freeze option 
allows building ahead all the container images required by the pipeline execution.  


Signed-off-by: Ben Sherman <bentshermann@gmail.com>
Signed-off-by: Paolo Di Tommaso <paolo.ditommaso@gmail.com>
  • Loading branch information
bentsherman authored and abhi18av committed Oct 28, 2023
1 parent ccfc769 commit 94143d6
Show file tree
Hide file tree
Showing 17 changed files with 571 additions and 24 deletions.
67 changes: 59 additions & 8 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,57 @@ $ nextflow info nextflow-io/hello
v1.2 [t]
```

### inspect

:::{versionadded} 23.09.0-edge
:::

Inspect process settings in a pipeline project. Currently only supports the `container` directive.

**Usage**

```console
$ nextflow inspect [options] [project]
```

**Description**

The `inspect` command allows you to determine the container for each process in a pipeline without running the pipeline. It prints to stdout a listing of containers for each process, formatted either as JSON or Nextflow configuration.

**Options**

`-concretize`
: Build the container images resolved by the inspect command.

`-format` (`json`)
: Inspect output format. Can be `json` or `config`.

`-i, -ignore-errors`
: Ignore errors while inspecting the pipeline.

`-params-file`
: Load script parameters from a JSON/YAML file.

`-profile`
: Use the given configuration profile(s).

`-r, revision`
: Revision of the project to inspect (either a git branch, tag or commit SHA number).

**Examples**

Get the list of containers used by a pipeline.

```console
$ nextflow inspect nextflow-io/hello
```

Specify parameters as with the `run` command:

```console
$ nextflow inspect main.nf --alpha 1 --beta foo
```

### kuberun

Launch a Nextflow pipeline on a Kubernetes cluster.
Expand Down Expand Up @@ -1070,7 +1121,7 @@ The `run` command is used to execute a local pipeline script or remote pipeline
`-preview`
: :::{versionadded} 22.06.0-edge
:::
: Run the workflow script skipping the execution of all processes
: Run the workflow script skipping the execution of all processes.

`-process.<key>=<value>`
: Set process config options.
Expand Down Expand Up @@ -1106,7 +1157,7 @@ The `run` command is used to execute a local pipeline script or remote pipeline
`-with-conda`
: Use the specified Conda environment package or file (must end with `.yml` or `.yaml`)

`-with-dag` (`dag.dot`)
`-with-dag` (`dag-<timestamp>.dot`)
: Create pipeline DAG file.

`-with-docker`
Expand All @@ -1118,7 +1169,7 @@ The `run` command is used to execute a local pipeline script or remote pipeline
`-with-podman`
: Enable process execution in a Podman container.

`-with-report` (`report.html`)
`-with-report` (`report-<timestamp>.html`)
: Create workflow execution HTML report.

`-with-singularity`
Expand All @@ -1127,19 +1178,19 @@ The `run` command is used to execute a local pipeline script or remote pipeline
`-with-spack`
: Use the specified Spack environment package or file (must end with `.yaml`)

`-with-timeline` (`timeline.html`)
`-with-timeline` (`timeline-<timestamp>.html`)
: Create workflow execution timeline.

`-with-tower`
`-with-tower` (`https://api.tower.nf`)
: Monitor workflow execution with [Tower](https://cloud.tower.nf/).

`-with-trace` (`trace.txt`)
`-with-trace` (`trace-<timestamp>.txt`)
: Create workflow execution trace file.

`-with-wave`
`-with-wave` (`https://wave.seqera.io`)
: Enable the use of Wave containers.

`-with-weblog`
`-with-weblog` (`http://localhost`)
: Send workflow status messages via HTTP to target URL.

`-without-conda`
Expand Down
5 changes: 5 additions & 0 deletions modules/nextflow/src/main/groovy/nextflow/Session.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ class Session implements ISession {
*/
boolean disableRemoteBinDir

/**
* Suppress all output from pipeline script
*/
boolean quiet

/**
* Local path where script generated classes are saved
*/
Expand Down
106 changes: 106 additions & 0 deletions modules/nextflow/src/main/groovy/nextflow/cli/CmdInspect.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2013-2023, 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.cli

import com.beust.jcommander.DynamicParameter
import com.beust.jcommander.Parameter
import com.beust.jcommander.Parameters
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import nextflow.Session
import nextflow.container.inspect.ContainersInspector
import nextflow.util.LoggerHelper
/**
* Implement `inspect` command
*
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
*/
@Slf4j
@CompileStatic
@Parameters(commandDescription = "Inspect process settings in a pipeline project")
class CmdInspect extends CmdBase {

@Override
String getName() {
return 'inspect'
}

@Parameter(names=['-concretize'], description = "Build the container images resolved by the inspect command")
boolean concretize

@Parameter(names=['-c','-config'], hidden = true)
List<String> runConfig

@Parameter(names=['-format'], description = "Inspect output format. Can be 'json' or 'config'")
String format = 'json'

@Parameter(names=['-i','-ignore-errors'], description = 'Ignore errors while inspecting the pipeline')
boolean ignoreErrors

@DynamicParameter(names = '--', hidden = true)
Map<String,String> params = new LinkedHashMap<>()

@Parameter(names='-params-file', description = 'Load script parameters from a JSON/YAML file')
String paramsFile

@Parameter(names=['-profile'], description = 'Use the given configuration profile(s)')
String profile

@Parameter(names=['-r','-revision'], description = 'Revision of the project to inspect (either a git branch, tag or commit SHA number)')
String revision

@Parameter(description = 'Project name or repository url')
List<String> args

@Override
void run() {
// configure quiet mode
LoggerHelper.setQuiet(true)
// setup the target run command
final target = new CmdRun()
target.launcher = this.launcher
target.args = args
target.profile = this.profile
target.revision = this.revision
target.runConfig = this.runConfig
target.params = this.params
target.paramsFile = this.paramsFile
target.preview = true
target.previewAction = this.&applyInspect
target.ansiLog = false
// run it
target.run()
}

protected void applyInspect(Session session) {
// disable wave await mode when running
if( session.config.wave instanceof Map )
checkWaveConfig(session.config.wave as Map)
// run the inspector
new ContainersInspector(session.dag)
.withFormat(format)
.withIgnoreErrors(ignoreErrors)
.printContainers()
}

protected void checkWaveConfig(Map wave) {
if( wave.enabled && wave.freeze )
wave.dryRun = !concretize
}

}
7 changes: 6 additions & 1 deletion modules/nextflow/src/main/groovy/nextflow/cli/CmdRun.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,11 @@ class CmdRun extends CmdBase implements HubOptions {
: sysEnv.get('NXF_DISABLE_JOBS_CANCELLATION') as boolean
}

/**
* Optional closure modelling an action to be invoked when the preview mode is enabled
*/
Closure<Void> previewAction

@Override
String getName() { NAME }

Expand Down Expand Up @@ -343,7 +348,7 @@ class CmdRun extends CmdBase implements HubOptions {
// -- create a new runner instance
final runner = new ScriptRunner(config)
runner.setScript(scriptFile)
runner.setPreview(this.preview)
runner.setPreview(this.preview, previewAction)
runner.session.profile = profile
runner.session.commandLine = launcher.cliString
runner.session.ansiLog = launcher.options.ansiLog
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ class Launcher {
new CmdHelp(),
new CmdSelfUpdate(),
new CmdPlugins(),
new CmdPlugin()
new CmdPlugin(),
new CmdInspect()
]

if(SecretsLoader.isEnabled())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright 2013-2023, 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.container.inspect

import groovy.json.JsonBuilder
import groovy.json.JsonOutput
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import nextflow.dag.DAG
import nextflow.exception.AbortOperationException
import org.codehaus.groovy.util.ListHashMap
/**
* Preview the list of containers used by a pipeline.
*
* @author Ben Sherman <bentshermann@gmail.com>
*/
@Slf4j
@CompileStatic
class ContainersInspector {

private DAG dag

private String format

private boolean ignoreErrors

ContainersInspector(DAG dag) {
this.dag = dag
}

ContainersInspector withFormat(String format) {
if( format !in ['config', 'json'] )
throw new AbortOperationException("Invalid format for containers inspect '${format}' -- should be 'config' or 'json'")
this.format = format
return this
}

ContainersInspector withIgnoreErrors(boolean ignore) {
this.ignoreErrors = ignore
return this
}

String renderContainers() {
log.debug "Rendering container preview"
final containers = getContainers()
if( format == 'config' )
return renderConfig(containers)
if( format == 'json' )
return renderJson(containers)
else
throw new IllegalStateException("Unknown containers preview format: $format")
}

void printContainers() {
final result = renderContainers()
if( result )
print result
}

protected Map<String,String> getContainers() {
final containers = new ListHashMap<String,String>()

for( def vertex : dag.vertices ) {
// skip nodes that are not processes
final process = vertex.process
if( !process )
continue

try {
// get container preview
containers[process.name] = process.createTaskPreview().getContainer()
}
catch( Exception e ) {
if( ignoreErrors )
log.warn "Unable to inspect container for task `$process.name` - cause: ${e.message}"
else
throw e
}
}

return containers
}

protected String renderConfig(Map<String,String> containers) {
final result = new StringBuilder()
for( Map.Entry<String,String> entry : containers ) {
result.append("process { withName: '${entry.key}' { container = '${entry.value}' } }\n")
}
return result.toString()
}

protected String renderJson(Map<String,String> containers) {
final list = containers.collect( (k, v) -> [name: k, container: v] )
final result = Map.of("processes", list)
return JsonOutput.prettyPrint(new JsonBuilder(result).toString()) + '\n'
}

}
Loading

0 comments on commit 94143d6

Please sign in to comment.