github.com/GoogleContainerTools/skaffold/v2@v2.13.2/integration/dev_test.go (about)

     1  /*
     2  Copyright 2019 The Skaffold Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package integration
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  	"net/http"
    24  	"os"
    25  	"runtime"
    26  	"strings"
    27  	"syscall"
    28  	"testing"
    29  	"time"
    30  
    31  	appsv1 "k8s.io/api/apps/v1"
    32  	"k8s.io/apimachinery/pkg/util/wait"
    33  	"k8s.io/client-go/tools/clientcmd"
    34  
    35  	"github.com/GoogleContainerTools/skaffold/v2/integration/skaffold"
    36  	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/config"
    37  	"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/constants"
    38  	event "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/event/v2"
    39  	"github.com/GoogleContainerTools/skaffold/v2/proto/v1"
    40  	V2proto "github.com/GoogleContainerTools/skaffold/v2/proto/v2"
    41  	"github.com/GoogleContainerTools/skaffold/v2/testutil"
    42  )
    43  
    44  func TestDevNotification(t *testing.T) {
    45  	tests := []struct {
    46  		description string
    47  		trigger     string
    48  	}{
    49  		{
    50  			description: "dev with polling trigger",
    51  			trigger:     "polling",
    52  		},
    53  		{
    54  			description: "dev with notify trigger",
    55  			trigger:     "notify",
    56  		},
    57  	}
    58  	for _, test := range tests {
    59  		t.Run(test.description, func(t *testing.T) {
    60  			MarkIntegrationTest(t, CanRunWithoutGcp)
    61  			Run(t, "testdata/dev", "sh", "-c", "echo foo > foo")
    62  			defer Run(t, "testdata/dev", "rm", "foo")
    63  
    64  			// Run skaffold build first to fail quickly on a build failure
    65  			skaffold.Build().InDir("testdata/dev").RunOrFail(t)
    66  
    67  			ns, client := SetupNamespace(t)
    68  
    69  			rpcAddr := randomPort()
    70  			skaffold.Dev("--rpc-port", rpcAddr, "--trigger", test.trigger).InDir("testdata/dev").InNs(ns.Name).RunBackground(t)
    71  
    72  			dep := client.GetDeployment(testDev)
    73  
    74  			_, entries := v2apiEvents(t, rpcAddr)
    75  
    76  			// Wait for the first devloop to register target files to the monitor before running command to change target files
    77  			failNowIfError(t, waitForV2Event(100*time.Second, entries, func(e *V2proto.Event) bool {
    78  				taskEvent, ok := e.EventType.(*V2proto.Event_TaskEvent)
    79  				return ok && taskEvent.TaskEvent.Task == string(constants.DevLoop) && taskEvent.TaskEvent.Status == event.Succeeded
    80  			}))
    81  
    82  			// Make a change to foo so that dev is forced to delete the Deployment and redeploy
    83  			Run(t, "testdata/dev", "sh", "-c", "echo bar > foo")
    84  
    85  			// Make sure the old Deployment and the new Deployment are different
    86  			err := wait.PollImmediate(time.Millisecond*500, 1*time.Minute, func() (bool, error) {
    87  				newDep := client.GetDeployment(testDev)
    88  				t.Logf("old gen: %d, new gen: %d", dep.GetGeneration(), newDep.GetGeneration())
    89  				return dep.GetGeneration() != newDep.GetGeneration(), nil
    90  			})
    91  			failNowIfError(t, err)
    92  		})
    93  	}
    94  }
    95  
    96  func TestDevGracefulCancel(t *testing.T) {
    97  	if runtime.GOOS == "windows" {
    98  		t.Skip("graceful cancel doesn't work on windows")
    99  	}
   100  
   101  	tests := []struct {
   102  		name        string
   103  		dir         string
   104  		pods        []string
   105  		deployments []string
   106  	}{
   107  		{
   108  			name: "getting-started",
   109  			dir:  "examples/getting-started",
   110  			pods: []string{"getting-started"},
   111  		},
   112  		{
   113  			name:        "multi-config-microservices",
   114  			dir:         "examples/multi-config-microservices",
   115  			deployments: []string{"leeroy-app", "leeroy-web"},
   116  		},
   117  		{
   118  			name: "multiple deployers",
   119  			dir:  "testdata/deploy-multiple",
   120  			pods: []string{"deploy-kubectl", "deploy-kustomize"},
   121  		},
   122  	}
   123  
   124  	for _, test := range tests {
   125  		t.Run(test.name, func(t *testing.T) {
   126  			MarkIntegrationTest(t, CanRunWithoutGcp)
   127  
   128  			ns, client := SetupNamespace(t)
   129  			p, _ := skaffold.Dev("-vtrace").InDir(test.dir).InNs(ns.Name).StartWithProcess(t)
   130  			client.WaitForPodsReady(test.pods...)
   131  			client.WaitForDeploymentsToStabilize(test.deployments...)
   132  
   133  			defer func() {
   134  				state, _ := p.Wait()
   135  
   136  				// We can't `recover()` from a remotely panicked process, but we can check exit code instead.
   137  				// Exit code 2 means the process panicked.
   138  				// https://github.com/golang/go/issues/24284
   139  				if state.ExitCode() == 2 {
   140  					t.Fail()
   141  				}
   142  			}()
   143  
   144  			// once deployments are stable, send a SIGINT and make sure things cleanup correctly
   145  			p.Signal(syscall.SIGINT)
   146  		})
   147  	}
   148  }
   149  
   150  func TestDevCancelWithDockerDeployer(t *testing.T) {
   151  	if runtime.GOOS == "windows" {
   152  		t.Skip("graceful cancel doesn't work on windows")
   153  	}
   154  
   155  	tests := []struct {
   156  		description string
   157  		dir         string
   158  		containers  []string
   159  	}{
   160  		{
   161  			description: "interrupt dev loop in Docker deployer",
   162  			dir:         "testdata/docker-deploy",
   163  			containers:  []string{"ernie", "bert"},
   164  		},
   165  	}
   166  
   167  	for _, test := range tests {
   168  		t.Run(test.description, func(t *testing.T) {
   169  			MarkIntegrationTest(t, CanRunWithoutGcp)
   170  			p, err := skaffold.Dev().InDir(test.dir).StartWithProcess(t)
   171  			if err != nil {
   172  				t.Fatalf("error starting skaffold dev process")
   173  			}
   174  
   175  			if err = waitForContainersRunning(t, test.containers...); err != nil {
   176  				t.Fatalf("failed waiting for containers: %v", err)
   177  			}
   178  
   179  			p.Signal(syscall.SIGINT)
   180  
   181  			state, _ := p.Wait()
   182  
   183  			if state.ExitCode() != 0 {
   184  				t.Fail()
   185  			}
   186  		})
   187  	}
   188  }
   189  
   190  func TestDevAPIBuildTrigger(t *testing.T) {
   191  	MarkIntegrationTest(t, CanRunWithoutGcp)
   192  
   193  	Run(t, "testdata/dev", "sh", "-c", "echo foo > foo")
   194  	defer Run(t, "testdata/dev", "rm", "foo")
   195  
   196  	// Run skaffold build first to fail quickly on a build failure
   197  	skaffold.Build().InDir("testdata/dev").RunOrFail(t)
   198  
   199  	ns, _ := SetupNamespace(t)
   200  
   201  	rpcAddr := randomPort()
   202  	skaffold.Dev("--auto-build=false", "--auto-sync=false", "--auto-deploy=false", "--rpc-port", rpcAddr, "--cache-artifacts=false").InDir("testdata/dev").InNs(ns.Name).RunBackground(t)
   203  
   204  	rpcClient, entries := apiEvents(t, rpcAddr)
   205  
   206  	// Wait for the first devloop to register target files to the monitor before running command to change target files
   207  	failNowIfError(t, waitForEvent(90*time.Second, entries, func(e *proto.LogEntry) bool {
   208  		dle, ok := e.Event.EventType.(*proto.Event_DevLoopEvent)
   209  		return ok && dle.DevLoopEvent.Status == event.Succeeded
   210  	}))
   211  
   212  	// Make a change to foo
   213  	Run(t, "testdata/dev", "sh", "-c", "echo bar > foo")
   214  
   215  	// Issue a build trigger
   216  	rpcClient.Execute(context.Background(), &proto.UserIntentRequest{
   217  		Intent: &proto.Intent{
   218  			Build: true,
   219  		},
   220  	})
   221  
   222  	// Ensure we see a build triggered in the event log
   223  	err := waitForEvent(2*time.Minute, entries, func(e *proto.LogEntry) bool {
   224  		return e.GetEvent().GetBuildEvent().GetArtifact() == testDev
   225  	})
   226  	failNowIfError(t, err)
   227  }
   228  
   229  func TestDevApiDeployTrigger(t *testing.T) {
   230  	MarkIntegrationTest(t, CanRunWithoutGcp)
   231  
   232  	Run(t, "testdata/dev", "sh", "-c", "echo foo > foo")
   233  	defer Run(t, "testdata/dev", "rm", "foo")
   234  
   235  	// Run skaffold build first to fail quickly on a build failure
   236  	skaffold.Build().InDir("testdata/dev").RunOrFail(t)
   237  
   238  	ns, client := SetupNamespace(t)
   239  
   240  	rpcAddr := randomPort()
   241  	skaffold.Dev("--auto-deploy=false", "--rpc-port", rpcAddr, "--cache-artifacts=false").InDir("testdata/dev").InNs(ns.Name).RunBackground(t)
   242  
   243  	rpcClient, entries := apiEvents(t, rpcAddr)
   244  	dep := client.GetDeployment(testDev)
   245  
   246  	// Wait for the first devloop to register target files to the monitor before running command to change target files
   247  	failNowIfError(t, waitForEvent(90*time.Second, entries, func(e *proto.LogEntry) bool {
   248  		dle, ok := e.Event.EventType.(*proto.Event_DevLoopEvent)
   249  		return ok && dle.DevLoopEvent.Status == event.Succeeded
   250  	}))
   251  
   252  	// Make a change to foo
   253  	Run(t, "testdata/dev", "sh", "-c", "echo bar > foo")
   254  
   255  	// Issue a deploy trigger
   256  	rpcClient.Execute(context.Background(), &proto.UserIntentRequest{
   257  		Intent: &proto.Intent{
   258  			Deploy: true,
   259  		},
   260  	})
   261  
   262  	verifyDeployment(t, entries, client, dep)
   263  }
   264  
   265  func TestDevAPIAutoTriggers(t *testing.T) {
   266  	MarkIntegrationTest(t, CanRunWithoutGcp)
   267  
   268  	Run(t, "testdata/dev", "sh", "-c", "echo foo > foo")
   269  	defer Run(t, "testdata/dev", "rm", "foo")
   270  
   271  	// Run skaffold build first to fail quickly on a build failure
   272  	skaffold.Build().InDir("testdata/dev").RunOrFail(t)
   273  
   274  	ns, client := SetupNamespace(t)
   275  
   276  	rpcAddr := randomPort()
   277  	skaffold.Dev("--auto-build=false", "--auto-sync=false", "--auto-deploy=false", "--rpc-port", rpcAddr, "--cache-artifacts=false").InDir("testdata/dev").InNs(ns.Name).RunBackground(t)
   278  
   279  	rpcClient, entries := apiEvents(t, rpcAddr)
   280  	dep := client.GetDeployment(testDev)
   281  
   282  	// Wait for the first devloop to register target files to the monitor before running command to change target files
   283  	failNowIfError(t, waitForEvent(90*time.Second, entries, func(e *proto.LogEntry) bool {
   284  		dle, ok := e.Event.EventType.(*proto.Event_DevLoopEvent)
   285  		return ok && dle.DevLoopEvent.Status == event.Succeeded
   286  	}))
   287  
   288  	// Make a change to foo
   289  	Run(t, "testdata/dev", "sh", "-c", "echo bar > foo")
   290  
   291  	// Enable auto build
   292  	rpcClient.AutoBuild(context.Background(), &proto.TriggerRequest{
   293  		State: &proto.TriggerState{
   294  			Val: &proto.TriggerState_Enabled{
   295  				Enabled: true,
   296  			},
   297  		},
   298  	})
   299  	// Ensure we see a build triggered in the event log
   300  	err := waitForEvent(2*time.Minute, entries, func(e *proto.LogEntry) bool {
   301  		return e.GetEvent().GetBuildEvent().GetArtifact() == testDev
   302  	})
   303  	failNowIfError(t, err)
   304  
   305  	rpcClient.AutoDeploy(context.Background(), &proto.TriggerRequest{
   306  		State: &proto.TriggerState{
   307  			Val: &proto.TriggerState_Enabled{
   308  				Enabled: true,
   309  			},
   310  		},
   311  	})
   312  	verifyDeployment(t, entries, client, dep)
   313  }
   314  
   315  func verifyDeployment(t *testing.T, entries chan *proto.LogEntry, client *NSKubernetesClient, dep *appsv1.Deployment) {
   316  	// Ensure we see a deploy triggered in the event log
   317  	err := waitForEvent(2*time.Minute, entries, func(e *proto.LogEntry) bool {
   318  		return e.GetEvent().GetDeployEvent().GetStatus() == InProgress
   319  	})
   320  	failNowIfError(t, err)
   321  
   322  	// Make sure the old Deployment and the new Deployment are different
   323  	err = wait.Poll(5*time.Second, 3*time.Minute, func() (bool, error) {
   324  		newDep := client.GetDeployment(testDev)
   325  		t.Logf("old gen: %d, new gen: %d", dep.GetGeneration(), newDep.GetGeneration())
   326  		return dep.GetGeneration() != newDep.GetGeneration(), nil
   327  	})
   328  	failNowIfError(t, err)
   329  }
   330  
   331  func TestDevPortForward(t *testing.T) {
   332  	tests := []struct {
   333  		name string
   334  		dir  string
   335  	}{
   336  		{
   337  			name: "microservices",
   338  			dir:  "examples/microservices"},
   339  		{
   340  			name: "multi-config-microservices",
   341  			dir:  "examples/multi-config-microservices"},
   342  	}
   343  	for _, test := range tests {
   344  		t.Run(test.name, func(t *testing.T) {
   345  			MarkIntegrationTest(t, CanRunWithoutGcp)
   346  			// Run skaffold build first to fail quickly on a build failure
   347  			skaffold.Build().InDir(test.dir).RunOrFail(t)
   348  
   349  			ns, _ := SetupNamespace(t)
   350  
   351  			rpcAddr := randomPort()
   352  			skaffold.Dev("--status-check=false", "--port-forward", "--rpc-port", rpcAddr).InDir(test.dir).InNs(ns.Name).RunBackground(t)
   353  
   354  			_, entries := apiEvents(t, rpcAddr)
   355  
   356  			waitForPortForwardEvent(t, entries, "leeroy-app", "service", ns.Name, "leeroooooy app!!\n")
   357  
   358  			original, perms, fErr := replaceInFile("leeroooooy app!!", "test string", fmt.Sprintf("%s/leeroy-app/app.go", test.dir))
   359  			failNowIfError(t, fErr)
   360  			defer func() {
   361  				if original != nil {
   362  					os.WriteFile(fmt.Sprintf("%s/leeroy-app/app.go", test.dir), original, perms)
   363  				}
   364  			}()
   365  
   366  			waitForPortForwardEvent(t, entries, "leeroy-app", "service", ns.Name, "test string\n")
   367  		})
   368  	}
   369  }
   370  
   371  func TestDevDeletePreviousBuiltImages(t *testing.T) {
   372  	tests := []struct {
   373  		name string
   374  		dir  string
   375  	}{
   376  		{
   377  			name: "microservices",
   378  			dir:  "examples/microservices"},
   379  	}
   380  	for _, test := range tests {
   381  		t.Run(test.name, func(t *testing.T) {
   382  			MarkIntegrationTest(t, CanRunWithoutGcp)
   383  			// Run skaffold build first to fail quickly on a build failure
   384  			skaffold.Build().InDir(test.dir).RunOrFail(t)
   385  
   386  			ns, k8sClient := SetupNamespace(t)
   387  
   388  			rpcAddr := randomPort()
   389  			skaffold.Dev("--status-check=false", "--port-forward", "--rpc-port", rpcAddr).InDir(test.dir).InNs(ns.Name).RunBackground(t)
   390  
   391  			_, entries := apiEvents(t, rpcAddr)
   392  
   393  			waitForPortForwardEvent(t, entries, "leeroy-app", "service", ns.Name, "leeroooooy app!!\n")
   394  			deployment := k8sClient.GetDeployment("leeroy-app")
   395  			image := deployment.Spec.Template.Spec.Containers[0].Image
   396  
   397  			original, perms, fErr := replaceInFile("leeroooooy app!!", "test string", fmt.Sprintf("%s/leeroy-app/app.go", test.dir))
   398  			failNowIfError(t, fErr)
   399  			defer func() {
   400  				if original != nil {
   401  					os.WriteFile(fmt.Sprintf("%s/leeroy-app/app.go", test.dir), original, perms)
   402  				}
   403  			}()
   404  
   405  			waitForPortForwardEvent(t, entries, "leeroy-app", "service", ns.Name, "test string\n")
   406  			client := SetupDockerClient(t)
   407  			ctx := context.TODO()
   408  			wait.Poll(3*time.Second, time.Minute*2, func() (done bool, err error) {
   409  				return !client.ImageExists(ctx, image), nil
   410  			})
   411  		})
   412  	}
   413  }
   414  
   415  func TestDevPortForwardDefaultNamespace(t *testing.T) {
   416  	MarkIntegrationTest(t, CanRunWithoutGcp)
   417  
   418  	// Run skaffold build first to fail quickly on a build failure
   419  	skaffold.Build().InDir("examples/microservices").RunOrFail(t)
   420  
   421  	rpcAddr := randomPort()
   422  	skaffold.Dev("--status-check=false", "--port-forward", "--rpc-port", rpcAddr).InDir("examples/microservices").RunBackground(t)
   423  	defer skaffold.Delete().InDir("examples/microservices").Run(t)
   424  	_, entries := apiEvents(t, rpcAddr)
   425  
   426  	// No namespace was provided to `skaffold dev`, so we assume "default"
   427  	waitForPortForwardEvent(t, entries, "leeroy-app", "service", "default", "leeroooooy app!!\n")
   428  
   429  	original, perms, fErr := replaceInFile("leeroooooy app!!", "test string", "examples/microservices/leeroy-app/app.go")
   430  	failNowIfError(t, fErr)
   431  	defer func() {
   432  		if original != nil {
   433  			os.WriteFile("examples/microservices/leeroy-app/app.go", original, perms)
   434  		}
   435  	}()
   436  
   437  	waitForPortForwardEvent(t, entries, "leeroy-app", "service", "default", "test string\n")
   438  }
   439  
   440  func TestDevPortForwardGKELoadBalancer(t *testing.T) {
   441  	MarkIntegrationTest(t, NeedsGcp)
   442  	t.Skip("Skipping until resolved")
   443  
   444  	// Run skaffold build first to fail quickly on a build failure
   445  	skaffold.Build().InDir("testdata/gke_loadbalancer").RunOrFail(t)
   446  
   447  	ns, _ := SetupNamespace(t)
   448  
   449  	rpcAddr := randomPort()
   450  	env := []string{fmt.Sprintf("TEST_NS=%s", ns.Name)}
   451  	skaffold.Dev("--port-forward", "--rpc-port", rpcAddr).InDir("testdata/gke_loadbalancer").InNs(ns.Name).WithEnv(env).RunBackground(t)
   452  
   453  	_, entries := apiEvents(t, rpcAddr)
   454  
   455  	waitForPortForwardEvent(t, entries, "gke-loadbalancer", "service", ns.Name, "hello!!\n")
   456  }
   457  
   458  func getLocalPortFromPortForwardEvent(t *testing.T, entries chan *proto.LogEntry, resourceName, resourceType, namespace string) (string, int) {
   459  	timeout := time.After(2 * time.Minute)
   460  	for {
   461  		select {
   462  		case <-timeout:
   463  			t.Fatalf("timed out waiting for port forwarding event")
   464  		case e := <-entries:
   465  			switch e.Event.GetEventType().(type) {
   466  			case *proto.Event_PortEvent:
   467  				t.Logf("port event received: %v", e)
   468  				if e.Event.GetPortEvent().ResourceName == resourceName &&
   469  					e.Event.GetPortEvent().ResourceType == resourceType &&
   470  					e.Event.GetPortEvent().Namespace == namespace {
   471  					address := e.Event.GetPortEvent().Address
   472  					port := e.Event.GetPortEvent().LocalPort
   473  					t.Logf("Detected %s/%s is forwarded to address %s port %d", resourceType, resourceName, address, port)
   474  					return address, int(port)
   475  				}
   476  			default:
   477  				t.Logf("event received: %v", e)
   478  			}
   479  		}
   480  	}
   481  }
   482  
   483  //nolint:unparam
   484  func waitForPortForwardEvent(t *testing.T, entries chan *proto.LogEntry, resourceName, resourceType, namespace, expected string) {
   485  	address, port := getLocalPortFromPortForwardEvent(t, entries, resourceName, resourceType, namespace)
   486  	assertResponseFromPort(t, address, port, expected)
   487  }
   488  
   489  // assertResponseFromPort waits for two minutes for the expected response at port.
   490  func assertResponseFromPort(t *testing.T, address string, port int, expected string) {
   491  	url := fmt.Sprintf("http://%s:%d", address, port)
   492  	t.Logf("Waiting on %s to return: %s", url, expected)
   493  	ctx, cancelTimeout := context.WithTimeout(context.Background(), 5*time.Minute)
   494  	defer cancelTimeout()
   495  
   496  	for {
   497  		select {
   498  		case <-ctx.Done():
   499  			t.Fatalf("Timed out waiting for response from port %d", port)
   500  		case <-time.After(1 * time.Second):
   501  			client := http.Client{Timeout: 1 * time.Second}
   502  			resp, err := client.Get(url)
   503  			if err != nil {
   504  				t.Logf("[retriable error]: %v", err)
   505  				continue
   506  			}
   507  			defer resp.Body.Close()
   508  			body, err := io.ReadAll(resp.Body)
   509  			if err != nil {
   510  				t.Logf("[retriable error] reading response: %v", err)
   511  				continue
   512  			}
   513  			if string(body) == expected {
   514  				return
   515  			}
   516  			t.Logf("[retriable error] didn't get expected response from port. got: %s, expected: %s", string(body), expected)
   517  		}
   518  	}
   519  }
   520  
   521  func replaceInFile(target, replacement, filepath string) ([]byte, os.FileMode, error) {
   522  	fInfo, err := os.Stat(filepath)
   523  	if err != nil {
   524  		return nil, 0, err
   525  	}
   526  	original, err := os.ReadFile(filepath)
   527  	if err != nil {
   528  		return nil, 0, err
   529  	}
   530  
   531  	newContents := strings.ReplaceAll(string(original), target, replacement)
   532  
   533  	err = os.WriteFile(filepath, []byte(newContents), 0)
   534  
   535  	return original, fInfo.Mode(), err
   536  }
   537  
   538  func TestDev_WithKubecontextOverride(t *testing.T) {
   539  	MarkIntegrationTest(t, CanRunWithoutGcp)
   540  
   541  	testutil.Run(t, "skaffold run with kubecontext override", func(t *testutil.T) {
   542  		ns, client := SetupNamespace(t.T)
   543  
   544  		modifiedKubeconfig, kubecontext, err := createModifiedKubeconfig(ns.Name)
   545  		failNowIfError(t, err)
   546  
   547  		kubeconfig := t.NewTempDir().
   548  			Write("kubeconfig", string(modifiedKubeconfig)).
   549  			Path("kubeconfig")
   550  		env := []string{fmt.Sprintf("KUBECONFIG=%s", kubeconfig)}
   551  
   552  		// n.b. for the sake of this test the namespace must not be given explicitly
   553  		skaffold.Run("--kube-context", kubecontext).InDir("examples/getting-started").WithEnv(env).InNs(ns.Name).RunOrFail(t.T)
   554  
   555  		client.WaitForPodsReady("getting-started")
   556  	})
   557  }
   558  
   559  func createModifiedKubeconfig(namespace string) ([]byte, string, error) {
   560  	// do not use context.CurrentConfig(), because it may have cached a different config
   561  	kubeConfig, err := clientcmd.NewDefaultClientConfigLoadingRules().Load()
   562  	if err != nil {
   563  		return nil, "", err
   564  	}
   565  
   566  	contextName := "modified-context"
   567  	if config.IsKindCluster(kubeConfig.CurrentContext) {
   568  		contextName = "kind-" + contextName
   569  	}
   570  	if config.IsK3dCluster(kubeConfig.CurrentContext) {
   571  		contextName = "k3d-" + contextName
   572  	}
   573  
   574  	if kubeConfig.CurrentContext == constants.DefaultMinikubeContext {
   575  		contextName = constants.DefaultMinikubeContext // skip, since integration test with minikube runs on single cluster
   576  	}
   577  
   578  	activeContext := kubeConfig.Contexts[kubeConfig.CurrentContext]
   579  	if activeContext == nil {
   580  		return nil, "", fmt.Errorf("no active kube-context set")
   581  	}
   582  	// clear the namespace in the active context
   583  	activeContext.Namespace = ""
   584  
   585  	newContext := activeContext.DeepCopy()
   586  	newContext.Namespace = namespace
   587  	kubeConfig.Contexts[contextName] = newContext
   588  
   589  	yaml, err := clientcmd.Write(*kubeConfig)
   590  	return yaml, contextName, err
   591  }