Skip to content

Commit d8f39df

Browse files
committed
refactor: use deployment instead of replica set as replacement
1 parent d754ea6 commit d8f39df

File tree

17 files changed

+383
-197
lines changed

17 files changed

+383
-197
lines changed

pkg/devspace/config/loader/validate.go

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -327,36 +327,6 @@ func validateImages(config *latest.Config) error {
327327
return nil
328328
}
329329

330-
func isDevPodUnique(index string, rp *latest.DevPod, rps map[string]*latest.DevPod) bool {
331-
for i, r := range rps {
332-
if i == index {
333-
continue
334-
}
335-
336-
if r.ImageSelector != "" && r.ImageSelector == rp.ImageSelector {
337-
return false
338-
} else if len(r.LabelSelector) > 0 && len(rp.LabelSelector) > 0 && strMapEquals(r.LabelSelector, rp.LabelSelector) {
339-
return false
340-
}
341-
}
342-
343-
return true
344-
}
345-
346-
func strMapEquals(a, b map[string]string) bool {
347-
if len(a) != len(b) {
348-
return false
349-
}
350-
351-
for k, v := range a {
352-
if w, ok := b[k]; !ok || v != w {
353-
return false
354-
}
355-
}
356-
357-
return true
358-
}
359-
360330
func validateDev(config *latest.Config) error {
361331
for devPodName, devPod := range config.Dev {
362332
if devPodName == "" {
@@ -365,22 +335,22 @@ func validateDev(config *latest.Config) error {
365335
if encoding.IsUnsafeName(devPodName) {
366336
return fmt.Errorf("dev[%s] has to match the following regex: %v", devPodName, encoding.UnsafeNameRegEx.String())
367337
}
368-
if len(devPod.LabelSelector) == 0 && devPod.ImageSelector == "" {
369-
return errors.Errorf("dev[%s]: image selector and label selector are nil", devPodName)
338+
if len(devPod.LabelSelector) == 0 && devPod.ImageSelector == "" && devPod.Pod == "" {
339+
return errors.Errorf("dev[%s]: image selector, pod and label selector are nil", devPodName)
370340
}
371341

372342
definedSelectors := 0
343+
if devPod.Pod != "" {
344+
definedSelectors++
345+
}
373346
if devPod.ImageSelector != "" {
374347
definedSelectors++
375348
}
376349
if len(devPod.LabelSelector) > 0 {
377350
definedSelectors++
378351
}
379352
if definedSelectors > 1 {
380-
return errors.Errorf("dev[%s]: image selector and label selector cannot both be defined", devPodName)
381-
}
382-
if !isDevPodUnique(devPodName, devPod, config.Dev) {
383-
return errors.Errorf("dev[%s]: image selector or label selector is not unique", devPodName)
353+
return errors.Errorf("dev[%s]: image selector, pod and label selector cannot all be defined", devPodName)
384354
}
385355

386356
err := validateDevContainer(fmt.Sprintf("dev[%s]", devPodName), &devPod.DevContainer, false)

pkg/devspace/config/remotecache/schema.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ type DevPodCache struct {
6868
// Namespace is the namespace where the replace happened
6969
Namespace string `yaml:"namespace,omitempty"`
7070

71-
// ReplicaSet is the replica set that was created by DevSpace
72-
ReplicaSet string `yaml:"replicaSet,omitempty"`
71+
// Deployment is the deployment that was created by DevSpace
72+
Deployment string `yaml:"deployment,omitempty"`
7373

7474
// TargetKind is the kind of the original parent
7575
TargetKind string `yaml:"parentKind,omitempty"`

pkg/devspace/config/versions/latest/schema.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,7 @@ type KubectlConfig struct {
750750

751751
type DevPod struct {
752752
Name string `yaml:"name,omitempty" json:"name,omitempty"`
753+
Pod string `yaml:"pod,omitempty" json:"pod,omitempty"`
753754
ImageSelector string `yaml:"imageSelector,omitempty" json:"imageSelector,omitempty"`
754755
LabelSelector map[string]string `yaml:"labelSelector,omitempty" json:"labelSelector,omitempty"`
755756
Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"`
@@ -770,14 +771,24 @@ type DevContainer struct {
770771
// Target Container architecture to use for the devspacehelper (currently amd64 or arm64). Defaults to amd64
771772
Arch ContainerArchitecture `yaml:"arch,omitempty" json:"arch,omitempty"`
772773

773-
PortMappingsReverse []*PortMapping `yaml:"reverseForward,omitempty" json:"reverseForward,omitempty"`
774-
Container string `yaml:"container,omitempty" json:"container,omitempty"`
775-
Terminal *Terminal `yaml:"terminal,omitempty" json:"terminal,omitempty"`
776-
Logs *Logs `yaml:"logs,omitempty" json:"logs,omitempty"`
777-
Attach *Attach `yaml:"attach,omitempty" json:"attach,omitempty"`
778-
ReplaceImage string `yaml:"replaceImage,omitempty" json:"replaceImage,omitempty"`
779-
PersistPaths []PersistentPath `yaml:"persistPaths,omitempty" json:"persistPaths,omitempty"`
780-
Sync []*SyncConfig `yaml:"sync,omitempty" json:"sync,omitempty" patchStrategy:"merge" patchMergeKey:"localSubPath"`
774+
PortMappingsReverse []*PortMapping `yaml:"reverseForward,omitempty" json:"reverseForward,omitempty"`
775+
Container string `yaml:"container,omitempty" json:"container,omitempty"`
776+
Command []string `yaml:"command,omitempty" json:"command,omitempty"`
777+
Args []string `yaml:"args,omitempty" json:"args,omitempty"`
778+
Env []EnvVar `yaml:"env,omitempty" json:"env,omitempty"`
779+
RestartHelperPath string `yaml:"restartHelperPath,omitempty" json:"restartHelperPath,omitempty"`
780+
DisableRestartHelper bool `yaml:"disableRestartHelper,omitempty" json:"disableRestartHelper,omitempty"`
781+
Terminal *Terminal `yaml:"terminal,omitempty" json:"terminal,omitempty"`
782+
Logs *Logs `yaml:"logs,omitempty" json:"logs,omitempty"`
783+
Attach *Attach `yaml:"attach,omitempty" json:"attach,omitempty"`
784+
ReplaceImage string `yaml:"replaceImage,omitempty" json:"replaceImage,omitempty"`
785+
PersistPaths []PersistentPath `yaml:"persistPaths,omitempty" json:"persistPaths,omitempty"`
786+
Sync []*SyncConfig `yaml:"sync,omitempty" json:"sync,omitempty" patchStrategy:"merge" patchMergeKey:"localSubPath"`
787+
}
788+
789+
type EnvVar struct {
790+
Name string `yaml:"name" json:"name"`
791+
Value string `yaml:"value" json:"value"`
781792
}
782793

783794
type Attach struct {

pkg/devspace/config/versions/v1beta11/upgrade.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,13 @@ func (c *Config) mergeDevConfig(log log.Logger) (map[string]*next.DevPod, error)
515515
}
516516
}
517517

518+
// disable sync replace
519+
for k := range devPods {
520+
for i := range devPods[k].Containers {
521+
devPods[k].Containers[i].DisableRestartHelper = true
522+
}
523+
}
524+
518525
// flatten dev containers
519526
for k := range devPods {
520527
if len(devPods[k].Containers) == 1 {

pkg/devspace/config/versions/versions.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ func Parse(data map[string]interface{}, log log.Logger) (*latest.Config, error)
165165
decoder.KnownFields(true)
166166
err = decoder.Decode(latestConfig)
167167
if err != nil {
168-
return nil, errors.Errorf("Error loading config: %v", err)
168+
return nil, errors.Errorf("error loading config: %v", err)
169169
}
170170

171171
// Upgrade config to latest

pkg/devspace/devpod/devpod.go

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ package devpod
22

33
import (
44
"context"
5+
"fmt"
56
"github.com/loft-sh/devspace/pkg/devspace/config/loader"
67
"github.com/loft-sh/devspace/pkg/devspace/kubectl/selector"
78
"github.com/loft-sh/devspace/pkg/devspace/services/logs"
89
"github.com/loft-sh/devspace/pkg/devspace/services/terminal"
9-
"github.com/loft-sh/devspace/pkg/util/log"
1010
"github.com/loft-sh/devspace/pkg/util/tomb"
11-
"github.com/sirupsen/logrus"
1211
"github.com/skratchdot/open-golang/open"
1312
"net/http"
1413
"os"
@@ -28,6 +27,9 @@ import (
2827

2928
var (
3029
openMaxWait = 5 * time.Minute
30+
31+
terminalDevPodMutex syncpkg.Mutex
32+
terminalDevPod *devPod
3133
)
3234

3335
type devPod struct {
@@ -122,46 +124,29 @@ func (d *devPod) startWithRetry(ctx *devspacecontext.Context, devPodConfig *late
122124
// Create a new tomb and run it
123125
tombCtx := t.Context(ctx.Context)
124126
ctx = ctx.WithContext(tombCtx)
125-
var (
126-
hasTerminal bool
127-
err error
128-
)
129127
<-t.NotifyGo(func() error {
130-
hasTerminal, err = d.start(ctx, devPodConfig, options, t)
131-
return err
128+
return d.start(ctx, devPodConfig, options, t)
132129
})
133-
if hasTerminal {
134-
err = t.Wait()
135-
if err != nil {
136-
// if we just lost connection we wait here until stopped
137-
if _, ok := t.Err().(DevPodLostConnection); ok {
138-
<-d.done
139-
return nil
140-
}
141-
142-
return err
143-
}
144-
return nil
145-
} else if !t.Alive() {
130+
if !t.Alive() {
146131
return t.Err()
147132
}
148133

149134
return nil
150135
}
151136

152-
func (d *devPod) start(ctx *devspacecontext.Context, devPodConfig *latest.DevPod, opts Options, parent *tomb.Tomb) (hasTerminal bool, err error) {
137+
func (d *devPod) start(ctx *devspacecontext.Context, devPodConfig *latest.DevPod, opts Options, parent *tomb.Tomb) error {
153138
// check first if we need to replace the pod
154139
if !opts.DisablePodReplace && needPodReplace(devPodConfig) {
155140
err := podreplace.NewPodReplacer().ReplacePod(ctx, devPodConfig)
156141
if err != nil {
157-
return false, errors.Wrap(err, "replace pod")
142+
return errors.Wrap(err, "replace pod")
158143
}
159144
} else {
160145
devPodCache, ok := ctx.Config.RemoteCache().GetDevPod(devPodConfig.Name)
161-
if ok && devPodCache.ReplicaSet != "" {
146+
if ok && devPodCache.Deployment != "" {
162147
_, err := podreplace.NewPodReplacer().RevertReplacePod(ctx, &devPodCache)
163148
if err != nil {
164-
return false, errors.Wrap(err, "replace pod")
149+
return errors.Wrap(err, "replace pod")
165150
}
166151
}
167152
}
@@ -170,7 +155,7 @@ func (d *devPod) start(ctx *devspacecontext.Context, devPodConfig *latest.DevPod
170155
if devPodConfig.ImageSelector != "" {
171156
imageSelectorObject, err := runtimevar.NewRuntimeResolver(ctx.WorkingDir, true).FillRuntimeVariablesAsImageSelector(ctx.Context, devPodConfig.ImageSelector, ctx.Config, ctx.Dependencies)
172157
if err != nil {
173-
return false, err
158+
return err
174159
}
175160

176161
imageSelector = []string{imageSelectorObject.Image}
@@ -179,12 +164,13 @@ func (d *devPod) start(ctx *devspacecontext.Context, devPodConfig *latest.DevPod
179164
// wait for pod to be ready
180165
ctx.Log.Infof("Waiting for pod to become ready...")
181166
options := targetselector.NewEmptyOptions().
182-
ApplyConfigParameter("", devPodConfig.LabelSelector, imageSelector, devPodConfig.Namespace, "").
167+
ApplyConfigParameter("", devPodConfig.LabelSelector, imageSelector, devPodConfig.Namespace, devPodConfig.Pod).
183168
WithWaitingStrategy(targetselector.NewUntilNewestRunningWaitingStrategy(time.Millisecond * 500)).
184169
WithSkipInitContainers(true)
170+
var err error
185171
d.selectedPod, err = targetselector.NewTargetSelector(options).SelectSingleContainer(ctx.Context, ctx.KubeClient, ctx.Log)
186172
if err != nil {
187-
return false, errors.Wrap(err, "waiting for pod to become ready")
173+
return errors.Wrap(err, "waiting for pod to become ready")
188174
}
189175
ctx.Log.Debugf("Selected pod:container %s:%s", d.selectedPod.Pod.Name, d.selectedPod.Container.Name)
190176

@@ -217,17 +203,17 @@ func (d *devPod) start(ctx *devspacecontext.Context, devPodConfig *latest.DevPod
217203
// start sync and port forwarding
218204
err = d.startSyncAndPortForwarding(ctx, devPodConfig, newTargetSelector(d.selectedPod.Pod.Name, d.selectedPod.Pod.Namespace, d.selectedPod.Container.Name, parent), opts, parent)
219205
if err != nil {
220-
return false, err
206+
return err
221207
}
222208

223209
// start logs or terminal
224210
terminalDevContainer := d.getTerminalDevContainer(devPodConfig)
225211
if terminalDevContainer != nil {
226-
return true, d.startTerminal(ctx, terminalDevContainer, parent)
212+
return d.startTerminal(ctx, terminalDevContainer, opts, parent)
227213
}
228214

229215
// TODO attach
230-
return false, d.startLogs(ctx, devPodConfig, parent)
216+
return d.startLogs(ctx, devPodConfig, parent)
231217
}
232218

233219
func (d *devPod) startLogs(ctx *devspacecontext.Context, devPodConfig *latest.DevPod, parent *tomb.Tomb) error {
@@ -262,19 +248,28 @@ func (d *devPod) getTerminalDevContainer(devPodConfig *latest.DevPod) *latest.De
262248
return devContainer
263249
}
264250

265-
func (d *devPod) startTerminal(ctx *devspacecontext.Context, devContainer *latest.DevContainer, parent *tomb.Tomb) error {
251+
func (d *devPod) startTerminal(ctx *devspacecontext.Context, devContainer *latest.DevContainer, opts Options, parent *tomb.Tomb) error {
266252
parent.Go(func() error {
253+
err := setTerminalDevPod(d)
254+
if err != nil {
255+
return err
256+
}
257+
267258
// make sure the global log is silent
268-
before := log.GetBaseInstance().GetLevel()
269-
log.GetBaseInstance().SetLevel(logrus.PanicLevel)
270-
err := terminal.StartTerminal(ctx, devContainer, newTargetSelector(d.selectedPod.Pod.Name, d.selectedPod.Pod.Namespace, d.selectedPod.Container.Name, parent), os.Stdout, os.Stderr, os.Stdin, parent)
271-
log.GetBaseInstance().SetLevel(before)
259+
err = terminal.StartTerminal(ctx, devContainer, newTargetSelector(d.selectedPod.Pod.Name, d.selectedPod.Pod.Namespace, d.selectedPod.Container.Name, parent), os.Stdout, os.Stderr, os.Stdin, parent)
260+
terminalDevPodMutex.Lock()
261+
terminalDevPod = nil
262+
terminalDevPodMutex.Unlock()
272263
if err != nil {
273264
return errors.Wrap(err, "error in terminal forwarding")
274265
}
275266

276267
// kill ourselves here
277-
parent.Kill(nil)
268+
if !opts.ContinueOnTerminalExit && opts.KillApplication != nil {
269+
go opts.KillApplication()
270+
} else {
271+
parent.Kill(nil)
272+
}
278273
return nil
279274
})
280275

@@ -345,6 +340,34 @@ func needPodReplaceContainer(devContainer *latest.DevContainer) bool {
345340
if devContainer.Terminal != nil && !devContainer.Terminal.Disabled && !devContainer.Terminal.DisableReplace {
346341
return true
347342
}
343+
if len(devContainer.Env) > 0 {
344+
return true
345+
}
346+
if len(devContainer.Command) > 0 {
347+
return true
348+
}
349+
if devContainer.Args != nil {
350+
return true
351+
}
352+
if !devContainer.DisableRestartHelper {
353+
for _, s := range devContainer.Sync {
354+
if s.OnUpload != nil && s.OnUpload.RestartContainer {
355+
return true
356+
}
357+
}
358+
}
348359

349360
return false
350361
}
362+
363+
func setTerminalDevPod(devPod *devPod) error {
364+
terminalDevPodMutex.Lock()
365+
defer terminalDevPodMutex.Unlock()
366+
367+
if terminalDevPod != nil {
368+
return fmt.Errorf("error starting terminal as it is currently already used by another dev pod")
369+
}
370+
371+
terminalDevPod = devPod
372+
return nil
373+
}

pkg/devspace/devpod/manager.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,14 @@ import (
1313
)
1414

1515
type Options struct {
16+
ContinueOnTerminalExit bool `long:"continue-on-terminal-exit" description:"Continue on terminal exit"`
17+
1618
DisableSync bool `long:"disable-sync" description:"If enabled will not start any sync configuration"`
1719
DisablePortForwarding bool `long:"disable-port-forwarding" description:"If enabled will not start any port forwarding configuration"`
1820
DisablePodReplace bool `long:"disable-pod-replace" description:"If enabled will not replace any pods"`
21+
22+
// KillApplication kills the whole pipeline including all dev pods
23+
KillApplication func()
1924
}
2025

2126
type Manager interface {

pkg/devspace/helm/generic/generic.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"github.com/loft-sh/devspace/pkg/util/command"
88
"io/ioutil"
99
"os"
10-
"os/exec"
1110
"path/filepath"
1211
"strings"
1312

@@ -89,11 +88,7 @@ func (c *client) Exec(ctx *devspacecontext.Context, args []string, helmConfig *l
8988
}
9089
result, err := command.Output(ctx.Context, ctx.WorkingDir, c.helmPath, args...)
9190
if err != nil {
92-
if exitError, ok := err.(*exec.ExitError); ok {
93-
return nil, fmt.Errorf("error during '%s %s':\n %s%s => %v", c.helmPath, strings.Join(args, " "), string(result), string(exitError.Stderr), err)
94-
}
95-
96-
return nil, fmt.Errorf("error during '%s %s':\n %s => %v %s", c.helmPath, strings.Join(args, " "), string(result), err)
91+
return nil, fmt.Errorf("%s %v", string(result), err)
9792
}
9893

9994
return result, nil

pkg/devspace/pipeline/engine/pipelinehandler/commands/deploy.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,13 @@ func applySetValues(ctx *devspacecontext.Context, name, objName string, set, set
7676
return nil, err
7777
}
7878

79+
if rawConfig[name] == nil {
80+
rawConfig[name] = map[string]interface{}{}
81+
}
7982
_, ok := rawConfig[name].(map[string]interface{})
8083
if !ok {
8184
return ctx, nil
8285
}
83-
84-
if rawConfig[name] == nil {
85-
rawConfig[name] = map[string]interface{}{}
86-
}
8786
if rawConfig[name].(map[string]interface{})[objName] == nil {
8887
rawConfig[name].(map[string]interface{})[objName] = map[string]interface{}{}
8988
}

0 commit comments

Comments
 (0)