Skip to content

Commit c2822d7

Browse files
committed
feat: exec_container & run_default_pipeline
1 parent 7ff905b commit c2822d7

File tree

11 files changed

+215
-26
lines changed

11 files changed

+215
-26
lines changed

cmd/deploy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ func runPipeline(ctx *devspacecontext.Context, f factory.Factory, forceLeader bo
271271
if ctx.Config.Config().Pipelines != nil && ctx.Config.Config().Pipelines[options.Pipeline] != nil {
272272
configPipeline = ctx.Config.Config().Pipelines[options.Pipeline]
273273
} else {
274-
configPipeline, err = pipeline.GetDefaultPipeline(options.Pipeline)
274+
configPipeline, err = types.GetDefaultPipeline(options.Pipeline)
275275
if err != nil {
276276
return err
277277
}

e2e/e2e_suite_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
_ "github.com/loft-sh/devspace/e2e/tests/hooks"
1919
_ "github.com/loft-sh/devspace/e2e/tests/imports"
2020
_ "github.com/loft-sh/devspace/e2e/tests/init"
21+
_ "github.com/loft-sh/devspace/e2e/tests/pipelines"
2122
_ "github.com/loft-sh/devspace/e2e/tests/pullsecret"
2223
_ "github.com/loft-sh/devspace/e2e/tests/render"
2324
_ "github.com/loft-sh/devspace/e2e/tests/replacepods"

e2e/tests/pipelines/framework.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package pipelines
2+
3+
import "github.com/onsi/ginkgo"
4+
5+
// DevSpaceDescribe annotates the test with the label.
6+
func DevSpaceDescribe(text string, body func()) bool {
7+
return ginkgo.Describe("[pipelines] "+text, body)
8+
}

e2e/tests/pipelines/pipelines.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package pipelines
2+
3+
import (
4+
"github.com/loft-sh/devspace/cmd"
5+
"github.com/loft-sh/devspace/cmd/flags"
6+
"github.com/loft-sh/devspace/e2e/framework"
7+
"github.com/loft-sh/devspace/e2e/kube"
8+
"github.com/loft-sh/devspace/pkg/util/factory"
9+
"github.com/onsi/ginkgo"
10+
"os"
11+
)
12+
13+
var _ = DevSpaceDescribe("portforward", func() {
14+
initialDir, err := os.Getwd()
15+
if err != nil {
16+
panic(err)
17+
}
18+
19+
// create a new factory
20+
var (
21+
f factory.Factory
22+
kubeClient *kube.KubeHelper
23+
)
24+
25+
ginkgo.BeforeEach(func() {
26+
f = framework.NewDefaultFactory()
27+
28+
kubeClient, err = kube.NewKubeHelper()
29+
framework.ExpectNoError(err)
30+
})
31+
32+
ginkgo.It("should exec container", func() {
33+
tempDir, err := framework.CopyToTempDir("tests/pipelines/testdata/exec_container")
34+
framework.ExpectNoError(err)
35+
defer framework.CleanupTempDir(initialDir, tempDir)
36+
37+
ns, err := kubeClient.CreateNamespace("pipelines")
38+
framework.ExpectNoError(err)
39+
defer framework.ExpectDeleteNamespace(kubeClient, ns)
40+
41+
devCmd := &cmd.DevCmd{
42+
GlobalFlags: &flags.GlobalFlags{
43+
NoWarn: true,
44+
Namespace: ns,
45+
},
46+
}
47+
err = devCmd.Run(f)
48+
framework.ExpectNoError(err)
49+
framework.ExpectLocalFileContentsImmediately("test.txt", "Hello World!\n")
50+
})
51+
})
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
version: v2beta1
2+
name: exec
3+
4+
deployments:
5+
test:
6+
helm:
7+
values:
8+
containers:
9+
- image: alpine
10+
command: ["sleep", "99999999"]
11+
12+
pipelines:
13+
dev:
14+
steps:
15+
- run: |-
16+
run_default_pipeline deploy
17+
18+
echo "Hello World!" | exec_container --image-selector alpine --silent -- cat - > test.txt
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package commands
2+
3+
import (
4+
"fmt"
5+
"github.com/jessevdk/go-flags"
6+
devspacecontext "github.com/loft-sh/devspace/pkg/devspace/context"
7+
"github.com/loft-sh/devspace/pkg/devspace/kubectl"
8+
"github.com/loft-sh/devspace/pkg/devspace/services/targetselector"
9+
"github.com/loft-sh/devspace/pkg/util/log"
10+
"github.com/pkg/errors"
11+
"mvdan.cc/sh/v3/interp"
12+
"strings"
13+
"time"
14+
)
15+
16+
type ExecContainerOptions struct {
17+
ImageSelector string `long:"image-selector" description:"The image selector to use to select the container"`
18+
LabelSelector string `long:"label-selector" description:"The label selector to use to select the container"`
19+
Container string `long:"container" description:"The container to use"`
20+
21+
Namespace string `long:"namespace" short:"n" description:"The namespace to use"`
22+
DisableWait bool `long:"disable-wait" description:"If true, will not wait for the container to become ready"`
23+
Timeout int64 `long:"timeout" description:"The timeout to wait. Defaults to 5 minutes"`
24+
Silent bool `long:"silent" short:"s" description:"If true will not output any other log messages"`
25+
}
26+
27+
func ExecContainer(ctx *devspacecontext.Context, args []string) error {
28+
hc := interp.HandlerCtx(ctx.Context)
29+
30+
ctx.Log.Debugf("exec_container %s", strings.Join(args, " "))
31+
options := &ExecContainerOptions{
32+
Namespace: ctx.KubeClient.Namespace(),
33+
}
34+
args, err := flags.ParseArgs(options, args)
35+
if err != nil {
36+
return errors.Wrap(err, "parse args")
37+
}
38+
if len(args) == 0 {
39+
return fmt.Errorf("usage: exec_container [--image-selector|--label-selector] COMMAND")
40+
}
41+
if options.ImageSelector == "" && options.LabelSelector == "" {
42+
return fmt.Errorf("usage: exec_container [--image-selector|--label-selector] COMMAND")
43+
}
44+
45+
logger := ctx.Log
46+
if options.Silent {
47+
logger = log.Discard
48+
}
49+
50+
selectorOptions := targetselector.NewOptionsFromFlags(options.Container, options.LabelSelector, []string{options.ImageSelector}, options.Namespace, "")
51+
if options.Timeout != 0 {
52+
selectorOptions = selectorOptions.WithTimeout(options.Timeout)
53+
}
54+
selectorOptions.WithWaitingStrategy(targetselector.NewUntilNewestRunningWaitingStrategy(time.Millisecond * 500))
55+
selectedContainer, err := targetselector.NewTargetSelector(selectorOptions).SelectSingleContainer(ctx.Context, ctx.KubeClient, logger)
56+
if err != nil {
57+
return err
58+
}
59+
60+
return ctx.KubeClient.ExecStream(ctx.Context, &kubectl.ExecStreamOptions{
61+
Pod: selectedContainer.Pod,
62+
Container: selectedContainer.Container.Name,
63+
Command: args,
64+
Stdin: hc.Stdin,
65+
Stdout: hc.Stdout,
66+
Stderr: hc.Stderr,
67+
SubResource: kubectl.SubResourceExec,
68+
})
69+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package commands
2+
3+
import (
4+
"fmt"
5+
devspacecontext "github.com/loft-sh/devspace/pkg/devspace/context"
6+
"github.com/loft-sh/devspace/pkg/devspace/pipeline/engine"
7+
enginetypes "github.com/loft-sh/devspace/pkg/devspace/pipeline/engine/types"
8+
"github.com/loft-sh/devspace/pkg/devspace/pipeline/types"
9+
"io"
10+
"mvdan.cc/sh/v3/interp"
11+
)
12+
13+
type NewHandlerFn func(ctx *devspacecontext.Context, stdout io.Writer, pipeline types.Pipeline) enginetypes.ExecHandler
14+
15+
func RunCommand(ctx *devspacecontext.Context, pipeline types.Pipeline, args []string, newHandler NewHandlerFn) error {
16+
hc := interp.HandlerCtx(ctx.Context)
17+
if len(args) == 0 {
18+
return fmt.Errorf("please specify a command to run")
19+
}
20+
21+
// try to find command
22+
for _, command := range ctx.Config.Config().Commands {
23+
if command.Name == args[0] {
24+
if len(command.Args) > 0 {
25+
return fmt.Errorf("calling commands that use args is not supported currently")
26+
}
27+
28+
_, err := engine.ExecutePipelineShellCommand(ctx.Context, command.Command, args[1:], hc.Dir, false, hc.Stdout, hc.Stderr, hc.Stdin, hc.Env, newHandler(ctx, hc.Stdout, pipeline))
29+
return err
30+
}
31+
}
32+
33+
return fmt.Errorf("couldn't find command %v", args[0])
34+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package commands
2+
3+
import (
4+
"fmt"
5+
devspacecontext "github.com/loft-sh/devspace/pkg/devspace/context"
6+
"github.com/loft-sh/devspace/pkg/devspace/pipeline/engine"
7+
"github.com/loft-sh/devspace/pkg/devspace/pipeline/types"
8+
"mvdan.cc/sh/v3/interp"
9+
)
10+
11+
func RunDefaultPipeline(ctx *devspacecontext.Context, pipeline types.Pipeline, args []string, newHandler NewHandlerFn) error {
12+
hc := interp.HandlerCtx(ctx.Context)
13+
if len(args) != 1 {
14+
return fmt.Errorf("usage: run_default_pipeline [pipeline]")
15+
}
16+
17+
defaultPipeline, err := types.GetDefaultPipeline(args[0])
18+
if err != nil {
19+
return err
20+
}
21+
22+
_, err = engine.ExecutePipelineShellCommand(ctx.Context, defaultPipeline.Steps[0].Run, nil, hc.Dir, false, hc.Stdout, hc.Stderr, hc.Stdin, hc.Env, newHandler(ctx, hc.Stdout, pipeline))
23+
return err
24+
}

pkg/devspace/pipeline/engine/pipelinehandler/handler.go

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"io"
88

99
devspacecontext "github.com/loft-sh/devspace/pkg/devspace/context"
10-
"github.com/loft-sh/devspace/pkg/devspace/pipeline/engine"
1110
"github.com/loft-sh/devspace/pkg/devspace/pipeline/engine/basichandler"
1211
"github.com/loft-sh/devspace/pkg/devspace/pipeline/engine/pipelinehandler/commands"
1312
enginetypes "github.com/loft-sh/devspace/pkg/devspace/pipeline/engine/types"
@@ -19,11 +18,17 @@ import (
1918

2019
// PipelineCommands are commands that can only be run within a pipeline and have special functionality in there
2120
var PipelineCommands = map[string]func(devCtx *devspacecontext.Context, pipeline types.Pipeline, args []string) error{
21+
"exec_container": func(devCtx *devspacecontext.Context, pipeline types.Pipeline, args []string) error {
22+
return commands.ExecContainer(devCtx, args)
23+
},
2224
"get_image": func(devCtx *devspacecontext.Context, pipeline types.Pipeline, args []string) error {
2325
return commands.GetImage(devCtx, args)
2426
},
2527
"run_command": func(devCtx *devspacecontext.Context, pipeline types.Pipeline, args []string) error {
26-
return runCommand(devCtx, pipeline, args)
28+
return commands.RunCommand(devCtx, pipeline, args, NewPipelineExecHandler)
29+
},
30+
"run_default_pipeline": func(devCtx *devspacecontext.Context, pipeline types.Pipeline, args []string) error {
31+
return commands.RunDefaultPipeline(devCtx, pipeline, args, NewPipelineExecHandler)
2732
},
2833
"run_pipelines": func(devCtx *devspacecontext.Context, pipeline types.Pipeline, args []string) error {
2934
return commands.RunPipelines(devCtx, pipeline, args)
@@ -128,27 +133,6 @@ func (e *execHandler) executePipelineCommand(ctx context.Context, command string
128133
return true, handleError(ctx, command, commandFn())
129134
}
130135

131-
func runCommand(ctx *devspacecontext.Context, pipeline types.Pipeline, args []string) error {
132-
hc := interp.HandlerCtx(ctx.Context)
133-
if len(args) == 0 {
134-
return fmt.Errorf("please specify a command to run")
135-
}
136-
137-
// try to find command
138-
for _, command := range ctx.Config.Config().Commands {
139-
if command.Name == args[0] {
140-
if len(command.Args) > 0 {
141-
return fmt.Errorf("calling commands that use args is not supported currently")
142-
}
143-
144-
_, err := engine.ExecutePipelineShellCommand(ctx.Context, command.Command, args[1:], ctx.WorkingDir, false, hc.Stdout, hc.Stderr, hc.Stdin, hc.Env, NewPipelineExecHandler(ctx, hc.Stdout, pipeline))
145-
return err
146-
}
147-
}
148-
149-
return fmt.Errorf("couldn't find command %v", args[0])
150-
}
151-
152136
func handleError(ctx context.Context, command string, err error) error {
153137
if err == nil {
154138
return interp.NewExitStatus(0)

pkg/devspace/pipeline/pipeline.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ func (p *pipeline) startNewDependency(ctx *devspacecontext.Context, dependency t
187187
err error
188188
)
189189
if dependency.Config().Config().Pipelines == nil || dependency.Config().Config().Pipelines[executePipeline] == nil {
190-
pipelineConfig, err = GetDefaultPipeline(executePipeline)
190+
pipelineConfig, err = types.GetDefaultPipeline(executePipeline)
191191
if err != nil {
192192
return err
193193
}

0 commit comments

Comments
 (0)