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

Add an odo run command to manually execute command during odo dev #6857

Merged
1 change: 1 addition & 0 deletions cmd/odo/help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Examples:
init Init bootstraps a new project
logs Show logs of all containers of the component
registry List all components from the Devfile registry
run Run a specific command in the Dev mode

`

Expand Down
53 changes: 53 additions & 0 deletions pkg/dev/common/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package common

import (
"context"
"fmt"

"github.com/redhat-developer/odo/pkg/component"
"github.com/redhat-developer/odo/pkg/configAutomount"
"github.com/redhat-developer/odo/pkg/devfile/image"
"github.com/redhat-developer/odo/pkg/exec"
"github.com/redhat-developer/odo/pkg/libdevfile"
odocontext "github.com/redhat-developer/odo/pkg/odo/context"
"github.com/redhat-developer/odo/pkg/platform"
"github.com/redhat-developer/odo/pkg/testingutil/filesystem"
)

func Run(
ctx context.Context,
commandName string,
platformClient platform.Client,
execClient exec.Client,
configAutomountClient configAutomount.Client,
filesystem filesystem.Filesystem,
) error {
var (
componentName = odocontext.GetComponentName(ctx)
devfileObj = odocontext.GetEffectiveDevfileObj(ctx)
devfilePath = odocontext.GetDevfilePath(ctx)
)

pod, err := platformClient.GetPodUsingComponentName(componentName)
if err != nil {
return fmt.Errorf("unable to get pod for component %s: %w. Please check the command odo dev is running", componentName, err)
rm3l marked this conversation as resolved.
Show resolved Hide resolved
}

handler := component.NewRunHandler(
ctx,
platformClient,
execClient,
configAutomountClient,
pod.Name,
false,
component.GetContainersNames(pod),
"Executing command in container",

filesystem,
image.SelectBackend(ctx),
*devfileObj,
devfilePath,
)

return libdevfile.ExecuteCommandByName(ctx, *devfileObj, commandName, handler, false)
}
5 changes: 5 additions & 0 deletions pkg/dev/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ type Client interface {
options StartOptions,
) error

Run(
ctx context.Context,
commandName string,
) error

// CleanupResources deletes the component created using the context's devfile and writes any outputs to out
CleanupResources(ctx context.Context, out io.Writer) error
}
23 changes: 23 additions & 0 deletions pkg/dev/kubedev/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package kubedev

import (
"context"

"github.com/redhat-developer/odo/pkg/dev/common"
"k8s.io/klog"
)

func (o *DevClient) Run(
ctx context.Context,
commandName string,
) error {
klog.V(4).Infof("running command %q on cluster", commandName)
return common.Run(
ctx,
commandName,
o.kubernetesClient,
o.execClient,
o.configAutomountClient,
o.filesystem,
)
}
14 changes: 14 additions & 0 deletions pkg/dev/mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions pkg/dev/podmandev/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package podmandev

import (
"context"

"github.com/redhat-developer/odo/pkg/dev/common"
"k8s.io/klog"
)

func (o *DevClient) Run(
ctx context.Context,
commandName string,
) error {
klog.V(4).Infof("running command %q on cluster", commandName)
rm3l marked this conversation as resolved.
Show resolved Hide resolved
return common.Run(
ctx,
commandName,
o.podmanClient,
o.execClient,
nil, // TODO(feloy) set when running on new container is supported on podman
o.fs,
)
}
4 changes: 4 additions & 0 deletions pkg/exec/exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ func (o fakePlatform) GetRunningPodFromSelector(selector string) (*corev1.Pod, e
panic("not implemented yet")
}

func (o fakePlatform) GetPodUsingComponentName(componentName string) (*corev1.Pod, error) {
panic("not implemented yet")
}

func TestExecuteCommand(t *testing.T) {
for _, tt := range []struct {
name string
Expand Down
3 changes: 3 additions & 0 deletions pkg/libdevfile/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ func (e NoCommandFoundError) Error() string {
if e.name == "" {
return fmt.Sprintf("no %s command found in devfile", e.kind)
}
if e.kind == "" {
return fmt.Sprintf("no command named %q found in devfile", e.name)
}
return fmt.Sprintf("no %s command with name %q found in Devfile", e.kind, e.name)
}

Expand Down
20 changes: 20 additions & 0 deletions pkg/libdevfile/libdevfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,26 @@ func ExecuteCommandByNameAndKind(ctx context.Context, devfileObj parser.DevfileO
return executeCommand(ctx, devfileObj, cmd, handler)
}

// ExecuteCommandByNameAndKind executes the specified command cmdName of the given kind in the Devfile.
rm3l marked this conversation as resolved.
Show resolved Hide resolved
// If cmdName is empty, it executes the default command for the given kind or returns an error if there is no default command.
// If ignoreCommandNotFound is true, nothing is executed if the command is not found and no error is returned.
func ExecuteCommandByName(ctx context.Context, devfileObj parser.DevfileObj, cmdName string, handler Handler, ignoreCommandNotFound bool) error {
commands, err := devfileObj.Data.GetCommands(
common.DevfileOptions{
FilterByName: cmdName,
},
)
if err != nil {
return err
}
if len(commands) != 1 {
return NewNoCommandFoundError("", cmdName)
}

cmd := commands[0]
return executeCommand(ctx, devfileObj, cmd, handler)
}

// executeCommand executes a specific command of a devfile using handler as backend
func executeCommand(ctx context.Context, devfileObj parser.DevfileObj, command v1alpha2.Command, handler Handler) error {
cmd, err := newCommand(devfileObj, command)
Expand Down
2 changes: 2 additions & 0 deletions pkg/odo/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"unicode"

"github.com/redhat-developer/odo/pkg/odo/cli/logs"
"github.com/redhat-developer/odo/pkg/odo/cli/run"
"github.com/redhat-developer/odo/pkg/odo/commonflags"

"github.com/redhat-developer/odo/pkg/log"
Expand Down Expand Up @@ -195,6 +196,7 @@ func odoRootCmd(ctx context.Context, name, fullName string) *cobra.Command {
set.NewCmdSet(set.RecommendedCommandName, util.GetFullName(fullName, set.RecommendedCommandName)),
logs.NewCmdLogs(logs.RecommendedCommandName, util.GetFullName(fullName, logs.RecommendedCommandName)),
completion.NewCmdCompletion(completion.RecommendedCommandName, util.GetFullName(fullName, completion.RecommendedCommandName)),
run.NewCmdRun(run.RecommendedCommandName, util.GetFullName(fullName, run.RecommendedCommandName)),
)

// Add all subcommands to base commands
Expand Down
14 changes: 14 additions & 0 deletions pkg/odo/cli/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ func (o NoCommandInDevfileError) Error() string {
return fmt.Sprintf("no command of kind %q found in the devfile", o.command)
}

type NoCommandNameInDevfileError struct {
name string
}

func NewNoCommandNameInDevfileError(name string) NoCommandNameInDevfileError {
return NoCommandNameInDevfileError{
name: name,
}
}

func (o NoCommandNameInDevfileError) Error() string {
return fmt.Sprintf("no command named %q found in the devfile", o.name)
}

type Warning struct {
msg string
err error
Expand Down
119 changes: 119 additions & 0 deletions pkg/odo/cli/run/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package run

import (
"context"
"fmt"

"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
"github.com/spf13/cobra"

"github.com/redhat-developer/odo/pkg/kclient"
"github.com/redhat-developer/odo/pkg/odo/cli/errors"
"github.com/redhat-developer/odo/pkg/odo/cmdline"
"github.com/redhat-developer/odo/pkg/odo/commonflags"
fcontext "github.com/redhat-developer/odo/pkg/odo/commonflags/context"
odocontext "github.com/redhat-developer/odo/pkg/odo/context"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions"
"github.com/redhat-developer/odo/pkg/odo/genericclioptions/clientset"
odoutil "github.com/redhat-developer/odo/pkg/odo/util"
"github.com/redhat-developer/odo/pkg/podman"
scontext "github.com/redhat-developer/odo/pkg/segment/context"

ktemplates "k8s.io/kubectl/pkg/util/templates"
)

const (
RecommendedCommandName = "run"
)

type RunOptions struct {
// Clients
clientset *clientset.Clientset

// Args
commandName string
}

var _ genericclioptions.Runnable = (*RunOptions)(nil)

func NewRunOptions() *RunOptions {
return &RunOptions{}
}

var runExample = ktemplates.Examples(`
# Run the command "my-command" in the Dev mode
%[1]s my-command

`)

func (o *RunOptions) SetClientset(clientset *clientset.Clientset) {
o.clientset = clientset
}

func (o *RunOptions) Complete(ctx context.Context, cmdline cmdline.Cmdline, args []string) error {
o.commandName = args[0] // Value at 0 is expected to exist, thanks to ExactArgs(1)
return nil
}

func (o *RunOptions) Validate(ctx context.Context) error {
var (
devfileObj = odocontext.GetEffectiveDevfileObj(ctx)
platform = fcontext.GetPlatform(ctx, commonflags.PlatformCluster)
)

if devfileObj == nil {
return genericclioptions.NewNoDevfileError(odocontext.GetWorkingDirectory(ctx))
}

commands, err := devfileObj.Data.GetCommands(common.DevfileOptions{
FilterByName: o.commandName,
})
if err != nil || len(commands) != 1 {
return errors.NewNoCommandNameInDevfileError(o.commandName)
rm3l marked this conversation as resolved.
Show resolved Hide resolved
}

switch platform {

case commonflags.PlatformCluster:
if o.clientset.KubernetesClient == nil {
return kclient.NewNoConnectionError()
}
scontext.SetPlatform(ctx, o.clientset.KubernetesClient)

case commonflags.PlatformPodman:
if o.clientset.PodmanClient == nil {
return podman.NewPodmanNotFoundError(nil)
}
scontext.SetPlatform(ctx, o.clientset.PodmanClient)
}
return nil
}

func (o *RunOptions) Run(ctx context.Context) (err error) {
return o.clientset.DevClient.Run(ctx, o.commandName)
}

func NewCmdRun(name, fullName string) *cobra.Command {
o := NewRunOptions()
runCmd := &cobra.Command{
Use: name,
Short: "Run a specific command in the Dev mode",
Long: `odo run executes a specific command of the Devfile during the Dev mode ("odo dev" needs to be running)`,
Example: fmt.Sprintf(runExample, fullName),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return genericclioptions.GenericRun(o, cmd, args)
},
}
clientset.Add(runCmd,
clientset.FILESYSTEM,
clientset.KUBERNETES_NULLABLE,
clientset.PODMAN_NULLABLE,
clientset.DEV,
)

odoutil.SetCommandGroup(runCmd, odoutil.MainGroup)
runCmd.SetUsageTemplate(odoutil.CmdUsageTemplate)
commonflags.UsePlatformFlag(runCmd)
return runCmd
}
2 changes: 2 additions & 0 deletions pkg/platform/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@ type Client interface {
// GetRunningPodFromSelector returns any pod matching the given label selector.
// If multiple pods are found, implementations might have different behavior, by either returning an error or returning any element.
GetRunningPodFromSelector(selector string) (*corev1.Pod, error)

GetPodUsingComponentName(componentName string) (*corev1.Pod, error)
}
15 changes: 15 additions & 0 deletions pkg/platform/mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading