Skip to content

Commit 369a40d

Browse files
authored
Merge pull request #1863 from pratikjagrut/terminal.cmd
Run cmd locally if no imageSelector and labelSelector is provided in dev.terminal
2 parents fcee7d8 + 6685c3e commit 369a40d

File tree

5 files changed

+128
-45
lines changed

5 files changed

+128
-45
lines changed

cmd/dev.go

Lines changed: 67 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ package cmd
22

33
import (
44
"fmt"
5-
runtimevar "github.com/loft-sh/devspace/pkg/devspace/config/loader/variable/runtime"
6-
"github.com/loft-sh/devspace/pkg/devspace/imageselector"
75
"io"
86
"os"
7+
"os/exec"
98
"strings"
109
"sync"
1110
"time"
1211

12+
runtimevar "github.com/loft-sh/devspace/pkg/devspace/config/loader/variable/runtime"
13+
"github.com/loft-sh/devspace/pkg/devspace/imageselector"
14+
"github.com/loft-sh/devspace/pkg/util/command"
15+
1316
"github.com/loft-sh/devspace/pkg/devspace/config"
1417
"github.com/loft-sh/devspace/pkg/devspace/config/legacy"
1518
"github.com/loft-sh/devspace/pkg/devspace/dependency/types"
@@ -600,30 +603,52 @@ func (cmd *DevCmd) startOutput(configInterface config.Config, dependencies []typ
600603
return 0, pluginErr
601604
}
602605

603-
selectorOptions := targetselector.NewDefaultOptions().ApplyCmdParameter("", "", cmd.Namespace, "")
604-
if config.Dev.Terminal != nil {
605-
selectorOptions = selectorOptions.ApplyConfigParameter(config.Dev.Terminal.LabelSelector, config.Dev.Terminal.Namespace, config.Dev.Terminal.ContainerName, "")
606-
}
606+
stdout, stderr, stdin := defaultStdStreams(cmd.Stdout, cmd.Stderr, cmd.Stdin)
607607

608-
var imageSelectors []imageselector.ImageSelector
609-
if config.Dev.Terminal != nil && config.Dev.Terminal.ImageSelector != "" {
610-
imageSelector, err := runtimevar.NewRuntimeResolver(true).FillRuntimeVariablesAsImageSelector(config.Dev.Terminal.ImageSelector, configInterface, dependencies)
608+
// if config.Dev.Terminal is defined
609+
// config.Dev.Terminal.ImageSelector is empty &&
610+
// config.Dev.Terminal.LabelSelector is also empty &&
611+
// config.Dev.Terminal.Command is defined then
612+
// run the command locally instead on in container
613+
if config.Dev.Terminal.ImageSelector == "" &&
614+
config.Dev.Terminal.LabelSelector == nil &&
615+
config.Dev.Terminal.Command != nil &&
616+
len(config.Dev.Terminal.Command) > 0 {
617+
c := command.NewStreamCommand(config.Dev.Terminal.Command[0], config.Dev.Terminal.Command[1:])
618+
err := c.Run(stdout, stderr, stdin)
611619
if err != nil {
612-
return 0, err
620+
if exitError, ok := err.(*exec.ExitError); ok {
621+
cmd.log.Failf("Command '%s' returned an error: %s", config.Dev.Terminal.Command[0], err)
622+
return exitError.ExitCode(), err
623+
}
624+
}
625+
return 0, nil
626+
} else {
627+
selectorOptions := targetselector.NewDefaultOptions().ApplyCmdParameter("", "", cmd.Namespace, "")
628+
if config.Dev.Terminal != nil {
629+
selectorOptions = selectorOptions.ApplyConfigParameter(config.Dev.Terminal.LabelSelector, config.Dev.Terminal.Namespace, config.Dev.Terminal.ContainerName, "")
613630
}
614631

615-
imageSelectors = append(imageSelectors, *imageSelector)
616-
}
632+
var imageSelectors []imageselector.ImageSelector
633+
if config.Dev.Terminal != nil && config.Dev.Terminal.ImageSelector != "" {
634+
imageSelector, err := runtimevar.NewRuntimeResolver(true).FillRuntimeVariablesAsImageSelector(config.Dev.Terminal.ImageSelector, configInterface, dependencies)
635+
if err != nil {
636+
return 0, err
637+
}
617638

618-
cmd.log.Info("Terminal: Waiting for containers to start...")
619-
selectorOptions.ImageSelector = imageSelectors
620-
stdout, stderr, stdin := defaultStdStreams(cmd.Stdout, cmd.Stderr, cmd.Stdin)
621-
code, err := servicesClient.StartTerminal(selectorOptions, args, cmd.WorkingDirectory, exitChan, true, cmd.TerminalReconnect, stdout, stderr, stdin)
622-
if services.IsUnexpectedExitCode(code) {
623-
cmd.log.Warnf("Command terminated with exit code %d", code)
624-
}
639+
imageSelectors = append(imageSelectors, *imageSelector)
640+
}
625641

626-
return code, err
642+
cmd.log.Info("Terminal: Waiting for containers to start...")
643+
selectorOptions.ImageSelector = imageSelectors
644+
645+
code, err := servicesClient.StartTerminal(selectorOptions, args, cmd.WorkingDirectory, exitChan, true, cmd.TerminalReconnect, stdout, stderr, stdin)
646+
if services.IsUnexpectedExitCode(code) {
647+
cmd.log.Warnf("Command terminated with exit code %d", code)
648+
}
649+
650+
return code, err
651+
}
627652
} else if config.Dev.Logs == nil || config.Dev.Logs.Disabled == nil || !*config.Dev.Logs.Disabled {
628653
pluginErr := hook.ExecuteHooks(client, configInterface, dependencies, nil, cmd.log, "devCommand:before:streamLogs")
629654
if pluginErr != nil {
@@ -754,32 +779,31 @@ func (cmd *DevCmd) loadConfig(configOptions *loader.ConfigOptions) (config.Confi
754779
// check if terminal is enabled
755780
c := configInterface.Config()
756781

757-
if cmd.Terminal || (c.Dev.Terminal != nil && !c.Dev.Terminal.Disabled) {
758-
if c.Dev.Terminal == nil || (c.Dev.Terminal.ImageSelector == "" && len(c.Dev.Terminal.LabelSelector) == 0) {
759-
imageNames := make([]string, 0, len(c.Images))
760-
for k := range c.Images {
761-
imageNames = append(imageNames, k)
762-
}
782+
if cmd.Terminal && c.Dev.Terminal == nil {
783+
if len(c.Images) == 0 {
784+
return nil, errors.New("No image available in devspace config")
785+
}
763786

764-
// if only one image exists, use it, otherwise show image picker
765-
imageName := ""
766-
if len(imageNames) == 1 {
767-
imageName = imageNames[0]
768-
} else {
769-
imageName, err = cmd.log.Question(&survey.QuestionOptions{
770-
Question: "Which image do you want to open a terminal to?",
771-
Options: imageNames,
772-
})
773-
if err != nil {
774-
return nil, err
775-
}
776-
}
787+
imageNames := make([]string, 0, len(c.Images))
788+
for k := range c.Images {
789+
imageNames = append(imageNames, k)
790+
}
777791

778-
c.Dev.Terminal = &latest.Terminal{
779-
ImageSelector: fmt.Sprintf("${runtime.images.%s.image}:${runtime.images.%s.tag}", imageName, imageName),
780-
}
792+
// if only one image exists, use it, otherwise show image picker
793+
imageName := ""
794+
if len(imageNames) == 1 {
795+
imageName = imageNames[0]
781796
} else {
782-
c.Dev.Terminal.Disabled = false
797+
imageName, err = cmd.log.Question(&survey.QuestionOptions{
798+
Question: "Which image do you want to open a terminal to?",
799+
Options: imageNames,
800+
})
801+
if err != nil {
802+
return nil, err
803+
}
804+
}
805+
c.Dev.Terminal = &latest.Terminal{
806+
ImageSelector: fmt.Sprintf("${runtime.images.%s.image}:${runtime.images.%s.tag}", imageName, imageName),
783807
}
784808
}
785809

docs/pages/configuration/development/terminal.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ dev:
8484
containerName: container1
8585
```
8686

87+
:::note Execute command locally
88+
If `imageSelector` and `labelSelector` are not defined then the `command` will be executed on your local machine.
89+
:::
90+
8791
### `containerName`
8892
If you select a pod via `labelSelector` and the pod has multiple containers, you'll need to specify a container name with this option.
8993

e2e/tests/terminal/terminal.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,30 @@ var _ = DevSpaceDescribe("terminal", func() {
133133
err = <-done
134134
framework.ExpectNoError(err)
135135
})
136+
137+
ginkgo.It("should run command locally", func() {
138+
tempDir, err := framework.CopyToTempDir("tests/terminal/testdata/run_cmd_locally")
139+
framework.ExpectNoError(err)
140+
defer framework.CleanupTempDir(initialDir, tempDir)
141+
142+
ns, err := kubeClient.CreateNamespace("terminal")
143+
framework.ExpectNoError(err)
144+
defer framework.ExpectDeleteNamespace(kubeClient, ns)
145+
146+
interrupt := make(chan error)
147+
stdout := &Buffer{}
148+
devCmd := &cmd.DevCmd{
149+
GlobalFlags: &flags.GlobalFlags{
150+
NoWarn: true,
151+
Namespace: ns,
152+
},
153+
Interrupt: interrupt,
154+
Stdout: stdout,
155+
}
156+
err = devCmd.Run(f, nil)
157+
framework.ExpectNoError(err)
158+
framework.ExpectEqual("hello", strings.TrimSuffix(stdout.String(), "\n"))
159+
})
136160
})
137161

138162
// Buffer is a goroutine safe bytes.Buffer

e2e/tests/terminal/testdata/restart/devspace.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version: v1beta10
1+
version: v1beta11
22
vars:
33
- name: IMAGE
44
value: john/devbackend
@@ -28,4 +28,4 @@ dev:
2828
path: spec.containers[0].workingDir
2929
value: "/workdir"
3030
terminal:
31-
imageSelector: ${IMAGE}
31+
imageSelector: ${IMAGE}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
version: v1beta11
2+
vars:
3+
- name: IMAGE
4+
value: john/devbackend
5+
deployments:
6+
- name: test
7+
helm:
8+
componentChart: true
9+
values:
10+
containers:
11+
- name: container-0
12+
image: ${IMAGE}
13+
dev:
14+
autoReload:
15+
paths:
16+
- ./devspace.yaml
17+
replacePods:
18+
- imageSelector: ${IMAGE}
19+
replaceImage: ubuntu:18.04
20+
patches:
21+
- op: add
22+
path: spec.containers[0].command
23+
value: ["sleep"]
24+
- op: add
25+
path: spec.containers[0].args
26+
value: ["9999999999"]
27+
- op: add
28+
path: spec.containers[0].workingDir
29+
value: "/workdir"
30+
terminal:
31+
command: ["echo", "hello"]

0 commit comments

Comments
 (0)