Repository: GoogleContainerTools/skaffold
Pull request #8028 (2022-11-02 20:46:22)

fix: preserve configs order when creating renderers and deployers
Fixes: #7993

**Description**
This PR fixes the order in which every deployer and renderer should be created and invoked according to the dependencies created between different modules. So, if `module1` depends on/requires `module2`, skaffold should deploy first `module2` and then `module1`

Also, we now mock the `GetNamespace` from [deploy/helm/helm_test.go](https://github.com/GoogleContainerTools/skaffold/pull/8028/files#diff-00324bab5a92b65d9af9c88f4eb9db3aa71cad52423ca109a0b0b0ac2026ff70) to not depend on kubectl output to get the namespace.

**How to test**
From the code example created in this [commit](https://github.com/duongcongtoaimanabie/skaffold/commit/bfa0e231713ed042c253bb8838561ef8edd83611), run `skaffold dev`and you should be able to see the modules deployed in the proper dependency order all the time:

```
Starting deploy...
deploying for sk4
 - pod/getting-started created
deploying for sk3
 - pod/getting-started unchanged
deploying for sk2
 - pod/getting-started unchanged
deploying for sk1
 - pod/getting-started unchanged
```

codecov[bot]: # [Codecov](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8028?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) Report
> Merging [#8028](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8028?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) (631507b) into [main](https://codecov.io/gh/GoogleContainerTools/skaffold/commit/290280e8c6c19231e9ed5b5c027f2c01d22f75be?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) (290280e) will **decrease** coverage by `3.80%`.
> The diff coverage is `54.09%`.

```diff
@@            Coverage Diff             @@
##             main    #8028      +/-   ##
==========================================
- Coverage   70.48%   66.67%   -3.81%     
==========================================
  Files         515      596      +81     
  Lines       23150    28906    +5756     
==========================================
+ Hits        16317    19273    +2956     
- Misses       5776     8205    +2429     
- Partials     1057     1428     +371     
```


| [Impacted Files](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8028?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) | Coverage Δ | |
|---|---|---|
| [cmd/skaffold/app/cmd/credits/export.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8028/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvY3JlZGl0cy9leHBvcnQuZ28=) | `0.00% <0.00%> (ø)` | |
| [cmd/skaffold/app/cmd/deploy.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8028/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvZGVwbG95Lmdv) | `40.90% <0.00%> (-12.94%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/test.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8028/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvdGVzdC5nbw==) | `44.44% <0.00%> (ø)` | |
| [cmd/skaffold/app/exitcode.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8028/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9leGl0Y29kZS5nbw==) | `100.00% <ø> (+6.66%)` | :arrow_up: |
| [cmd/skaffold/skaffold.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8028/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL3NrYWZmb2xkLmdv) | `0.00% <0.00%> (ø)` | |
| [cmd/skaffold/app/cmd/inspect\_tests.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8028/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvaW5zcGVjdF90ZXN0cy5nbw==) | `62.50% <14.28%> (-1.14%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/render.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8028/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvcmVuZGVyLmdv) | `35.48% <18.18%> (-5.90%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/lsp.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8028/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvbHNwLmdv) | `28.12% <28.12%> (ø)` | |
| [cmd/skaffold/app/cmd/run.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8028/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvcnVuLmdv) | `64.28% <33.33%> (-9.63%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/fix.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8028/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvZml4Lmdv) | `56.41% <37.50%> (-20.07%)` | :arrow_down: |
| ... and [389 more](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/8028/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) | |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools)


Patch:
diff --git a/pkg/skaffold/runner/deployer.go b/pkg/skaffold/runner/deployer.go
index fe600eef3..dcc5f8e02 100644
--- a/pkg/skaffold/runner/deployer.go
+++ b/pkg/skaffold/runner/deployer.go
@@ -120,7 +120,8 @@ func GetDeployer(ctx context.Context, runCtx *runcontext.RunContext, labeller *l
 	var deployers []deploy.Deployer
 	localDeploy := false
 	remoteDeploy := false
-	for configName, pl := range pipelines.AllByConfigNames() {
+	for _, configName := range pipelines.AllOrderedConfigNames() {
+		pl := pipelines.GetForConfigName(configName)
 		d := pl.Deploy
 		r := pl.Render
 		dCtx := &deployerCtx{runCtx, d}
diff --git a/pkg/skaffold/runner/renderer.go b/pkg/skaffold/runner/renderer.go
index c68a7fcbf..90ab5e3b9 100644
--- a/pkg/skaffold/runner/renderer.go
+++ b/pkg/skaffold/runner/renderer.go
@@ -28,10 +28,11 @@ import (
 
 // GetRenderer creates a renderer from a given RunContext and pipeline definitions.
 func GetRenderer(ctx context.Context, runCtx *runcontext.RunContext, hydrationDir string, labels map[string]string, usingLegacyHelmDeploy bool) (renderer.Renderer, error) {
-	ps := runCtx.Pipelines.AllByConfigNames()
+	configNames := runCtx.Pipelines.AllOrderedConfigNames()
 
 	var gr renderer.GroupRenderer
-	for configName, p := range ps {
+	for _, configName := range configNames {
+		p := runCtx.Pipelines.GetForConfigName(configName)
 		rs, err := renderer.New(ctx, runCtx, p.Render, hydrationDir, labels, configName)
 		if err != nil {
 			return nil, err
@@ -43,7 +44,9 @@ func GetRenderer(ctx context.Context, runCtx *runcontext.RunContext, hydrationDi
 	// In case of legacy helm deployer configured and render command used
 	// force a helm renderer from deploy helm config
 	if usingLegacyHelmDeploy && runCtx.Opts.Command == "render" {
-		for configName, p := range ps {
+		for _, configName := range configNames {
+			p := runCtx.Pipelines.GetForConfigName(configName)
+
 			if p.Deploy.LegacyHelmDeploy == nil {
 				continue
 			}
diff --git a/pkg/skaffold/runner/runcontext/context.go b/pkg/skaffold/runner/runcontext/context.go
index 5fda5767b..a207b60d9 100644
--- a/pkg/skaffold/runner/runcontext/context.go
+++ b/pkg/skaffold/runner/runcontext/context.go
@@ -50,6 +50,7 @@ type Pipelines struct {
 	pipelines            []latest.Pipeline
 	pipelinesByConfig    map[string]latest.Pipeline
 	pipelinesByImageName map[string]latest.Pipeline
+	orderedConfigs       []string
 }
 
 // All returns all config pipelines.
@@ -57,8 +58,14 @@ func (ps Pipelines) All() []latest.Pipeline {
 	return ps.pipelines
 }
 
-func (ps Pipelines) AllByConfigNames() map[string]latest.Pipeline {
-	return ps.pipelinesByConfig
+// Returns the config names in the correct dependency order to be mapped against their proper pipeline.
+func (ps Pipelines) AllOrderedConfigNames() []string {
+	return ps.orderedConfigs
+}
+
+// Returns a pipeline given its associated config name.
+func (ps Pipelines) GetForConfigName(configName string) latest.Pipeline {
+	return ps.pipelinesByConfig[configName]
 }
 
 // Head returns the first `latest.Pipeline`.
@@ -158,18 +165,20 @@ func (ps Pipelines) StatusCheckDeadlineSeconds() int {
 	}
 	return c
 }
-func NewPipelines(pipelinesByConfig map[string]latest.Pipeline) Pipelines {
+func NewPipelines(pipelinesByConfig map[string]latest.Pipeline, orderedConfigs []string) Pipelines {
 	m := make(map[string]latest.Pipeline)
 	var pipelines []latest.Pipeline
 
-	for _, p := range pipelinesByConfig {
+	for _, cfgName := range orderedConfigs {
+		p := pipelinesByConfig[cfgName]
+
 		for _, a := range p.Build.Artifacts {
 			m[a.ImageName] = p
 		}
 		pipelines = append(pipelines, p)
 	}
 
-	return Pipelines{pipelines: pipelines, pipelinesByImageName: m, pipelinesByConfig: pipelinesByConfig}
+	return Pipelines{pipelines: pipelines, pipelinesByImageName: m, pipelinesByConfig: pipelinesByConfig, orderedConfigs: orderedConfigs}
 }
 
 func (rc *RunContext) PipelineForImage(imageName string) (latest.Pipeline, bool) {
@@ -323,11 +332,14 @@ func getConfigName(configName string) string {
 
 func GetRunContext(ctx context.Context, opts config.SkaffoldOptions, configs []schemaUtil.VersionedConfig) (*RunContext, error) {
 	pipelines := make(map[string]latest.Pipeline)
+	var orderedConfigs []string
+
 	for _, cfg := range configs {
 		if cfg != nil {
 			pipeline := cfg.(*latest.SkaffoldConfig).Pipeline
 			cfgName := getConfigName(cfg.(*latest.SkaffoldConfig).Metadata.Name)
 			pipelines[cfgName] = pipeline
+			orderedConfigs = append(orderedConfigs, cfgName)
 		}
 	}
 	kubeConfig, err := kubectx.CurrentConfig()
@@ -350,7 +362,8 @@ func GetRunContext(ctx context.Context, opts config.SkaffoldOptions, configs []s
 	}
 	var regList []string
 	regList = append(regList, opts.InsecureRegistries...)
-	for _, cfg := range pipelines {
+	for _, cfgName := range orderedConfigs {
+		cfg := pipelines[cfgName]
 		regList = append(regList, cfg.Build.InsecureRegistries...)
 	}
 	regList = append(regList, cfgRegistries...)
@@ -358,7 +371,7 @@ func GetRunContext(ctx context.Context, opts config.SkaffoldOptions, configs []s
 	for _, r := range regList {
 		insecureRegistries[r] = true
 	}
-	ps := NewPipelines(pipelines)
+	ps := NewPipelines(pipelines, orderedConfigs)
 
 	// TODO(https://github.com/GoogleContainerTools/skaffold/issues/3668):
 	// remove minikubeProfile from here and instead detect it by matching the


Test patch:
diff --git a/integration/deploy_test.go b/integration/deploy_test.go
index 6715de944..c3410dc92 100644
--- a/integration/deploy_test.go
+++ b/integration/deploy_test.go
@@ -19,6 +19,7 @@ package integration
 import (
 	"encoding/json"
 	"errors"
+	"fmt"
 	"io"
 	"os"
 	"path/filepath"
@@ -190,6 +191,62 @@ func TestDeployWithoutWorkspaces(t *testing.T) {
 	skaffold.Deploy("--build-artifacts", buildOutputFile, "--status-check=false").InDir(tmpDir.Root()).InNs(ns.Name).RunOrFail(t)
 }
 
+func TestDeployDependenciesOrder(t *testing.T) {
+	MarkIntegrationTest(t, CanRunWithoutGcp)
+
+	tests := []struct {
+		description         string
+		dir                 string
+		moduleToDeploy      string
+		expectedDeployOrder []string
+	}{
+		{
+			description: "Deploy order of entire project",
+			dir:         "testdata/multi-config-dependencies-order",
+			expectedDeployOrder: []string{
+				"module4",
+				"module3",
+				"module2",
+				"module1",
+			},
+		},
+		{
+			description:    "Deploy order for just one part of the project",
+			dir:            "testdata/multi-config-dependencies-order",
+			moduleToDeploy: "module2",
+			expectedDeployOrder: []string{
+				"module4",
+				"module3",
+				"module2",
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.description, func(t *testing.T) {
+			targetModule := []string{}
+			if test.moduleToDeploy != "" {
+				targetModule = []string{"--module", test.moduleToDeploy}
+			}
+
+			expectedFormatedDeployOrder := []string{}
+			for _, module := range test.expectedDeployOrder {
+				expectedFormatedDeployOrder = append(expectedFormatedDeployOrder, fmt.Sprintf(" - pod/%v created", module))
+			}
+			expectedFormatedDeployOrder = append([]string{"Starting deploy..."}, expectedFormatedDeployOrder...)
+			expectedFormatedDeployOrder = append(expectedFormatedDeployOrder, "Waiting for deployments to stabilize...")
+			expectedOutput := strings.Join(expectedFormatedDeployOrder, "\n")
+
+			ns, _ := SetupNamespace(t)
+			outputBytes := skaffold.Run(targetModule...).InDir(test.dir).InNs(ns.Name).RunOrFailOutput(t)
+			defer skaffold.Delete().InDir(test.dir).InNs(ns.Name).RunOrFail(t)
+
+			output := string(outputBytes)
+			testutil.CheckContains(t, expectedOutput, output)
+		})
+	}
+}
+
 // Copies a file or directory tree.  There are 2x3 cases:
 //   1. If _src_ is a file,
 //      1. and _dst_ exists and is a file then _src_ is copied into _dst_
diff --git a/integration/testdata/multi-config-dependencies-order/module1/Dockerfile b/integration/testdata/multi-config-dependencies-order/module1/Dockerfile
new file mode 100644
index 000000000..f5884c7f4
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/module1/Dockerfile
@@ -0,0 +1,13 @@
+FROM golang:1.18 as builder
+WORKDIR /code
+COPY main.go .
+# `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
+ARG SKAFFOLD_GO_GCFLAGS
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
+
+FROM alpine:3
+# Define GOTRACEBACK to mark this container as using the Go language runtime
+# for `skaffold debug` (https://skaffold.dev/docs/workflows/debug/).
+ENV GOTRACEBACK=single
+CMD ["./app"]
+COPY --from=builder /app .
diff --git a/integration/testdata/multi-config-dependencies-order/module1/k8s/k8s-pod.yaml b/integration/testdata/multi-config-dependencies-order/module1/k8s/k8s-pod.yaml
new file mode 100644
index 000000000..df37fb3d0
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/module1/k8s/k8s-pod.yaml
@@ -0,0 +1,8 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: module1
+spec:
+  containers:
+  - name: module1
+    image: multi-config-module1
diff --git a/integration/testdata/multi-config-dependencies-order/module1/main.go b/integration/testdata/multi-config-dependencies-order/module1/main.go
new file mode 100644
index 000000000..ef038c2a3
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/module1/main.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+	"fmt"
+	"runtime"
+	"time"
+)
+
+func main() {
+	for {
+		fmt.Printf("Hello module-1! Running on %s/%s\n", runtime.GOOS, runtime.GOARCH)
+
+		time.Sleep(time.Second * 1)
+	}
+}
diff --git a/integration/testdata/multi-config-dependencies-order/module1/skaffold.yaml b/integration/testdata/multi-config-dependencies-order/module1/skaffold.yaml
new file mode 100644
index 000000000..68b4f69c5
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/module1/skaffold.yaml
@@ -0,0 +1,15 @@
+apiVersion: skaffold/v3
+kind: Config
+metadata:
+  name: module1
+build:
+  artifacts:
+  - image: multi-config-module1
+    context: .
+manifests:
+  rawYaml:
+  - k8s/k8s-pod.yaml
+deploy:
+  kubectl: {}
+requires:
+  - path: ../module2
\ No newline at end of file
diff --git a/integration/testdata/multi-config-dependencies-order/module2/Dockerfile b/integration/testdata/multi-config-dependencies-order/module2/Dockerfile
new file mode 100644
index 000000000..f5884c7f4
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/module2/Dockerfile
@@ -0,0 +1,13 @@
+FROM golang:1.18 as builder
+WORKDIR /code
+COPY main.go .
+# `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
+ARG SKAFFOLD_GO_GCFLAGS
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
+
+FROM alpine:3
+# Define GOTRACEBACK to mark this container as using the Go language runtime
+# for `skaffold debug` (https://skaffold.dev/docs/workflows/debug/).
+ENV GOTRACEBACK=single
+CMD ["./app"]
+COPY --from=builder /app .
diff --git a/integration/testdata/multi-config-dependencies-order/module2/k8s/k8s-pod.yaml b/integration/testdata/multi-config-dependencies-order/module2/k8s/k8s-pod.yaml
new file mode 100644
index 000000000..49dce1f7c
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/module2/k8s/k8s-pod.yaml
@@ -0,0 +1,8 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: module2
+spec:
+  containers:
+  - name: module2
+    image: multi-config-module2
diff --git a/integration/testdata/multi-config-dependencies-order/module2/main.go b/integration/testdata/multi-config-dependencies-order/module2/main.go
new file mode 100644
index 000000000..d947ee8f6
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/module2/main.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+	"fmt"
+	"runtime"
+	"time"
+)
+
+func main() {
+	for {
+		fmt.Printf("Hello module-2! Running on %s/%s\n", runtime.GOOS, runtime.GOARCH)
+
+		time.Sleep(time.Second * 1)
+	}
+}
diff --git a/integration/testdata/multi-config-dependencies-order/module2/skaffold.yaml b/integration/testdata/multi-config-dependencies-order/module2/skaffold.yaml
new file mode 100644
index 000000000..3eeda0d15
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/module2/skaffold.yaml
@@ -0,0 +1,15 @@
+apiVersion: skaffold/v3
+kind: Config
+metadata:
+  name: module2
+build:
+  artifacts:
+  - image: multi-config-module2
+    context: .
+manifests:
+  rawYaml:
+  - k8s/k8s-pod.yaml
+deploy:
+  kubectl: {}
+requires:
+  - path: ../module3
\ No newline at end of file
diff --git a/integration/testdata/multi-config-dependencies-order/module3/Dockerfile b/integration/testdata/multi-config-dependencies-order/module3/Dockerfile
new file mode 100644
index 000000000..f5884c7f4
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/module3/Dockerfile
@@ -0,0 +1,13 @@
+FROM golang:1.18 as builder
+WORKDIR /code
+COPY main.go .
+# `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
+ARG SKAFFOLD_GO_GCFLAGS
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
+
+FROM alpine:3
+# Define GOTRACEBACK to mark this container as using the Go language runtime
+# for `skaffold debug` (https://skaffold.dev/docs/workflows/debug/).
+ENV GOTRACEBACK=single
+CMD ["./app"]
+COPY --from=builder /app .
diff --git a/integration/testdata/multi-config-dependencies-order/module3/k8s/k8s-pod.yaml b/integration/testdata/multi-config-dependencies-order/module3/k8s/k8s-pod.yaml
new file mode 100644
index 000000000..7d289f094
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/module3/k8s/k8s-pod.yaml
@@ -0,0 +1,8 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: module3
+spec:
+  containers:
+  - name: module1
+    image: multi-config-module3
diff --git a/integration/testdata/multi-config-dependencies-order/module3/main.go b/integration/testdata/multi-config-dependencies-order/module3/main.go
new file mode 100644
index 000000000..87fef12e3
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/module3/main.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+	"fmt"
+	"runtime"
+	"time"
+)
+
+func main() {
+	for {
+		fmt.Printf("Hello module-3! Running on %s/%s\n", runtime.GOOS, runtime.GOARCH)
+
+		time.Sleep(time.Second * 1)
+	}
+}
diff --git a/integration/testdata/multi-config-dependencies-order/module3/skaffold.yaml b/integration/testdata/multi-config-dependencies-order/module3/skaffold.yaml
new file mode 100644
index 000000000..2c5320f3b
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/module3/skaffold.yaml
@@ -0,0 +1,15 @@
+apiVersion: skaffold/v3
+kind: Config
+metadata:
+  name: module3
+build:
+  artifacts:
+  - image: multi-config-module3
+    context: .
+manifests:
+  rawYaml:
+  - k8s/k8s-pod.yaml
+deploy:
+  kubectl: {}
+requires:
+  - path: ../module4
\ No newline at end of file
diff --git a/integration/testdata/multi-config-dependencies-order/module4/Dockerfile b/integration/testdata/multi-config-dependencies-order/module4/Dockerfile
new file mode 100644
index 000000000..f5884c7f4
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/module4/Dockerfile
@@ -0,0 +1,13 @@
+FROM golang:1.18 as builder
+WORKDIR /code
+COPY main.go .
+# `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
+ARG SKAFFOLD_GO_GCFLAGS
+RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go
+
+FROM alpine:3
+# Define GOTRACEBACK to mark this container as using the Go language runtime
+# for `skaffold debug` (https://skaffold.dev/docs/workflows/debug/).
+ENV GOTRACEBACK=single
+CMD ["./app"]
+COPY --from=builder /app .
diff --git a/integration/testdata/multi-config-dependencies-order/module4/k8s/k8s-pod.yaml b/integration/testdata/multi-config-dependencies-order/module4/k8s/k8s-pod.yaml
new file mode 100644
index 000000000..d590459a8
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/module4/k8s/k8s-pod.yaml
@@ -0,0 +1,8 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: module4
+spec:
+  containers:
+  - name: module4
+    image: multi-config-module4
diff --git a/integration/testdata/multi-config-dependencies-order/module4/main.go b/integration/testdata/multi-config-dependencies-order/module4/main.go
new file mode 100644
index 000000000..262e5caa4
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/module4/main.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+	"fmt"
+	"runtime"
+	"time"
+)
+
+func main() {
+	for {
+		fmt.Printf("Hello module-4! Running on %s/%s\n", runtime.GOOS, runtime.GOARCH)
+
+		time.Sleep(time.Second * 1)
+	}
+}
diff --git a/integration/testdata/multi-config-dependencies-order/module4/skaffold.yaml b/integration/testdata/multi-config-dependencies-order/module4/skaffold.yaml
new file mode 100644
index 000000000..6eae228e0
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/module4/skaffold.yaml
@@ -0,0 +1,13 @@
+apiVersion: skaffold/v3
+kind: Config
+metadata:
+  name: module4
+build:
+  artifacts:
+  - image: multi-config-module4
+    context: .
+manifests:
+  rawYaml:
+  - k8s/k8s-pod.yaml
+deploy:
+  kubectl: {}
\ No newline at end of file
diff --git a/integration/testdata/multi-config-dependencies-order/skaffold.yaml b/integration/testdata/multi-config-dependencies-order/skaffold.yaml
new file mode 100644
index 000000000..67d75712f
--- /dev/null
+++ b/integration/testdata/multi-config-dependencies-order/skaffold.yaml
@@ -0,0 +1,4 @@
+apiVersion: skaffold/v3
+kind: Config
+requires:
+  - path: ./module1
\ No newline at end of file
diff --git a/pkg/skaffold/deploy/helm/helm_test.go b/pkg/skaffold/deploy/helm/helm_test.go
index ad6550f07..6842d42eb 100644
--- a/pkg/skaffold/deploy/helm/helm_test.go
+++ b/pkg/skaffold/deploy/helm/helm_test.go
@@ -1426,6 +1426,7 @@ func (c *helmConfig) ForceDeploy() bool                                   { retu
 func (c *helmConfig) GetKubeConfig() string                               { return kubectl.TestKubeConfig }
 func (c *helmConfig) GetKubeContext() string                              { return kubectl.TestKubeContext }
 func (c *helmConfig) GetKubeNamespace() string                            { return c.namespace }
+func (c *helmConfig) GetNamespace() string                                { return c.namespace }
 func (c *helmConfig) ConfigurationFile() string                           { return c.configFile }
 func (c *helmConfig) PortForwardResources() []*latest.PortForwardResource { return nil }
 
diff --git a/pkg/skaffold/deploy/kubectl/kubectl_test.go b/pkg/skaffold/deploy/kubectl/kubectl_test.go
index e789269fe..62ffa6a7c 100644
--- a/pkg/skaffold/deploy/kubectl/kubectl_test.go
+++ b/pkg/skaffold/deploy/kubectl/kubectl_test.go
@@ -231,7 +231,7 @@ func TestKubectlV1RenderDeploy(t *testing.T) {
 						configName: {
 							Render: rc,
 						},
-					}),
+					}, []string{configName}),
 				},
 			}
 
@@ -326,7 +326,7 @@ func TestKubectlCleanup(t *testing.T) {
 						configName: {
 							Render: rc,
 						},
-					}),
+					}, []string{configName}),
 				},
 			}
 
@@ -531,7 +531,7 @@ func TestGCSManifests(t *testing.T) {
 						configName: {
 							Render: latest.RenderConfig{Generate: test.generate},
 						},
-					}),
+					}, []string{configName}),
 				},
 			}
 			r, err := kubectlR.New(mockCfg, rc, map[string]string{}, configName, "")
diff --git a/pkg/skaffold/runner/deployer_test.go b/pkg/skaffold/runner/deployer_test.go
index 900e55694..882fa9871 100644
--- a/pkg/skaffold/runner/deployer_test.go
+++ b/pkg/skaffold/runner/deployer_test.go
@@ -99,7 +99,7 @@ func TestGetDeployer(tOuter *testing.T) {
 							map[string]latest.Pipeline{
 								"default": {},
 							},
-						),
+							[]string{"default"}),
 					}, &label.DefaultLabeller{}, &latest.KubectlDeploy{
 						Flags: latest.KubectlFlags{},
 					}, "default")).(deploy.Deployer),
@@ -143,7 +143,7 @@ func TestGetDeployer(tOuter *testing.T) {
 						map[string]latest.Pipeline{
 							"default": {},
 						},
-					),
+						[]string{"default"}),
 				}, &label.DefaultLabeller{}, &latest.KubectlDeploy{
 					Flags: latest.KubectlFlags{},
 				}, "default")).(deploy.Deployer),
@@ -164,7 +164,7 @@ func TestGetDeployer(tOuter *testing.T) {
 						map[string]latest.Pipeline{
 							"default": {},
 						},
-					),
+						[]string{"default"}),
 				}, &label.DefaultLabeller{}, &latest.KubectlDeploy{
 					Flags: latest.KubectlFlags{},
 				}, "default")).(deploy.Deployer),
@@ -258,7 +258,7 @@ func TestGetDeployer(tOuter *testing.T) {
 						map[string]latest.Pipeline{
 							"default": {},
 						},
-					),
+						[]string{"default"}),
 				}, &label.DefaultLabeller{}, &latest.KubectlDeploy{
 					Flags: latest.KubectlFlags{},
 				}, "default")).(deploy.Deployer),
@@ -307,7 +307,7 @@ func TestGetDeployer(tOuter *testing.T) {
 						map[string]latest.Pipeline{
 							"default": test.cfg,
 						},
-					),
+						[]string{"default"}),
 				}, &label.DefaultLabeller{}, "", false)
 
 				t.CheckError(test.shouldErr, err)
@@ -369,7 +369,7 @@ func TestGetDefaultDeployer(tOuter *testing.T) {
 						map[string]latest.Pipeline{
 							configNameForDefaultDeployer: {},
 						},
-					),
+						[]string{"default"}),
 				}, &label.DefaultLabeller{}, &latest.KubectlDeploy{
 					Flags: latest.KubectlFlags{},
 				}, configNameForDefaultDeployer)).(*kubectl.Deployer),
@@ -391,7 +391,7 @@ func TestGetDefaultDeployer(tOuter *testing.T) {
 						map[string]latest.Pipeline{
 							configNameForDefaultDeployer: {},
 						},
-					),
+						[]string{"default"}),
 				}, &label.DefaultLabeller{}, &latest.KubectlDeploy{
 					Flags: latest.KubectlFlags{
 						Apply:  []string{"--foo"},
@@ -429,7 +429,7 @@ func TestGetDefaultDeployer(tOuter *testing.T) {
 						map[string]latest.Pipeline{
 							configNameForDefaultDeployer: {},
 						},
-					),
+						[]string{"default"}),
 				}, &label.DefaultLabeller{}, &latest.KubectlDeploy{
 					Flags: latest.KubectlFlags{},
 				}, configNameForDefaultDeployer)).(*kubectl.Deployer),
@@ -439,6 +439,7 @@ func TestGetDefaultDeployer(tOuter *testing.T) {
 		for _, test := range tests {
 			testutil.Run(tOuter, test.name, func(t *testutil.T) {
 				pipelines := map[string]latest.Pipeline{}
+				var orderedConfigNames []string
 				for configName, cfg := range test.cfgs {
 					pipeline := latest.Pipeline{
 						Deploy: latest.DeployConfig{
@@ -446,9 +447,10 @@ func TestGetDefaultDeployer(tOuter *testing.T) {
 						},
 					}
 					pipelines[configName] = pipeline
+					orderedConfigNames = append(orderedConfigNames, configName)
 				}
 				deployer, err := getDefaultDeployer(&runcontext.RunContext{
-					Pipelines: runcontext.NewPipelines(pipelines),
+					Pipelines: runcontext.NewPipelines(pipelines, orderedConfigNames),
 				}, &label.DefaultLabeller{})
 
 				t.CheckErrorAndFailNow(test.shouldErr, err)
@@ -506,12 +508,14 @@ func TestGetCloudRunDeployer(tOuter *testing.T) {
 	for _, test := range tests {
 		testutil.Run(tOuter, test.name, func(t *testutil.T) {
 			pipelines := make(map[string]latest.Pipeline)
+			var orderedConfigNames []string
 			for name, config := range test.cfgs {
 				pipelines[name] = latest.Pipeline{Deploy: latest.DeployConfig{DeployType: config}}
+				orderedConfigNames = append(orderedConfigNames, name)
 			}
 			rctx := &runcontext.RunContext{
 				Opts:      test.opts,
-				Pipelines: runcontext.NewPipelines(pipelines),
+				Pipelines: runcontext.NewPipelines(pipelines, orderedConfigNames),
 			}
 			crDeployer, err := getCloudRunDeployer(rctx, &label.DefaultLabeller{}, rctx.DeployConfigs(), "")
 			t.CheckErrorAndFailNow(test.haveErr, err)
diff --git a/pkg/skaffold/runner/renderer_test.go b/pkg/skaffold/runner/renderer_test.go
index 22659329c..a54771a65 100644
--- a/pkg/skaffold/runner/renderer_test.go
+++ b/pkg/skaffold/runner/renderer_test.go
@@ -32,7 +32,7 @@ func TestGetRenderer(tOuter *testing.T) {
 		Pipelines: runcontext.NewPipelines(
 			map[string]latest.Pipeline{
 				"default": {},
-			})}
+			}, []string{"default"})}
 	labels := map[string]string{}
 	kubectlCfg := latest.RenderConfig{
 		Generate: latest.Generate{
@@ -148,7 +148,7 @@ func TestGetRenderer(tOuter *testing.T) {
 						map[string]latest.Pipeline{
 							"default": test.cfg,
 						},
-					),
+						[]string{"default"}),
 				}, "", map[string]string{}, false)
 
 				t.CheckError(test.shouldErr, err)
diff --git a/pkg/skaffold/runner/runner_test.go b/pkg/skaffold/runner/runner_test.go
index 08e5ce22b..3cc618be9 100644
--- a/pkg/skaffold/runner/runner_test.go
+++ b/pkg/skaffold/runner/runner_test.go
@@ -314,7 +314,7 @@ func createRunner(t *testutil.T, testBench *TestBench, monitor filemon.Monitor,
 			map[string]latest.Pipeline{
 				"default": cfg.Pipeline,
 			},
-		),
+			[]string{"default"}),
 		Opts: config.SkaffoldOptions{
 			Trigger:           "polling",
 			WatchPollInterval: 100,
@@ -549,7 +549,7 @@ func TestNewForConfig(t *testing.T) {
 					map[string]latest.Pipeline{
 						"default": tt.pipeline,
 					},
-				),
+					[]string{"default"}),
 				Opts: config.SkaffoldOptions{
 					Trigger: "polling",
 				},
@@ -660,7 +660,7 @@ func TestTriggerCallbackAndIntents(t *testing.T) {
 					map[string]latest.Pipeline{
 						"default": pipeline,
 					},
-				),
+					[]string{"default"}),
 				WorkingDir: tmpDir.Root(),
 			})
 
diff --git a/pkg/skaffold/schema/validation/validation_test.go b/pkg/skaffold/schema/validation/validation_test.go
index 05c694662..13aa2a3e8 100644
--- a/pkg/skaffold/schema/validation/validation_test.go
+++ b/pkg/skaffold/schema/validation/validation_test.go
@@ -768,7 +768,7 @@ func TestValidateNetworkModeDockerContainerExists(t *testing.T) {
 							},
 						},
 					},
-				),
+					[]string{"default"}),
 			})
 
 			t.CheckError(test.shouldErr, err)
@@ -1826,7 +1826,7 @@ func TestValidateCloudRunLocation(t *testing.T) {
 							Deploy: test.deploy,
 						},
 					},
-				),
+					[]string{"default"}),
 				Opts: config.SkaffoldOptions{
 					CloudRunProject:  test.cloudRunProject,
 					CloudRunLocation: test.cloudRunLocation,