Skip to content

Commit

Permalink
Add build-command flag to odo dev to run non-default Build command (
Browse files Browse the repository at this point in the history
redhat-developer#5891)

* Add integration test cases

* Add documentation

* Add `build-command` flag to `odo dev`

This allows to build applications using a non-default build command.

* Update docs/website/docs/command-reference/dev.md

Co-authored-by: Philippe Martin <contact@elol.fr>

Co-authored-by: Philippe Martin <contact@elol.fr>
  • Loading branch information
2 people authored and cdrage committed Aug 31, 2022
1 parent caac761 commit b03fcf3
Show file tree
Hide file tree
Showing 9 changed files with 297 additions and 79 deletions.
53 changes: 53 additions & 0 deletions docs/website/docs/command-reference/dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,59 @@ will first delete all resources deployed into the cluster for this session befor
### Running an alternative command
#### Running an alternative build command
By default, `odo dev` builds the application using the default Build command defined in the Devfile,
i.e, the command with a group `kind` set to `build` and with `isDefault` set to `true`, if any.
Passing the `build-command` flag allows to override this behavior by running any other command, provided it is in the `build` group in the Devfile.
For example, given the following excerpt from a Devfile:
```yaml
- id: my-build
exec:
commandLine: go build main.go
component: tools
workingDir: ${PROJECT_SOURCE}
group:
isDefault: true
kind: build
- id: my-build-with-version
exec:
commandLine: go build -ldflags="-X main.version=v1.0.0" main.go
component: tools
workingDir: ${PROJECT_SOURCE}
group:
kind: build
```
- running `odo dev` will build the application using the default `my-build` command.
- running `odo dev --build-command my-build-with-version` will build the application using the `my-build-with-version` command:
```shell
$ odo dev --build-command my-build-with-version
__
/ \__ Developing using the my-sample-go Devfile
\__/ \ Namespace: default
/ \__/ odo version: v3.0.0-alpha3
\__/
↪ Deploying to the cluster in developer mode
✓ Waiting for Kubernetes resources [39s]
✓ Syncing files into the container [84ms]
✓ Building your application in container on cluster (command: my-build-with-version) [456ms]
• Executing the application (command: run) ...
Your application is now running on the cluster
- Forwarding from 127.0.0.1:40001 -> 8080
Watching for changes in the current directory /path/to/my/sources/go-app
Press Ctrl+c to exit `odo dev` and delete resources from the cluster
```
#### Running an alternative run 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`.
Expand Down
4 changes: 4 additions & 0 deletions pkg/dev/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func (o *DevClient) Start(
ignorePaths []string,
path string,
debug bool,
buildCommand string,
runCommand string,
) error {
klog.V(4).Infoln("Creating new adapter")
Expand All @@ -53,6 +54,7 @@ func (o *DevClient) Start(
Path: path,
IgnoredFiles: ignorePaths,
Debug: debug,
DevfileBuildCmd: buildCommand,
DevfileRunCmd: runCommand,
}

Expand All @@ -73,6 +75,7 @@ func (o *DevClient) Watch(
h Handler,
ctx context.Context,
debug bool,
buildCommand string,
runCommand string,
variables map[string]string,
) error {
Expand All @@ -90,6 +93,7 @@ func (o *DevClient) Watch(
FileIgnores: ignorePaths,
InitialDevfileObj: devfileObj,
Debug: debug,
DevfileBuildCmd: buildCommand,
DevfileRunCmd: runCommand,
DebugPort: envSpecificInfo.GetDebugPort(),
Variables: variables,
Expand Down
4 changes: 4 additions & 0 deletions pkg/dev/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,23 @@ import (
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.
// If buildCommand is set, this will look up the specified build command in the Devfile. Otherwise, it uses the default one.
// 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,
buildCommand string,
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.
// If buildCommand is set, this will look up the specified build command in the Devfile. Otherwise, it uses the default one.
// 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,
Expand All @@ -38,6 +41,7 @@ type Client interface {
h Handler,
ctx context.Context,
debug bool,
buildCommand string,
runCommand string,
variables map[string]string,
) error
Expand Down
2 changes: 1 addition & 1 deletion pkg/devfile/adapters/kubernetes/component/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ func (a Adapter) Push(parameters common.PushParameters) (err error) {
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)
return libdevfile.Build(a.Devfile, a.devfileBuildCmd, execHandler)
}
if componentExists {
if parameters.RunModeChanged || cmd.Exec == nil || !util.SafeGetBool(cmd.Exec.HotReloadCapable) {
Expand Down
25 changes: 15 additions & 10 deletions pkg/libdevfile/libdevfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ func Deploy(devfileObj parser.DevfileObj, handler Handler) error {
}

// Build executes the default Build command of the devfile.
// No error is returned and no operation is performed if the command is not found.
func Build(devfileObj parser.DevfileObj, handler Handler) error {
return ExecuteCommandByNameAndKind(devfileObj, "", v1alpha2.BuildCommandGroupKind, handler, true)
// If buildCmd is empty, this looks for the default Build command in the Devfile. No error is returned and no operation is performed
// if the default command could not be found.
// An error is returned if buildCmd is not empty and has no corresponding command in the Devfile.
func Build(devfileObj parser.DevfileObj, buildCmd string, handler Handler) error {
return ExecuteCommandByNameAndKind(devfileObj, buildCmd, v1alpha2.BuildCommandGroupKind, handler, buildCmd == "")
}

// ExecuteCommandByNameAndKind executes the specified command cmdName of the given kind in the Devfile.
Expand All @@ -44,19 +46,22 @@ func ExecuteCommandByNameAndKind(
handler Handler,
ignoreCommandNotFound bool,
) error {
cmd, ok, err := GetCommand(devfileObj, cmdName, kind)
cmd, hasDefaultCmd, err := GetCommand(devfileObj, cmdName, kind)
if err != nil {
if _, isNotFound := err.(NoCommandFoundError); isNotFound {
if ignoreCommandNotFound {
klog.V(3).Infof("ignoring command not found: %v", cmdName)
return nil
}
}
return err
}
if !ok {
if !hasDefaultCmd {
if ignoreCommandNotFound {
klog.V(3).Infof("ignoring command not found: %v", cmdName)
klog.V(3).Infof("ignoring default %v command not found", kind)
return nil
}
if cmdName == "" {
return NewNoDefaultCommandFoundError(kind)
}
return NewNoCommandFoundError(kind, cmdName)
return NewNoDefaultCommandFoundError(kind)
}

return executeCommand(devfileObj, cmd, handler)
Expand Down
46 changes: 45 additions & 1 deletion pkg/libdevfile/libdevfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ func TestBuild(t *testing.T) {
type args struct {
devfileObj func() parser.DevfileObj
handler func(ctrl *gomock.Controller) Handler
cmdName string
}
for _, tt := range []struct {
name string
Expand Down Expand Up @@ -437,9 +438,52 @@ func TestBuild(t *testing.T) {
},
},
},
{
name: "missing custom command",
args: args{
devfileObj: func() parser.DevfileObj {
dData, _ := data.NewDevfileData(string(data.APISchemaVersion200))
_ = dData.AddCommands([]v1alpha2.Command{defaultBuildCommand})
_ = dData.AddComponents([]v1alpha2.Component{containerComp})
return parser.DevfileObj{
Data: dData,
}
},
handler: func(ctrl *gomock.Controller) Handler {
h := NewMockHandler(ctrl)
h.EXPECT().Execute(gomock.Eq(defaultBuildCommand)).Times(0)
h.EXPECT().Execute(gomock.Eq(nonDefaultBuildCommandExplicit)).Times(0)
h.EXPECT().Execute(gomock.Eq(nonDefaultBuildCommandImplicit)).Times(0)
return h
},
cmdName: "my-explicit-non-default-build-command",
},
wantErr: true,
},
{
name: "with custom command",
args: args{
devfileObj: func() parser.DevfileObj {
dData, _ := data.NewDevfileData(string(data.APISchemaVersion200))
_ = dData.AddCommands([]v1alpha2.Command{nonDefaultBuildCommandExplicit, nonDefaultBuildCommandImplicit})
_ = dData.AddComponents([]v1alpha2.Component{containerComp})
return parser.DevfileObj{
Data: dData,
}
},
handler: func(ctrl *gomock.Controller) Handler {
h := NewMockHandler(ctrl)
h.EXPECT().Execute(gomock.Eq(defaultBuildCommand)).Times(0)
h.EXPECT().Execute(gomock.Eq(nonDefaultBuildCommandExplicit)).Times(1)
h.EXPECT().Execute(gomock.Eq(nonDefaultBuildCommandImplicit)).Times(0)
return h
},
cmdName: "my-explicit-non-default-build-command",
},
},
} {
t.Run(tt.name, func(t *testing.T) {
err := Build(tt.args.devfileObj(), tt.args.handler(gomock.NewController(t)))
err := Build(tt.args.devfileObj(), tt.args.cmdName, tt.args.handler(gomock.NewController(t)))
if (err != nil) != tt.wantErr {
t.Errorf("Build() error = %v, wantErr %v", err, tt.wantErr)
}
Expand Down
19 changes: 11 additions & 8 deletions pkg/odo/cli/dev/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,13 @@ type DevOptions struct {
contextDir string

// Flags
noWatchFlag bool
randomPortsFlag bool
debugFlag bool
varFileFlag string
varsFlag []string
runCommandFlag string
noWatchFlag bool
randomPortsFlag bool
debugFlag bool
varFileFlag string
varsFlag []string
buildCommandFlag string
runCommandFlag string

// Variables to override Devfile variables
variables map[string]string
Expand Down Expand Up @@ -216,7 +217,7 @@ func (o *DevOptions) Run(ctx context.Context) (err error) {
"odo version: "+version.VERSION)

log.Section("Deploying to the cluster in developer mode")
err = o.clientset.DevClient.Start(devFileObj, platformContext, o.ignorePaths, path, o.debugFlag, o.runCommandFlag)
err = o.clientset.DevClient.Start(devFileObj, platformContext, o.ignorePaths, path, o.debugFlag, o.buildCommandFlag, o.runCommandFlag)
if err != nil {
return err
}
Expand Down Expand Up @@ -272,7 +273,7 @@ func (o *DevOptions) Run(ctx context.Context) (err error) {
err = o.clientset.WatchClient.CleanupDevResources(devFileObj, log.GetStdout())
} else {
d := Handler{}
err = o.clientset.DevClient.Watch(devFileObj, path, o.ignorePaths, o.out, &d, o.ctx, o.debugFlag, o.runCommandFlag, o.variables)
err = o.clientset.DevClient.Watch(devFileObj, path, o.ignorePaths, o.out, &d, o.ctx, o.debugFlag, o.buildCommandFlag, o.runCommandFlag, o.variables)
}
return err
}
Expand Down Expand Up @@ -338,6 +339,8 @@ It forwards endpoints with exposure values 'public' or 'internal' to a port on l
devCmd.Flags().BoolVar(&o.debugFlag, "debug", false, "Execute the debug command within the component")
devCmd.Flags().StringArrayVar(&o.varsFlag, "var", []string{}, "Variable to override Devfile variable and variables in var-file")
devCmd.Flags().StringVar(&o.varFileFlag, "var-file", "", "File containing variables to override Devfile variables")
devCmd.Flags().StringVar(&o.buildCommandFlag, "build-command", "",
"Alternative build command. The default one will be used if this flag is not set.")
devCmd.Flags().StringVar(&o.runCommandFlag, "run-command", "",
"Alternative run command to execute. The default one will be used if this flag is not set.")
clientset.Add(devCmd, clientset.DEV, clientset.INIT, clientset.KUBERNETES, clientset.STATE, clientset.FILESYSTEM)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ commands:
group:
kind: build
isDefault: true
- id: my-custom-build
exec:
component: runtime
commandLine: 'echo waiting for one second before starting & sleep 1; mkdir /projects/file-from-my-custom-build && npm install'
workingDir: ${PROJECTS_ROOT}
group:
kind: build
- id: devrun
exec:
component: runtime
Expand Down
Loading

0 comments on commit b03fcf3

Please sign in to comment.