Skip to content

Commit fcd9791

Browse files
committed
refactor: improve error visibility on devspace dev
1 parent f29b28c commit fcd9791

File tree

14 files changed

+179
-73
lines changed

14 files changed

+179
-73
lines changed

cmd/dev.go

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cmd
22

33
import (
44
"fmt"
5+
"github.com/loft-sh/devspace/pkg/util/stringutil"
56
"io"
67
"os"
78
"os/exec"
@@ -758,7 +759,7 @@ func GetPaths(config *latest.Config) []string {
758759
}
759760
}
760761

761-
return removeDuplicates(paths)
762+
return stringutil.RemoveDuplicates(paths)
762763
}
763764

764765
func (cmd *DevCmd) loadConfig(configOptions *loader.ConfigOptions) (config.Config, error) {
@@ -823,25 +824,6 @@ func defaultStdStreams(stdout io.Writer, stderr io.Writer, stdin io.Reader) (io.
823824
return stdout, stderr, stdin
824825
}
825826

826-
func removeDuplicates(arr []string) []string {
827-
newArr := []string{}
828-
for _, v := range arr {
829-
if !contains(newArr, v) {
830-
newArr = append(newArr, v)
831-
}
832-
}
833-
return newArr
834-
}
835-
836-
func contains(haystack []string, needle string) bool {
837-
for _, v := range haystack {
838-
if v == needle {
839-
return true
840-
}
841-
}
842-
return false
843-
}
844-
845827
func updateLastKubeContext(configLoader loader.ConfigLoader, client kubectl.Client, generatedConfig *generated.Config) error {
846828
// Update generated if we deploy the application
847829
if generatedConfig != nil {

pkg/devspace/hook/hook.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,16 +143,16 @@ func executeSingle(client kubectl.Client, config config.Config, dependencies []t
143143
var hook Hook
144144
if hookConfig.Container != nil {
145145
if hookConfig.Upload != nil {
146-
hook = NewRemoteHook(NewUploadHook())
146+
hook = NewRemoteHook(NewUploadHook(), client, hookConfig.Container.Namespace)
147147
} else if hookConfig.Download != nil {
148-
hook = NewRemoteHook(NewDownloadHook())
148+
hook = NewRemoteHook(NewDownloadHook(), client, hookConfig.Container.Namespace)
149149
} else if hookConfig.Logs != nil {
150150
// we use another waiting strategy here, because the pod might has finished already
151-
hook = NewRemoteHookWithWaitingStrategy(NewLogsHook(hookWriter), targetselector.NewUntilNotWaitingStrategy(time.Second*2))
151+
hook = NewRemoteHookWithWaitingStrategy(NewLogsHook(hookWriter), targetselector.NewUntilNotWaitingStrategy(time.Second*2, client, hookConfig.Container.Namespace))
152152
} else if hookConfig.Wait != nil {
153153
hook = NewWaitHook()
154154
} else {
155-
hook = NewRemoteHook(NewRemoteCommandHook(hookWriter, hookWriter))
155+
hook = NewRemoteHook(NewRemoteCommandHook(hookWriter, hookWriter), client, hookConfig.Container.Namespace)
156156
}
157157
} else {
158158
hook = NewLocalCommandHook(hookWriter, hookWriter)

pkg/devspace/hook/remote_hook.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ type RemoteHook interface {
2323
ExecuteRemotely(hook *latest.HookConfig, podContainer *selector.SelectedPodContainer, client kubectl.Client, config config.Config, dependencies []types.Dependency, log logpkg.Logger) error
2424
}
2525

26-
func NewRemoteHook(hook RemoteHook) Hook {
26+
func NewRemoteHook(hook RemoteHook, client kubectl.Client, namespace string) Hook {
2727
return &remoteHook{
2828
Hook: hook,
29-
WaitingStrategy: targetselector.NewUntilNewestRunningWaitingStrategy(time.Second * 2),
29+
WaitingStrategy: targetselector.NewUntilNewestRunningWaitingStrategy(time.Second*2, client, namespace),
3030
}
3131
}
3232

pkg/devspace/services/logs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func (serviceClient *client) StartLogsWithWriter(options targetselector.Options,
2424
} else {
2525
options.FilterPod = nil
2626
options.FilterContainer = nil
27-
options.WaitingStrategy = targetselector.NewUntilNotWaitingStrategy(time.Second * 2)
27+
options.WaitingStrategy = targetselector.NewUntilNotWaitingStrategy(time.Second*2, serviceClient.client, options.Namespace)
2828
}
2929

3030
container, err := targetSelector.SelectSingleContainer(context.TODO(), options, serviceClient.log)

pkg/devspace/services/podreplace/persist.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func persistPaths(podName string, replacePod *latest.ReplacePod, copiedPod *core
7272
Name: fmt.Sprintf("path-%d-init", i),
7373
Image: container.Image,
7474
Command: []string{"sh"},
75-
Args: []string{"-c", fmt.Sprintf("if [ ! -d \"/devspace-persistence/.devspace/\" ] && [ -d \"%s\" ]; then cp -a %s/. /devspace-persistence/ && mkdir /devspace-persistence/.devspace ; fi", path.Clean(p.Path), path.Clean(p.Path))},
75+
Args: []string{"-c", fmt.Sprintf("set -x\nif [ ! -d \"/devspace-persistence/.devspace/\" ] && [ -d \"%s\" ]; then\n cp -a %s/. /devspace-persistence/ && mkdir /devspace-persistence/.devspace\nfi", path.Clean(p.Path), path.Clean(p.Path))},
7676
VolumeMounts: []corev1.VolumeMount{
7777
{
7878
Name: "devspace-persistence",

pkg/devspace/services/podreplace/replace.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -829,7 +829,7 @@ func findSingleReplacedPod(ctx context.Context, client kubectl.Client, replacePo
829829
targetOptions := targetselector.NewEmptyOptions().ApplyConfigParameter(labelSelector, replacePod.Namespace, replacePod.ContainerName, "")
830830
targetOptions.Timeout = 30
831831
targetOptions.AllowPick = false
832-
targetOptions.WaitingStrategy = targetselector.NewUntilNotTerminatingStrategy(0)
832+
targetOptions.WaitingStrategy = targetselector.NewUntilNotTerminatingStrategy(0, client, targetOptions.Namespace)
833833
targetOptions.SkipInitContainers = true
834834
selected, err := targetselector.NewTargetSelector(client).SelectSingleContainer(ctx, targetOptions, log)
835835
if err != nil {
@@ -902,7 +902,7 @@ func findSingleReplaceablePodParent(ctx context.Context, client kubectl.Client,
902902
targetOptions := targetselector.NewEmptyOptions().ApplyConfigParameter(replacePod.LabelSelector, replacePod.Namespace, replacePod.ContainerName, "")
903903
targetOptions.Timeout = int64(300)
904904
targetOptions.AllowPick = false
905-
targetOptions.WaitingStrategy = targetselector.NewUntilNotTerminatingStrategy(time.Second * 2)
905+
targetOptions.WaitingStrategy = targetselector.NewUntilNotTerminatingStrategy(time.Second*2, client, targetOptions.Namespace)
906906
targetOptions.SkipInitContainers = true
907907
targetOptions.ImageSelector = []imageselector.ImageSelector{}
908908
if replacePod.ImageSelector != "" {

pkg/devspace/services/port_forwarding.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ func (serviceClient *client) startForwarding(portForwarding *latest.PortForwardi
122122

123123
options.ImageSelector = append(options.ImageSelector, *imageSelector)
124124
}
125-
options.WaitingStrategy = targetselector.NewUntilNewestRunningWaitingStrategy(time.Second * 2)
125+
options.WaitingStrategy = targetselector.NewUntilNewestRunningWaitingStrategy(time.Second*2, serviceClient.client, options.Namespace)
126126
options.SkipInitContainers = true
127127

128128
// start port forwarding

pkg/devspace/services/reverse_port_forwarding.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func (serviceClient *client) startReversePortForwarding(portForwarding *latest.P
3434

3535
options.ImageSelector = append(options.ImageSelector, *imageSelector)
3636
}
37-
options.WaitingStrategy = targetselector.NewUntilNewestRunningWaitingStrategy(time.Second * 2)
37+
options.WaitingStrategy = targetselector.NewUntilNewestRunningWaitingStrategy(time.Second*2, serviceClient.client, options.Namespace)
3838
options.SkipInitContainers = true
3939

4040
log.Info("Reverse-Port-Forwarding: Waiting for containers to start...")

pkg/devspace/services/sync.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func (serviceClient *client) newSyncFn(idx int, syncConfig *latest.SyncConfig, i
9898
return func() error {
9999
targetOptions := targetselector.NewEmptyOptions().ApplyConfigParameter(syncConfig.LabelSelector, syncConfig.Namespace, syncConfig.ContainerName, "")
100100
targetOptions.AllowPick = false
101-
targetOptions.WaitingStrategy = targetselector.NewUntilNewestRunningWaitingStrategy(time.Second * 2)
101+
targetOptions.WaitingStrategy = targetselector.NewUntilNewestRunningWaitingStrategy(time.Second*2, serviceClient.client, targetOptions.Namespace)
102102

103103
// set options
104104
options := &synccontroller.Options{

pkg/devspace/services/targetselector/until_newest_running.go

Lines changed: 113 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package targetselector
22

33
import (
4+
"context"
5+
"github.com/loft-sh/devspace/pkg/util/stringutil"
6+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
47
"sort"
58
"strings"
69
"sync"
@@ -13,10 +16,14 @@ import (
1316
)
1417

1518
// NewUntilNewestRunningWaitingStrategy creates a new waiting strategy
16-
func NewUntilNewestRunningWaitingStrategy(initialDelay time.Duration) WaitingStrategy {
19+
func NewUntilNewestRunningWaitingStrategy(initialDelay time.Duration, client kubectl.Client, namespace string) WaitingStrategy {
1720
return &untilNewestRunning{
1821
initialDelay: time.Now().Add(initialDelay),
19-
lastWarning: time.Now().Add(initialDelay),
22+
podInfoPrinter: &PodInfoPrinter{
23+
lastWarning: time.Now().Add(initialDelay),
24+
namespace: namespace,
25+
client: client,
26+
},
2027
}
2128
}
2229

@@ -25,27 +32,26 @@ func NewUntilNewestRunningWaitingStrategy(initialDelay time.Duration) WaitingStr
2532
type untilNewestRunning struct {
2633
initialDelay time.Time
2734

28-
lastMutex sync.Mutex
29-
lastWarning time.Time
35+
podInfoPrinter *PodInfoPrinter
3036
}
3137

3238
func (u *untilNewestRunning) SelectPod(pods []*v1.Pod, log log.Logger) (bool, *v1.Pod, error) {
3339
now := time.Now()
3440
if now.Before(u.initialDelay) {
3541
return false, nil, nil
3642
} else if len(pods) == 0 {
37-
u.printNotFoundWarning(log)
43+
u.podInfoPrinter.PrintNotFoundWarning(log)
3844
return false, nil, nil
3945
}
4046

4147
sort.Slice(pods, func(i, j int) bool {
4248
return selector.SortPodsByNewest(pods, i, j)
4349
})
4450
if HasPodProblem(pods[0]) {
45-
u.printPodWarning(pods[0], log)
51+
u.podInfoPrinter.PrintPodWarning(pods[0], log)
4652
return false, nil, nil
4753
} else if kubectl.GetPodStatus(pods[0]) != "Running" {
48-
u.printPodInfo(pods[0], log)
54+
u.podInfoPrinter.PrintPodInfo(pods[0], log)
4955
return false, nil, nil
5056
}
5157

@@ -57,56 +63,148 @@ func (u *untilNewestRunning) SelectContainer(containers []*selector.SelectedPodC
5763
if now.Before(u.initialDelay) {
5864
return false, nil, nil
5965
} else if len(containers) == 0 {
60-
u.printNotFoundWarning(log)
66+
u.podInfoPrinter.PrintNotFoundWarning(log)
6167
return false, nil, nil
6268
}
6369

6470
sort.Slice(containers, func(i, j int) bool {
6571
return selector.SortContainersByNewest(containers, i, j)
6672
})
6773
if HasPodProblem(containers[0].Pod) {
68-
u.printPodWarning(containers[0].Pod, log)
74+
u.podInfoPrinter.PrintPodWarning(containers[0].Pod, log)
6975
return false, nil, nil
7076
} else if !IsContainerRunning(containers[0]) {
71-
u.printPodInfo(containers[0].Pod, log)
77+
u.podInfoPrinter.PrintPodInfo(containers[0].Pod, log)
7278
return false, nil, nil
7379
}
7480

7581
return true, containers[0], nil
7682
}
7783

78-
func (u *untilNewestRunning) printPodInfo(pod *v1.Pod, log log.Logger) {
84+
type PodInfoPrinter struct {
85+
lastMutex sync.Mutex
86+
lastWarning time.Time
87+
88+
namespace string
89+
client kubectl.Client
90+
shownEvents []string
91+
}
92+
93+
func (u *PodInfoPrinter) PrintPodInfo(pod *v1.Pod, log log.Logger) {
7994
u.lastMutex.Lock()
8095
defer u.lastMutex.Unlock()
8196

8297
if time.Since(u.lastWarning) > time.Second*10 {
8398
status := kubectl.GetPodStatus(pod)
84-
log.Infof("DevSpace is waiting, because Pod %s/%s has status: %s", pod.Namespace, pod.Name, status)
99+
log.Infof("DevSpace is waiting, because Pod %s has status: %s", pod.Name, status)
100+
101+
u.shownEvents = displayWarnings(relevantObjectsFromPod(pod), pod.Namespace, u.client, u.shownEvents, log)
85102
u.lastWarning = time.Now()
86103
}
87104
}
88105

89-
func (u *untilNewestRunning) printNotFoundWarning(log log.Logger) {
106+
func (u *PodInfoPrinter) PrintNotFoundWarning(log log.Logger) {
90107
u.lastMutex.Lock()
91108
defer u.lastMutex.Unlock()
92109

93110
if time.Since(u.lastWarning) > time.Second*10 {
94111
log.Warnf("DevSpace still couldn't find any Pods that match the selector. DevSpace will continue waiting, but this operation might timeout")
112+
113+
u.shownEvents = displayWarnings([]relevantObject{
114+
{
115+
Kind: "StatefulSet",
116+
},
117+
{
118+
Kind: "Deployment",
119+
},
120+
{
121+
Kind: "ReplicaSet",
122+
},
123+
}, u.namespace, u.client, u.shownEvents, log)
95124
u.lastWarning = time.Now()
96125
}
97126
}
98127

99-
func (u *untilNewestRunning) printPodWarning(pod *v1.Pod, log log.Logger) {
128+
func (u *PodInfoPrinter) PrintPodWarning(pod *v1.Pod, log log.Logger) {
100129
u.lastMutex.Lock()
101130
defer u.lastMutex.Unlock()
102131

103132
if time.Since(u.lastWarning) > time.Second*10 {
104133
status := kubectl.GetPodStatus(pod)
105-
log.Warnf("Pod %s/%s has critical status: %s. DevSpace will continue waiting, but this operation might timeout", pod.Namespace, pod.Name, status)
134+
log.Warnf("Pod %s has critical status: %s. DevSpace will continue waiting, but this operation might timeout", pod.Name, status)
106135
u.lastWarning = time.Now()
107136
}
108137
}
109138

139+
type relevantObject struct {
140+
Kind string
141+
Name string
142+
UID string
143+
}
144+
145+
func displayWarnings(relevantObjects []relevantObject, namespace string, client kubectl.Client, filter []string, log log.Logger) []string {
146+
events, err := client.KubeClient().CoreV1().Events(namespace).List(context.TODO(), metav1.ListOptions{})
147+
if err != nil {
148+
log.Debugf("Error retrieving pod events: %v", err)
149+
return nil
150+
}
151+
152+
sort.Slice(events.Items, func(i, j int) bool {
153+
return events.Items[i].CreationTimestamp.Unix() > events.Items[j].CreationTimestamp.Unix()
154+
})
155+
for _, event := range events.Items {
156+
if event.Type != "Warning" {
157+
continue
158+
} else if stringutil.Contains(filter, event.Name) {
159+
continue
160+
} else if !eventMatches(&event, relevantObjects) {
161+
continue
162+
}
163+
164+
log.Warnf("%s %s: %s (%s)", event.InvolvedObject.Kind, event.InvolvedObject.Name, event.Message, event.Reason)
165+
filter = append(filter, event.Name)
166+
}
167+
168+
return filter
169+
}
170+
171+
func relevantObjectsFromPod(pod *v1.Pod) []relevantObject {
172+
// search for persistent volume claims
173+
objects := []relevantObject{
174+
{
175+
Kind: "Pod",
176+
Name: pod.Name,
177+
UID: string(pod.UID),
178+
},
179+
}
180+
for _, v := range pod.Spec.Volumes {
181+
if v.PersistentVolumeClaim != nil {
182+
objects = append(objects, relevantObject{
183+
Kind: "PersistentVolumeClaim",
184+
Name: v.PersistentVolumeClaim.ClaimName,
185+
})
186+
}
187+
188+
}
189+
return objects
190+
}
191+
192+
func eventMatches(event *v1.Event, objects []relevantObject) bool {
193+
for _, o := range objects {
194+
if o.Name != "" && event.InvolvedObject.Name != o.Name {
195+
continue
196+
} else if o.Kind != "" && event.InvolvedObject.Kind != o.Kind {
197+
continue
198+
} else if o.UID != "" && string(event.InvolvedObject.UID) != o.UID {
199+
continue
200+
}
201+
202+
return true
203+
}
204+
205+
return false
206+
}
207+
110208
func IsContainerRunning(container *selector.SelectedPodContainer) bool {
111209
if container.Pod.DeletionTimestamp != nil {
112210
return false

0 commit comments

Comments
 (0)