Skip to content

Commit

Permalink
Add run-command flag to odo dev to run non-default Run command (r…
Browse files Browse the repository at this point in the history
…edhat-developer#5878)

* Add documentation

* Add integration test cases

* Refactor functions in 'pkg/libdevfile/libdevfile.go'

Few functions used to have similar but slightly different logic,
which could make them quite confusing to use.
This also fixes a behavior which is not supposed to be
part of the Devfile spec: in the past, if a Devfile contains a single
but non-default command for a given kind, this command would get returned
as the Default command for such kind.
Now, an explicit error is returned instead if this single command
is not marked as default in the Devfile.

* Add `run-command` to `odo dev`, to run non-default run commands

* Fix documentation of a few libdevfile functions, as suggested in review
  • Loading branch information
rm3l authored and cdrage committed Aug 31, 2022
1 parent 367d11b commit 5b190fa
Show file tree
Hide file tree
Showing 13 changed files with 841 additions and 1,800 deletions.
57 changes: 56 additions & 1 deletion docs/website/docs/command-reference/dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,62 @@ In the above example, three things have happened:
You can press Ctrl-c at any time to terminate the development session. The command can take a few moment to terminate, as it
will first delete all resources deployed into the cluster for this session before terminating.
### Sustituting variables
### Running an alternative command
By default, `odo dev` executes the default Run command defined in the Devfile,
i.e, the command with a group `kind` set to `run` and its `isDefault` field set to `true`.
Passing the `run-command` flag allows to override this behavior by running any other non-default command, provided it is in the `run` group in the Devfile.
For example, given the following excerpt from a Devfile:
```yaml
- id: my-run
exec:
commandLine: mvn spring-boot:run
component: tools
workingDir: ${PROJECT_SOURCE}
group:
isDefault: true
kind: run
- id: my-run-with-postgres
exec:
commandLine: mvn spring-boot:run -Dspring-boot.run.profiles=postgres
component: tools
workingDir: ${PROJECT_SOURCE}
group:
isDefault: false
kind: run
```
- running `odo dev` will run the default `my-run` command
- running `odo dev --run-command my-run-with-postgres` will run the `my-run-with-postgres` command:
```shell
$ odo dev --run-command my-run-with-postgres
__
/ \__ Developing using the my-java-springboot-app Devfile
\__/ \ Namespace: default
/ \__/ odo version: v3.0.0-alpha3
\__/
↪ Deploying to the cluster in developer mode
✓ Added storage m2 to my-java-springboot-app
✓ Creating kind ServiceBinding [8ms]
✓ Waiting for Kubernetes resources [39s]
✓ Syncing files into the container [84ms]
✓ Building your application in container on cluster (command: build) [51s]
• Executing the application (command: my-run-with-postgres) ...
Your application is now running on the cluster
- Forwarding from 127.0.0.1:40002 -> 8080
Watching for changes in the current directory /path/to/my/sources/java-springboot-app
Press Ctrl+c to exit `odo dev` and delete resources from the cluster
```
### Substituting variables
The Devfile can define variables to make the Devfile parameterizable. The Devfile can define values for these variables, and you
can override the values for variables from the command line when running `odo dev`, using the `--var` and `--var-file` options.
Expand Down
26 changes: 23 additions & 3 deletions pkg/dev/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import (
"github.com/redhat-developer/odo/pkg/envinfo"

"github.com/devfile/library/pkg/devfile/parser"
"k8s.io/klog/v2"

"github.com/redhat-developer/odo/pkg/devfile/adapters"
"github.com/redhat-developer/odo/pkg/devfile/adapters/common"
"github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes"
"github.com/redhat-developer/odo/pkg/watch"
"k8s.io/klog/v2"
)

// this causes compilation to fail if DevClient struct doesn't implement Client interface
Expand All @@ -27,7 +28,14 @@ func NewDevClient(watchClient watch.Client) *DevClient {
}
}

func (o *DevClient) Start(devfileObj parser.DevfileObj, platformContext kubernetes.KubernetesContext, ignorePaths []string, path string, debug bool) error {
func (o *DevClient) Start(
devfileObj parser.DevfileObj,
platformContext kubernetes.KubernetesContext,
ignorePaths []string,
path string,
debug bool,
runCommand string,
) error {
klog.V(4).Infoln("Creating new adapter")
adapter, err := adapters.NewComponentAdapter(devfileObj.GetMetadataName(), path, "app", devfileObj, platformContext)
if err != nil {
Expand All @@ -45,6 +53,7 @@ func (o *DevClient) Start(devfileObj parser.DevfileObj, platformContext kubernet
Path: path,
IgnoredFiles: ignorePaths,
Debug: debug,
DevfileRunCmd: runCommand,
}

klog.V(4).Infoln("Creating inner-loop resources for the component")
Expand All @@ -56,7 +65,17 @@ func (o *DevClient) Start(devfileObj parser.DevfileObj, platformContext kubernet
return nil
}

func (o *DevClient) Watch(devfileObj parser.DevfileObj, path string, ignorePaths []string, out io.Writer, h Handler, ctx context.Context, debug bool, variables map[string]string) error {
func (o *DevClient) Watch(
devfileObj parser.DevfileObj,
path string,
ignorePaths []string,
out io.Writer,
h Handler,
ctx context.Context,
debug bool,
runCommand string,
variables map[string]string,
) error {
envSpecificInfo, err := envinfo.NewEnvSpecificInfo(path)
if err != nil {
return err
Expand All @@ -71,6 +90,7 @@ func (o *DevClient) Watch(devfileObj parser.DevfileObj, path string, ignorePaths
FileIgnores: ignorePaths,
InitialDevfileObj: devfileObj,
Debug: debug,
DevfileRunCmd: runCommand,
DebugPort: envSpecificInfo.GetDebugPort(),
Variables: variables,
}
Expand Down
28 changes: 24 additions & 4 deletions pkg/dev/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,40 @@ import (
"github.com/redhat-developer/odo/pkg/devfile/adapters/common"

"github.com/devfile/library/pkg/devfile/parser"

"github.com/redhat-developer/odo/pkg/devfile/adapters/kubernetes"
"github.com/redhat-developer/odo/pkg/watch"
)

type Client interface {
// Start the resources in devfileObj on the platformContext. It then pushes the files in path to the container.
// If debug is true, executes the debug command, or the run command by default
Start(devfileObj parser.DevfileObj, platformContext kubernetes.KubernetesContext, ignorePaths []string, path string, debug bool) error
// If debug is true, executes the debug command, or the run command by default.
// If runCommand is set, this will look up the specified run command in the Devfile and execute it. Otherwise, it uses the default one.
Start(
devfileObj parser.DevfileObj,
platformContext kubernetes.KubernetesContext,
ignorePaths []string,
path string,
debug bool,
runCommand string,
) error

// Watch watches for any changes to the files under path while ignoring the files/directories in ignorePaths.
// It logs messages to out and uses the Handler h to perform push operation when anything changes in path.
// It uses devfileObj to notify user to restart odo dev if they change endpoint information in the devfile.
// If debug is true, the debug command will be started after a sync, or the run command by default
Watch(devfileObj parser.DevfileObj, path string, ignorePaths []string, out io.Writer, h Handler, ctx context.Context, debug bool, variables map[string]string) error
// If debug is true, the debug command will be started after a sync, or the run command by default.
// If runCommand is set, this will look up the specified run command in the Devfile and execute it. Otherwise, it uses the default one.
Watch(
devfileObj parser.DevfileObj,
path string,
ignorePaths []string,
out io.Writer,
h Handler,
ctx context.Context,
debug bool,
runCommand string,
variables map[string]string,
) error
}

type Handler interface {
Expand Down
28 changes: 15 additions & 13 deletions pkg/devfile/adapters/kubernetes/component/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) {
return err
}

pushDevfileCommands, err := libdevfile.ValidateAndGetPushCommands(a.Devfile.Data, a.devfileBuildCmd, a.devfileRunCmd)
pushDevfileCommands, err := libdevfile.ValidateAndGetPushCommands(a.Devfile, a.devfileBuildCmd, a.devfileRunCmd)
if err != nil {
return fmt.Errorf("failed to validate devfile build and run commands: %w", err)
}
Expand All @@ -158,9 +158,9 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) {
currentMode := envinfo.Run

if parameters.Debug {
pushDevfileDebugCommands, e := libdevfile.ValidateAndGetDebugCommands(a.Devfile.Data, a.devfileDebugCmd)
pushDevfileDebugCommands, e := libdevfile.ValidateAndGetCommand(a.Devfile, a.devfileDebugCmd, devfilev1.DebugCommandGroupKind)
if e != nil {
return fmt.Errorf("debug command is not valid: %w", err)
return fmt.Errorf("debug command is not valid: %w", e)
}
pushDevfileCommands[devfilev1.DebugCommandGroupKind] = pushDevfileDebugCommands
currentMode = envinfo.Debug
Expand Down Expand Up @@ -306,27 +306,29 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) {
}

cmdKind := devfilev1.RunCommandGroupKind
cmdName := a.devfileRunCmd
if parameters.Debug {
cmdKind = devfilev1.DebugCommandGroupKind
cmdName = a.devfileDebugCmd
}

cmd, err := libdevfile.GetDefaultCommand(a.Devfile, cmdKind)
cmd, err := libdevfile.ValidateAndGetCommand(a.Devfile, cmdName, cmdKind)
if err != nil {
return err
}

cmdHandler := adapterHandler{
Adapter: a,
parameters: parameters,
componentExists: componentExists,
}

commandType, err := devfileCommon.GetCommandType(cmd)
if err != nil {
return err
}
var running bool
var isComposite bool
cmdHandler := adapterHandler{
Adapter: a,
parameters: parameters,
componentExists: componentExists,
}

if commandType == devfilev1.ExecCommandType {
running, err = cmdHandler.isRemoteProcessForCommandRunning(cmd)
if err != nil {
Expand All @@ -345,12 +347,12 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) {
running, execRequired, parameters.RunModeChanged)

if isComposite || !running || execRequired || parameters.RunModeChanged {
// Invoke the build command once (before calling libdevfile.ExecuteCommandByKind), as, if cmd is a composite command,
// Invoke the build command once (before calling libdevfile.ExecuteCommandByNameAndKind), as, if cmd is a composite command,
// the handler we pass will be called for each command in that composite command.
doExecuteBuildCommand := func() error {
execHandler := component.NewExecHandler(a.kubeClient, a.AppName, a.ComponentName, a.pod.Name,
"Building your application in container on cluster", parameters.Show)
return libdevfile.Build(a.Devfile, execHandler, true)
return libdevfile.Build(a.Devfile, execHandler)
}
if componentExists {
if parameters.RunModeChanged || cmd.Exec == nil || !util.SafeGetBool(cmd.Exec.HotReloadCapable) {
Expand All @@ -363,7 +365,7 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) {
return err
}
}
err = libdevfile.ExecuteCommandByKind(a.Devfile, cmdKind, &cmdHandler, false)
err = libdevfile.ExecuteCommandByNameAndKind(a.Devfile, cmdName, cmdKind, &cmdHandler, false)
}

return err
Expand Down
32 changes: 23 additions & 9 deletions pkg/devfile/adapters/kubernetes/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,35 +99,45 @@ func UpdateContainersEntrypointsIfNeeded(
devfileRunCmd string,
devfileDebugCmd string,
) ([]corev1.Container, error) {
buildCmd, err := libdevfile.GetBuildCommand(devfileObj.Data, devfileBuildCmd)
buildCommand, hasBuildCmd, err := libdevfile.GetCommand(devfileObj, devfileBuildCmd, v1alpha2.BuildCommandGroupKind)
if err != nil {
return nil, err
}
runCommand, err := libdevfile.GetRunCommand(devfileObj.Data, devfileRunCmd)
runCommand, hasRunCmd, err := libdevfile.GetCommand(devfileObj, devfileRunCmd, v1alpha2.RunCommandGroupKind)
if err != nil {
return nil, err
}
debugCommand, err := libdevfile.GetDebugCommand(devfileObj.Data, devfileDebugCmd)
debugCommand, hasDebugCmd, err := libdevfile.GetCommand(devfileObj, devfileDebugCmd, v1alpha2.DebugCommandGroupKind)
if err != nil {
return nil, err
}

var cmdsToHandle []v1alpha2.Command
if hasBuildCmd {
cmdsToHandle = append(cmdsToHandle, buildCommand)
}
if hasRunCmd {
cmdsToHandle = append(cmdsToHandle, runCommand)
}
if hasDebugCmd {
cmdsToHandle = append(cmdsToHandle, debugCommand)
}

var components []string
var containerComps []string
for _, cmd := range []v1alpha2.Command{buildCmd, runCommand, debugCommand} {
for _, cmd := range cmdsToHandle {
containerComps, err = libdevfile.GetContainerComponentsForCommand(devfileObj, cmd)
if err != nil {
return nil, err
}
components = append(components, containerComps...)
}

for i := range containers {
container := &containers[i]
for _, c := range components {
for _, c := range components {
for i := range containers {
container := &containers[i]
if container.Name == c {
overrideContainerCommandAndArgsIfNeeded(container)
break
}
}
}
Expand All @@ -144,10 +154,14 @@ func UpdateContainerEnvVars(
devfileDebugPort int,
) ([]corev1.Container, error) {

debugCommand, err := libdevfile.GetDebugCommand(devfileObj.Data, devfileDebugCmd)
debugCommand, hasDebugCmd, err := libdevfile.GetCommand(devfileObj, devfileDebugCmd, v1alpha2.DebugCommandGroupKind)
if err != nil {
return nil, err
}
if !hasDebugCmd {
return containers, nil
}

debugContainers, err := libdevfile.GetContainerComponentsForCommand(devfileObj, debugCommand)
if err != nil {
return nil, err
Expand Down
9 changes: 7 additions & 2 deletions pkg/libdevfile/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,20 @@ import (
// NoCommandFoundError is returned when no command of the specified kind is found in devfile
type NoCommandFoundError struct {
kind v1alpha2.CommandGroupKind
name string
}

func NewNoCommandFoundError(kind v1alpha2.CommandGroupKind) NoCommandFoundError {
func NewNoCommandFoundError(kind v1alpha2.CommandGroupKind, name string) NoCommandFoundError {
return NoCommandFoundError{
kind: kind,
name: name,
}
}
func (e NoCommandFoundError) Error() string {
return fmt.Sprintf("no %s command found in devfile", e.kind)
if e.name == "" {
return fmt.Sprintf("no %s command found in devfile", e.kind)
}
return fmt.Sprintf("no %s command with name %q found in Devfile", e.kind, e.name)
}

// NoDefaultCommandFoundError is returned when several commands of the specified kind exist
Expand Down
Loading

0 comments on commit 5b190fa

Please sign in to comment.