@@ -6,16 +6,16 @@ import (
66 "github.com/loft-sh/devspace/pkg/devspace/config/localcache"
77 "github.com/loft-sh/devspace/pkg/devspace/kubectl/util"
88 "github.com/loft-sh/devspace/pkg/devspace/upgrade"
9- "io"
10- "net"
11- "net/url"
12- "os"
13- "sort"
14-
9+ "github.com/loft-sh/devspace/pkg/util/idle"
1510 "github.com/loft-sh/devspace/pkg/util/kubeconfig"
1611 "github.com/loft-sh/devspace/pkg/util/log"
1712 "github.com/loft-sh/devspace/pkg/util/survey"
1813 "github.com/loft-sh/devspace/pkg/util/terminal"
14+ "io"
15+ "k8s.io/apimachinery/pkg/util/wait"
16+ "net/http"
17+ "os"
18+ "time"
1919
2020 "github.com/mgutz/ansi"
2121 "github.com/pkg/errors"
@@ -27,6 +27,10 @@ import (
2727 "k8s.io/client-go/tools/clientcmd"
2828)
2929
30+ var (
31+ stopPingAfter = time .Second * 600
32+ )
33+
3034// Client holds all kubernetes related functions
3135type Client interface {
3236 // CurrentContext returns the current kube context name
@@ -108,6 +112,17 @@ func NewClientFromContext(context, namespace string, switchContext bool, kubeLoa
108112 return nil , err
109113 }
110114 restConfig .UserAgent = "DevSpace Version " + upgrade .GetVersion ()
115+ restConfig .WrapTransport = func (rt http.RoundTripper ) http.RoundTripper {
116+ return & devSpaceRoundTripper {
117+ roundTripper : rt ,
118+ requestType : "Regular" ,
119+ callback : func (response * http.Response ) {
120+ if response .Header .Get ("X-DevSpace-Response-Type" ) == "Blocked" {
121+ log .GetInstance ().Fatalf ("Targeted Kubernetes environment has begun sleeping. Please restart DevSpace to wake up the environment" )
122+ }
123+ },
124+ }
125+ }
111126
112127 kubeClient , err := kubernetes .NewForConfig (restConfig )
113128 if err != nil {
@@ -126,56 +141,6 @@ func NewClientFromContext(context, namespace string, switchContext bool, kubeLoa
126141 }, nil
127142}
128143
129- // NewClientBySelect creates a new kubernetes client by user select @Factory
130- func NewClientBySelect (allowPrivate bool , switchContext bool , kubeLoader kubeconfig.Loader , log log.Logger ) (Client , error ) {
131- kubeConfig , err := kubeLoader .LoadRawConfig ()
132- if err != nil {
133- return nil , err
134- }
135-
136- // Get all kube contexts
137- options := make ([]string , 0 , len (kubeConfig .Contexts ))
138- for context := range kubeConfig .Contexts {
139- options = append (options , context )
140- }
141- if len (options ) == 0 {
142- return nil , errors .New ("No kubectl context found. Make sure kubectl is installed and you have a working kubernetes context configured" )
143- }
144-
145- sort .Strings (options )
146- for {
147- kubeContext , err := log .Question (& survey.QuestionOptions {
148- Question : "Which kube context do you want to use" ,
149- DefaultValue : kubeConfig .CurrentContext ,
150- Options : options ,
151- })
152- if err != nil {
153- return nil , err
154- }
155-
156- // Check if cluster is in private network
157- if ! allowPrivate {
158- context := kubeConfig .Contexts [kubeContext ]
159- cluster := kubeConfig .Clusters [context .Cluster ]
160-
161- url , err := url .Parse (cluster .Server )
162- if err != nil {
163- return nil , errors .Wrap (err , "url parse" )
164- }
165-
166- ip := net .ParseIP (url .Hostname ())
167- if ip != nil {
168- if IsPrivateIP (ip ) {
169- log .Infof ("Clusters with private ips (%s) cannot be used" , url .Hostname ())
170- continue
171- }
172- }
173- }
174-
175- return NewClientFromContext (kubeContext , "" , switchContext , kubeLoader )
176- }
177- }
178-
179144// ClientConfig returns the underlying kube client config
180145func (client * client ) ClientConfig () clientcmd.ClientConfig {
181146 return client .clientConfig
@@ -312,7 +277,17 @@ func CheckKubeContext(client Client, localCache localcache.Cache, noWarning, aut
312277 log .Infof ("Using namespace '%s'" , ansi .Color (currentConfigContext .Namespace , "white+b" ))
313278 log .Infof ("Using kube context '%s'" , ansi .Color (currentConfigContext .Context , "white+b" ))
314279 if resetClient {
315- return NewClientFromContext (currentConfigContext .Context , currentConfigContext .Namespace , true , client .KubeConfigLoader ())
280+ var err error
281+ client , err = NewClientFromContext (currentConfigContext .Context , currentConfigContext .Namespace , true , client .KubeConfigLoader ())
282+ if err != nil {
283+ return nil , err
284+ }
285+ }
286+
287+ // wake up and ping
288+ err := wakeUpAndPing (context .TODO (), client , log )
289+ if err != nil {
290+ return nil , errors .Wrap (err , "wakeup environment" )
316291 }
317292
318293 return client , nil
@@ -337,3 +312,127 @@ func (client *client) RestConfig() *rest.Config {
337312func (client * client ) KubeConfigLoader () kubeconfig.Loader {
338313 return client .kubeLoader
339314}
315+
316+ func wakeUpAndPing (ctx context.Context , client Client , log log.Logger ) error {
317+ err := wakeUp (ctx , client , log )
318+ if err != nil {
319+ return err
320+ }
321+
322+ // create ping config
323+ pingConfig := rest .CopyConfig (client .RestConfig ())
324+ pingConfig .WrapTransport = func (rt http.RoundTripper ) http.RoundTripper {
325+ return & devSpaceRoundTripper {
326+ roundTripper : rt ,
327+ requestType : "Ping" ,
328+ callback : func (response * http.Response ) {
329+ if response .Header .Get ("X-DevSpace-Response-Type" ) == "Blocked" {
330+ log .Fatalf ("Targeted Kubernetes environment has begun sleeping. Please restart DevSpace to wake up the environment" )
331+ }
332+ },
333+ }
334+ }
335+
336+ // create kube client
337+ kubeClient , err := kubernetes .NewForConfig (pingConfig )
338+ if err != nil {
339+ return err
340+ }
341+
342+ // start pinging
343+ go func () {
344+ getter , _ := idle .NewIdleGetter ()
345+ wait .UntilWithContext (ctx , func (ctx context.Context ) {
346+ if getter != nil {
347+ amountIdle , err := getter .Idle ()
348+ if err == nil && amountIdle > stopPingAfter {
349+ return
350+ }
351+ }
352+
353+ _ , err = kubeClient .CoreV1 ().Pods (client .Namespace ()).List (ctx , metav1.ListOptions {LabelSelector : "devspace=ping" })
354+ if err != nil {
355+ log .Debugf ("Error pinging Kubernetes environment: %v" , err )
356+ }
357+ }, time .Minute * 3 )
358+ }()
359+
360+ return nil
361+ }
362+
363+ func wakeUp (ctx context.Context , client Client , log log.Logger ) error {
364+ // check if environment is sleeping
365+ var isSleeping bool
366+ isSleepingConfig := rest .CopyConfig (client .RestConfig ())
367+ isSleepingConfig .WrapTransport = func (rt http.RoundTripper ) http.RoundTripper {
368+ return & devSpaceRoundTripper {
369+ roundTripper : rt ,
370+ requestType : "Ping" ,
371+ callback : func (response * http.Response ) {
372+ if response .Header .Get ("X-DevSpace-Response-Type" ) == "Blocked" {
373+ isSleeping = true
374+ }
375+ },
376+ }
377+ }
378+
379+ // create kube client
380+ kubeClient , err := kubernetes .NewForConfig (isSleepingConfig )
381+ if err != nil {
382+ return err
383+ }
384+
385+ // wake up the environment
386+ _ , err = kubeClient .CoreV1 ().Pods (client .Namespace ()).List (ctx , metav1.ListOptions {LabelSelector : "devspace=wakeup" })
387+ if err != nil && ! isSleeping {
388+ return fmt .Errorf ("Please make sure you have an existing valid kube config. You might want to check one of the following things:\n \n * Make sure you can use 'kubectl get namespaces' locally\n * If you are using Loft, you might want to run 'devspace create space' or 'loft create space'" )
389+ } else if ! isSleeping {
390+ return nil
391+ }
392+
393+ // wake up the environment
394+ wakeUpConfig := rest .CopyConfig (client .RestConfig ())
395+ wakeUpConfig .WrapTransport = func (rt http.RoundTripper ) http.RoundTripper {
396+ return & devSpaceRoundTripper {
397+ roundTripper : rt ,
398+ requestType : "WakeUp" ,
399+ callback : func (response * http.Response ) {
400+ if response .Header .Get ("X-DevSpace-Response-Type" ) == "WokenUp" {
401+ log .Infof ("Successfully woken up Kubernetes environment" )
402+ }
403+ },
404+ }
405+ }
406+
407+ // create kube client
408+ kubeClient , err = kubernetes .NewForConfig (wakeUpConfig )
409+ if err != nil {
410+ return err
411+ }
412+
413+ // print message if it takes too long
414+ log .Infof ("DevSpace is waking up the Kubernetes environment, please wait a second..." )
415+
416+ // wake up the environment
417+ _ , err = kubeClient .CoreV1 ().Pods (client .Namespace ()).List (ctx , metav1.ListOptions {LabelSelector : "devspace=wakeup" })
418+ if err != nil {
419+ return errors .Wrap (err , "error waking up the environment" )
420+ }
421+
422+ return nil
423+ }
424+
425+ type devSpaceRoundTripper struct {
426+ roundTripper http.RoundTripper
427+ requestType string
428+ callback func (response * http.Response )
429+ }
430+
431+ func (d * devSpaceRoundTripper ) RoundTrip (req * http.Request ) (* http.Response , error ) {
432+ req .Header .Set ("X-DevSpace-Request-Type" , d .requestType )
433+ response , err := d .roundTripper .RoundTrip (req )
434+ if response != nil && d .callback != nil {
435+ d .callback (response )
436+ }
437+ return response , err
438+ }
0 commit comments