Skip to content

Commit 3744b31

Browse files
committed
feat: implement dependency locking
1 parent b0a590f commit 3744b31

File tree

22 files changed

+356
-182
lines changed

22 files changed

+356
-182
lines changed

cmd/build.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func (cmd *BuildCmd) Run(f factory.Factory) error {
7575
}
7676

7777
func (cmd *BuildCmd) runCommand(ctx *devspacecontext.Context, f factory.Factory, configOptions *loader.ConfigOptions) error {
78-
return runPipeline(ctx, f, &PipelineOptions{
78+
return runPipeline(ctx, f, true, &PipelineOptions{
7979
Options: pipelinetypes.Options{
8080
BuildOptions: build.Options{
8181
Tags: cmd.Tags,

cmd/deploy.go

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
"github.com/loft-sh/devspace/pkg/devspace/pipeline"
2020
"github.com/loft-sh/devspace/pkg/devspace/pipeline/types"
2121
"github.com/loft-sh/devspace/pkg/devspace/plugin"
22-
"github.com/loft-sh/devspace/pkg/devspace/server"
2322
"github.com/loft-sh/devspace/pkg/devspace/upgrade"
2423
"github.com/loft-sh/devspace/pkg/util/factory"
2524
logpkg "github.com/loft-sh/devspace/pkg/util/log"
@@ -29,6 +28,8 @@ import (
2928
"github.com/spf13/cobra"
3029
"gopkg.in/yaml.v3"
3130
"k8s.io/client-go/kubernetes/fake"
31+
"os"
32+
"strings"
3233
)
3334

3435
// DeployCmd holds the required data for the down cmd
@@ -124,7 +125,7 @@ func (cmd *DeployCmd) Run(f factory.Factory) error {
124125
}
125126

126127
func (cmd *DeployCmd) runCommand(ctx *devspacecontext.Context, f factory.Factory, configOptions *loader.ConfigOptions) error {
127-
return runPipeline(ctx, f, &PipelineOptions{
128+
return runPipeline(ctx, f, true, &PipelineOptions{
128129
Options: types.Options{
129130
BuildOptions: build.Options{
130131
SkipBuild: cmd.SkipBuild,
@@ -223,7 +224,7 @@ type PipelineOptions struct {
223224
UIPort int
224225
}
225226

226-
func runPipeline(ctx *devspacecontext.Context, f factory.Factory, options *PipelineOptions) error {
227+
func runPipeline(ctx *devspacecontext.Context, f factory.Factory, forceLeader bool, options *PipelineOptions) error {
227228
// create namespace if necessary
228229
if !options.DeployOptions.Render {
229230
err := ctx.KubeClient.EnsureNamespace(ctx.Context, ctx.KubeClient.Namespace(), ctx.Log)
@@ -242,12 +243,6 @@ func runPipeline(ctx *devspacecontext.Context, f factory.Factory, options *Pipel
242243
}
243244
ctx = ctx.WithDependencies(dependencies)
244245

245-
// start ui & open
246-
serv, err := startServices(ctx, options.UIPort)
247-
if err != nil {
248-
return err
249-
}
250-
251246
// execute plugin hook
252247
err = hook.ExecuteHooks(ctx, nil, "deploy")
253248
if err != nil {
@@ -270,29 +265,36 @@ func runPipeline(ctx *devspacecontext.Context, f factory.Factory, options *Pipel
270265
}
271266
}
272267

273-
// create dependency registry
274-
dependencyRegistry := registry.NewDependencyRegistry("http://"+serv.Server.Addr, options.DeployOptions.Render)
275-
276-
// exclude ourselves
277-
couldExclude, err := dependencyRegistry.MarkDependencyExcluded(ctx, ctx.Config.Config().Name, true)
278-
if err != nil {
279-
return err
280-
} else if !couldExclude {
281-
return fmt.Errorf("couldn't start project %s, because there is another DevSpace instance active in the current namespace right now that uses the same project", ctx.Config.Config().Name)
268+
// marshal pipeline
269+
configPipelineBytes, err := yaml.Marshal(configPipeline)
270+
if err == nil {
271+
ctx.Log.Debugf("Run pipeline:\n%s\n", string(configPipelineBytes))
282272
}
283273

284274
// create a new base dev pod manager
285275
devPodManager := devpod.NewManager(ctx.Context)
286276
defer devPodManager.Close()
287277

288-
// marshal pipeline
289-
configPipelineBytes, err := yaml.Marshal(configPipeline)
290-
if err == nil {
291-
ctx.Log.Debugf("Run pipeline:\n%s\n", string(configPipelineBytes))
292-
}
278+
// create dependency registry
279+
dependencyRegistry := registry.NewDependencyRegistry(options.DeployOptions.Render)
293280

294281
// get deploy pipeline
295-
pipe := pipeline.NewPipeline(options.Pipeline, devPodManager, dependencyRegistry, configPipeline, options.Options)
282+
pipe := pipeline.NewPipeline(ctx.Config.Config().Name, devPodManager, dependencyRegistry, configPipeline, options.Options)
283+
284+
// start ui & open
285+
serv, err := dev.UI(ctx, options.UIPort, pipe)
286+
if err != nil {
287+
return err
288+
}
289+
dependencyRegistry.SetServer("http://" + serv.Server.Addr)
290+
291+
// exclude ourselves
292+
couldExclude, err := dependencyRegistry.MarkDependencyExcluded(ctx, ctx.Config.Config().Name, forceLeader)
293+
if err != nil {
294+
return err
295+
} else if !couldExclude {
296+
return fmt.Errorf("couldn't execute '%s', because there is another DevSpace instance active in the current namespace right now that uses the same project name (%s)", strings.Join(os.Args, " "), ctx.Config.Config().Name)
297+
}
296298

297299
// start pipeline
298300
err = pipe.Run(ctx.WithLogger(ctx.Log.WithoutPrefix()))
@@ -317,13 +319,3 @@ func runPipeline(ctx *devspacecontext.Context, f factory.Factory, options *Pipel
317319

318320
return nil
319321
}
320-
321-
func startServices(ctx *devspacecontext.Context, uiPort int) (*server.Server, error) {
322-
// Open UI if configured
323-
serv, err := dev.UI(ctx, uiPort)
324-
if err != nil {
325-
return nil, err
326-
}
327-
328-
return serv, nil
329-
}

cmd/dev.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func (cmd *DevCmd) Run(f factory.Factory, args []string) error {
168168
}
169169

170170
func (cmd *DevCmd) runCommand(ctx *devspacecontext.Context, f factory.Factory, configOptions *loader.ConfigOptions) error {
171-
return runPipeline(ctx, f, &PipelineOptions{
171+
return runPipeline(ctx, f, true, &PipelineOptions{
172172
Options: types.Options{
173173
BuildOptions: build.Options{
174174
SkipBuild: cmd.SkipBuild,

cmd/purge.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func (cmd *PurgeCmd) Run(f factory.Factory) error {
7676
}
7777

7878
func (cmd *PurgeCmd) runCommand(ctx *devspacecontext.Context, f factory.Factory, configOptions *loader.ConfigOptions) error {
79-
return runPipeline(ctx, f, &PipelineOptions{
79+
return runPipeline(ctx, f, false, &PipelineOptions{
8080
Options: types.Options{
8181
DependencyOptions: types.DependencyOptions{
8282
Exclude: cmd.SkipDependency,

cmd/render.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func (cmd *RenderCmd) Run(f factory.Factory) error {
9191
}
9292

9393
func (cmd *RenderCmd) runCommand(ctx *devspacecontext.Context, f factory.Factory, configOptions *loader.ConfigOptions) error {
94-
return runPipeline(ctx, f, &PipelineOptions{
94+
return runPipeline(ctx, f, true, &PipelineOptions{
9595
Options: pipelinetypes.Options{
9696
BuildOptions: build.Options{
9797
Tags: cmd.Tags,

cmd/ui.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ func (cmd *UICmd) RunUI(f factory.Factory) error {
189189
}
190190

191191
// Create server
192-
server, err := server.NewServer(config, nil, cmd.Host, cmd.Dev, client.CurrentContext(), client.Namespace(), forcePort, cmd.log)
192+
server, err := server.NewServer(ctx, cmd.Host, cmd.Dev, forcePort, nil)
193193
if err != nil {
194194
return err
195195
}

examples/pipelines/dep2.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
version: v2beta1
2+
name: pipelines2
3+
4+
pipelines:
5+
helper:
6+
steps:
7+
- run: |-
8+
echo "Hello World!"
9+
dev:
10+
steps:
11+
- run: |-
12+
run_pipelines helper
13+
14+
#build_images test --set image=${IMAGE}
15+
16+
#create_deployments test2
17+
18+
# Start development mode for test here
19+
#start_dev test --set imageSelector=${runtime.images.test} \
20+
# --set terminal.command=sh

examples/pipelines/devspace.yaml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
version: v2beta1
22
name: pipelines
33

4+
dependencies:
5+
dep2:
6+
source:
7+
path: dep2.yaml
8+
49
pipelines:
510
helper:
611
steps:
712
- run: |-
8-
echo "Hello World!"
13+
sleep 10
914
dev:
1015
steps:
1116
- run: |-
12-
export TEST=$(run_pipelines helper)
13-
echo $TEST > swag.txt
17+
sleep 30
18+
#run_dependency_pipelines --all --pipeline dev
19+
20+
#export TEST=$(run_pipelines helper)
21+
#echo $TEST > swag.txt
1422
1523
#build_images test --set image=${IMAGE}
1624

pkg/devspace/dependency/registry/inter_process.go

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package registry
22

3-
import "context"
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"net/http"
8+
)
49

510
type ExcludePayload struct {
611
// RunID is the run id of the process that owns the dependency
@@ -21,19 +26,55 @@ type InterProcess interface {
2126
Ping(ctx context.Context, server string, payload *PingPayload) (bool, error)
2227

2328
// ExcludeDependency tells the remote server to exclude a certain dependency
24-
ExcludeDependency(ctx context.Context, server string, excludePayload *ExcludePayload) error
29+
ExcludeDependency(ctx context.Context, server string, excludePayload *ExcludePayload) (bool, error)
2530
}
2631

2732
func NewInterProcessCommunicator() InterProcess {
28-
return &dummyImplementation{}
33+
return &requester{}
2934
}
3035

31-
type dummyImplementation struct{}
36+
type requester struct{}
3237

33-
func (d *dummyImplementation) Ping(ctx context.Context, server string, pingPayload *PingPayload) (bool, error) {
38+
func (d *requester) Ping(ctx context.Context, server string, pingPayload *PingPayload) (bool, error) {
39+
out, err := json.Marshal(pingPayload)
40+
if err != nil {
41+
return false, err
42+
}
43+
44+
req, err := http.NewRequestWithContext(ctx, "POST", server+"/api/ping", bytes.NewReader(out))
45+
if err != nil {
46+
return false, err
47+
}
48+
49+
response, err := http.DefaultClient.Do(req)
50+
if err != nil {
51+
return false, err
52+
}
53+
defer response.Body.Close()
54+
if response.StatusCode == http.StatusOK {
55+
return true, nil
56+
}
3457
return false, nil
3558
}
3659

37-
func (d *dummyImplementation) ExcludeDependency(ctx context.Context, server string, excludePayload *ExcludePayload) error {
38-
return nil
60+
func (d *requester) ExcludeDependency(ctx context.Context, server string, excludePayload *ExcludePayload) (bool, error) {
61+
out, err := json.Marshal(excludePayload)
62+
if err != nil {
63+
return false, err
64+
}
65+
66+
req, err := http.NewRequestWithContext(ctx, "POST", server+"/api/exclude-dependency", bytes.NewReader(out))
67+
if err != nil {
68+
return false, err
69+
}
70+
71+
response, err := http.DefaultClient.Do(req)
72+
if err != nil {
73+
return false, err
74+
}
75+
defer response.Body.Close()
76+
if response.StatusCode == http.StatusForbidden {
77+
return false, nil
78+
}
79+
return true, nil
3980
}

pkg/devspace/dependency/registry/registry.go

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ type DependencyRegistry interface {
3030

3131
// MarkDependenciesExcluded same as MarkDependencyExcluded but for multiple dependencies
3232
MarkDependenciesExcluded(ctx *devspacecontext.Context, dependencyNames []string, forceLeader bool) (map[string]bool, error)
33+
34+
// SetServer sets the target server
35+
SetServer(server string)
3336
}
3437

35-
func NewDependencyRegistry(server string, mock bool) DependencyRegistry {
38+
func NewDependencyRegistry(mock bool) DependencyRegistry {
3639
return &dependencyRegistry{
37-
server: server,
3840
mock: mock,
39-
interProcess: &dummyImplementation{},
41+
interProcess: NewInterProcessCommunicator(),
4042
excludedDependencies: map[string]bool{},
4143
}
4244
}
@@ -50,6 +52,13 @@ type dependencyRegistry struct {
5052
excludedDependencies map[string]bool
5153
}
5254

55+
func (d *dependencyRegistry) SetServer(server string) {
56+
d.excludedDependenciesLock.Lock()
57+
defer d.excludedDependenciesLock.Unlock()
58+
59+
d.server = server
60+
}
61+
5362
func (d *dependencyRegistry) ForceExclude(dependencyName string) {
5463
d.excludedDependenciesLock.Lock()
5564
defer d.excludedDependenciesLock.Unlock()
@@ -212,18 +221,22 @@ func (d *dependencyRegistry) excludeDependencies(ctx *devspacecontext.Context, d
212221

213222
// check if we should take over
214223
if forceLeader {
215-
err = d.interProcess.ExcludeDependency(ctx.Context, payload.Server, &ExcludePayload{
224+
excludeCtx, excludeCancel := context.WithTimeout(ctx.Context, time.Second*10)
225+
allowed, err := d.interProcess.ExcludeDependency(excludeCtx, payload.Server, &ExcludePayload{
216226
RunID: payload.RunID,
217227
DependencyName: dependencyName,
218228
})
219-
if err != nil {
220-
ctx.Log.Debugf("error taking over dependency: %v", err)
221-
}
229+
excludeCancel()
230+
if err != nil || allowed {
231+
if err != nil {
232+
ctx.Log.Debugf("error taking over dependency: %v", err)
233+
}
222234

223-
configMap.Data[dependencyName] = string(encoded)
224-
retMap[dependencyName] = true
225-
shouldUpdate = true
226-
continue
235+
configMap.Data[dependencyName] = string(encoded)
236+
retMap[dependencyName] = true
237+
shouldUpdate = true
238+
continue
239+
}
227240
}
228241
}
229242

0 commit comments

Comments
 (0)