Repository: GoogleContainerTools/skaffold
Pull request #7603 (2022-06-28 15:53:50)

chore: Remove render from deployer inteface
<!-- Thank you for your contribution! -->

<!-- Include if applicable: -->
Fixes: #7563  <!-- tracking issues that this PR will close -->
**Related**: _Relevant tracking issues, for context_
**Merge before/after**: _Dependent or prerequisite PRs_

**Description**
 - remove Render method from deployers implementations
 - refactor helm render unit tests to use Render method from helm renderer


**Follow-up Work (remove if N/A)**
 - #7595 
 - #7567 
 - #7597 


<!--
Please be sure your PR includes unit tests - we don't merge code that brings down test coverage! 
Integration tests are sometimes an appropriate substitute. 
-->


codecov[bot]: # [Codecov](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7603?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) Report
> Merging [#7603](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7603?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) (d88c25f) 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.96%`.
> The diff coverage is `54.24%`.

```diff
@@            Coverage Diff             @@
##             main    #7603      +/-   ##
==========================================
- Coverage   70.48%   66.52%   -3.97%     
==========================================
  Files         515      582      +67     
  Lines       23150    27844    +4694     
==========================================
+ Hits        16317    18522    +2205     
- Misses       5776     7963    +2187     
- Partials     1057     1359     +302     
```


| [Impacted Files](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7603?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/7603/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/7603/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvZGVwbG95Lmdv) | `50.00% <0.00%> (-3.85%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/test.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7603/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/skaffold.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7603/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/7603/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/7603/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% <20.00%> (-5.90%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/lsp.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7603/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/7603/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/7603/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvZml4Lmdv) | `55.93% <38.88%> (-20.54%)` | :arrow_down: |
| [cmd/skaffold/app/cmd/verify.go](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7603/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools#diff-Y21kL3NrYWZmb2xkL2FwcC9jbWQvdmVyaWZ5Lmdv) | `41.17% <41.17%> (ø)` | |
| ... and [319 more](https://codecov.io/gh/GoogleContainerTools/skaffold/pull/7603/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools) | |

:mega: Codecov can now indicate which changes are the most critical in Pull Requests. [Learn more](https://about.codecov.io/product/feature/runtime-insights/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleContainerTools)


Patch:
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index e62e1d466..48cd76559 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -27,7 +27,7 @@ jobs:
           persist-credentials: false
 
       - name: "Run analysis"
-        uses: ossf/scorecard-action@3e15ea8318eee9b333819ec77a36aca8d39df13e # v1.1.1
+        uses: ossf/scorecard-action@ce330fde6b1a5c9c75b417e7efc510b822a35564 # v1.1.2
         with:
           results_file: results.sarif
           results_format: sarif
diff --git a/pkg/skaffold/deploy/cloudrun/deploy.go b/pkg/skaffold/deploy/cloudrun/deploy.go
index edc2d1ed3..c820ce8af 100644
--- a/pkg/skaffold/deploy/cloudrun/deploy.go
+++ b/pkg/skaffold/deploy/cloudrun/deploy.go
@@ -97,12 +97,6 @@ func (d *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, mani
 	return d.deleteRunService(ctx, out, dryRun, manifests)
 }
 
-// Render writes out the k8s configs, we may want to support this with service configs in the future
-// but it's not being implemented now
-func (d *Deployer) Render(context.Context, io.Writer, []graph.Artifact, bool, string) error {
-	return nil
-}
-
 // GetDebugger Get the Debugger for Cloud Run. Not supported by this deployer.
 func (d *Deployer) GetDebugger() debug.Debugger {
 	return &debug.NoopDebugger{}
diff --git a/pkg/skaffold/deploy/docker/deploy.go b/pkg/skaffold/deploy/docker/deploy.go
index 307a66591..fbdbcdbbd 100644
--- a/pkg/skaffold/deploy/docker/deploy.go
+++ b/pkg/skaffold/deploy/docker/deploy.go
@@ -313,10 +313,6 @@ func (d *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, list
 	return errors.Wrap(err, "cleaning up skaffold created network")
 }
 
-func (d *Deployer) Render(context.Context, io.Writer, []graph.Artifact, bool, string) error {
-	return errors.New("render not implemented for docker deployer")
-}
-
 func (d *Deployer) GetAccessor() access.Accessor {
 	return d.portManager
 }
diff --git a/pkg/skaffold/deploy/helm/helm.go b/pkg/skaffold/deploy/helm/helm.go
index 2361c9788..d0d2a8f95 100644
--- a/pkg/skaffold/deploy/helm/helm.go
+++ b/pkg/skaffold/deploy/helm/helm.go
@@ -387,78 +387,6 @@ func (h *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, list
 	return nil
 }
 
-// Render generates the Kubernetes manifests and writes them out
-func (h *Deployer) Render(ctx context.Context, out io.Writer, builds []graph.Artifact, offline bool, filepath string) error {
-	instrumentation.AddAttributesToCurrentSpanFromContext(ctx, map[string]string{
-		"DeployerType": "helm",
-	})
-	renderedManifests := new(bytes.Buffer)
-	helmEnv := util.OSEnviron()
-	var postRendererArgs []string
-
-	if len(builds) > 0 {
-		skaffoldBinary, filterEnv, cleanup, err := helm.PrepareSkaffoldFilter(h, builds)
-		if err != nil {
-			return fmt.Errorf("could not prepare `skaffold filter`: %w", err)
-		}
-		// need to include current environment, specifically for HOME to lookup ~/.kube/config
-		helmEnv = append(helmEnv, filterEnv...)
-		postRendererArgs = []string{"--post-renderer", skaffoldBinary}
-		defer cleanup()
-	}
-
-	for _, r := range h.Releases {
-		releaseName, err := util.ExpandEnvTemplateOrFail(r.Name, nil)
-		if err != nil {
-			return helm.UserErr(fmt.Sprintf("cannot expand release name %q", r.Name), err)
-		}
-
-		args := []string{"template", releaseName, helm.ChartSource(r)}
-		args = append(args, postRendererArgs...)
-		if r.Packaged == nil && r.Version != "" {
-			args = append(args, "--version", r.Version)
-		}
-
-		args, err = helm.ConstructOverrideArgs(&r, builds, args)
-		if err != nil {
-			return helm.UserErr("construct override args", err)
-		}
-
-		namespace, err := helm.ReleaseNamespace(h.namespace, r)
-		if err != nil {
-			return err
-		}
-		if namespace != "" {
-			args = append(args, "--namespace", namespace)
-		}
-
-		if r.Repo != "" {
-			args = append(args, "--repo")
-			args = append(args, r.Repo)
-		}
-
-		outBuffer := new(bytes.Buffer)
-		if err := helm.Exec(ctx, h, outBuffer, false, helmEnv, args...); err != nil {
-			return helm.UserErr("std out err", fmt.Errorf(outBuffer.String()))
-		}
-		renderedManifests.Write(outBuffer.Bytes())
-	}
-	manifests, err := manifest.Load(bytes.NewReader(renderedManifests.Bytes()))
-	if err != nil {
-		return err
-	}
-
-	modifiedManifests, err := manifests.SetLabels(h.labels, manifest.NewResourceSelectorLabels(h.transformableAllowlist, h.transformableDenylist))
-	if err != nil {
-		return err
-	}
-	modifiedText := modifiedManifests.String()
-	if filepath == "" {
-		modifiedText += "\n"
-	}
-	return manifest.Write(modifiedText, filepath, out)
-}
-
 func (h *Deployer) HasRunnableHooks() bool {
 	return len(h.LegacyHelmDeploy.LifecycleHooks.PreHooks) > 0 || len(h.LegacyHelmDeploy.LifecycleHooks.PostHooks) > 0
 }
diff --git a/pkg/skaffold/deploy/kubectl/kubectl.go b/pkg/skaffold/deploy/kubectl/kubectl.go
index 3e0a79cbb..6a28bb5f5 100644
--- a/pkg/skaffold/deploy/kubectl/kubectl.go
+++ b/pkg/skaffold/deploy/kubectl/kubectl.go
@@ -33,7 +33,6 @@ import (
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/debug"
 	component "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/component/kubernetes"
-	deployerr "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/error"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/label"
 	deployutil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/util"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/event"
@@ -397,100 +396,6 @@ func (k *Deployer) readRemoteManifest(ctx context.Context, name string) ([]byte,
 	return manifest.Bytes(), nil
 }
 
-func (k *Deployer) Render(ctx context.Context, out io.Writer, builds []graph.Artifact, offline bool, filepath string) error {
-	instrumentation.AddAttributesToCurrentSpanFromContext(ctx, map[string]string{
-		"DeployerType": "kubectl",
-	})
-
-	childCtx, endTrace := instrumentation.StartTrace(ctx, "Render_renderManifests")
-	manifests, err := k.renderManifests(childCtx, out, builds, offline)
-	if err != nil {
-		endTrace(instrumentation.TraceEndError(err))
-		return err
-	}
-	k.statusMonitor.RegisterDeployManifests(manifests)
-	endTrace()
-
-	_, endTrace = instrumentation.StartTrace(ctx, "Render_manifest.Write")
-	defer endTrace()
-	return manifest.Write(manifests.String(), filepath, out)
-}
-
-// renderManifests transforms the manifests' images with the actual image sha1 built from skaffold build.
-func (k *Deployer) renderManifests(ctx context.Context, out io.Writer, builds []graph.Artifact, offline bool) (manifest.ManifestList, error) {
-	if err := k.kubectl.CheckVersion(ctx); err != nil {
-		output.Default.Fprintln(out, "kubectl client version:", k.kubectl.Version(ctx))
-		output.Default.Fprintln(out, err)
-	}
-
-	debugHelpersRegistry, err := config.GetDebugHelpersRegistry(k.globalConfig)
-	if err != nil {
-		return nil, deployerr.DebugHelperRetrieveErr(fmt.Errorf("retrieving debug helpers registry: %w", err))
-	}
-	var localManifests, remoteManifests manifest.ManifestList
-	localManifests, err = k.readManifests(ctx, offline)
-	if err != nil {
-		return nil, err
-	}
-
-	for _, m := range k.RemoteManifests {
-		manifest, err := k.readRemoteManifest(ctx, m)
-		if err != nil {
-			return nil, err
-		}
-
-		remoteManifests = append(remoteManifests, manifest)
-	}
-
-	originalManifests := append(localManifests, remoteManifests...)
-
-	if len(k.originalImages) == 0 {
-		// TODO(aaron-prindle) maybe use different resoureselector?
-		k.originalImages, err = originalManifests.GetImages(manifest.NewResourceSelectorImages(k.transformableAllowlist, k.transformableDenylist))
-		// k.originalImages, err = originalManifests.GetImages(k.transformableAllowlist, k.transformableDenylist)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	if len(originalManifests) == 0 {
-		return nil, nil
-	}
-
-	if len(builds) == 0 {
-		for _, artifact := range k.originalImages {
-			tag, err := deployutil.ApplyDefaultRepo(k.globalConfig, k.defaultRepo, artifact.Tag)
-			if err != nil {
-				return nil, err
-			}
-			builds = append(builds, graph.Artifact{
-				ImageName: artifact.ImageName,
-				Tag:       tag,
-			})
-		}
-	}
-	if len(remoteManifests) > 0 {
-		remoteManifests, err = remoteManifests.ReplaceRemoteManifestImages(ctx, builds, manifest.NewResourceSelectorImages(k.transformableAllowlist, k.transformableDenylist))
-		if err != nil {
-			return nil, err
-		}
-	}
-	if len(localManifests) > 0 {
-		localManifests, err = localManifests.ReplaceImages(ctx, builds, manifest.NewResourceSelectorImages(k.transformableAllowlist, k.transformableDenylist))
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	modifiedManifests := append(localManifests, remoteManifests...)
-
-	if modifiedManifests, err = manifest.ApplyTransforms(modifiedManifests, builds, k.insecureRegistries, debugHelpersRegistry); err != nil {
-		return nil, err
-	}
-
-	return modifiedManifests.SetLabels(k.labeller.Labels(), manifest.NewResourceSelectorLabels(k.transformableAllowlist, k.transformableDenylist))
-}
-
 // Cleanup deletes what was deployed by calling Deploy.
 func (k *Deployer) Cleanup(ctx context.Context, out io.Writer, dryRun bool, manifests manifest.ManifestList) error {
 	instrumentation.AddAttributesToCurrentSpanFromContext(ctx, map[string]string{
diff --git a/pkg/skaffold/deploy/kustomize/kustomize.go b/pkg/skaffold/deploy/kustomize/kustomize.go
index 71d22434e..91f05e029 100644
--- a/pkg/skaffold/deploy/kustomize/kustomize.go
+++ b/pkg/skaffold/deploy/kustomize/kustomize.go
@@ -400,24 +400,6 @@ func (k *Deployer) Dependencies() ([]string, error) {
 	return deps.ToList(), nil
 }
 
-func (k *Deployer) Render(ctx context.Context, out io.Writer, builds []graph.Artifact, offline bool, filepath string) error {
-	instrumentation.AddAttributesToCurrentSpanFromContext(ctx, map[string]string{
-		"DeployerType": "kustomize",
-	})
-
-	childCtx, endTrace := instrumentation.StartTrace(ctx, "Render_renderManifests")
-	manifests, err := k.renderManifests(childCtx, out, builds)
-	if err != nil {
-		endTrace(instrumentation.TraceEndError(err))
-		return err
-	}
-	k.statusMonitor.RegisterDeployManifests(manifests)
-
-	_, endTrace = instrumentation.StartTrace(ctx, "Render_manifest.Write")
-	defer endTrace()
-	return manifest.Write(manifests.String(), filepath, out)
-}
-
 // Values of `patchesStrategicMerge` can be either:
 // + a file path, referenced as a plain string
 // + an inline patch referenced as a string literal
diff --git a/pkg/skaffold/kubernetes/manifest/gcs.go b/pkg/skaffold/kubernetes/manifest/gcs.go
index 3962cc594..a964ee5c2 100644
--- a/pkg/skaffold/kubernetes/manifest/gcs.go
+++ b/pkg/skaffold/kubernetes/manifest/gcs.go
@@ -20,15 +20,19 @@ import (
 	"context"
 	"fmt"
 	"os"
+	"path/filepath"
 	"strings"
 
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
 )
 
+var ManifestsFromGCS = "manifests_from_gcs"
+
 // DownloadFromGCS downloads all provided manifests from a remote GCS bucket,
 // and returns a relative path pointing to the GCS temp dir.
 func DownloadFromGCS(manifests []string) (string, error) {
-	if err := os.MkdirAll(ManifestTmpDir, os.ModePerm); err != nil {
+	dir := filepath.Join(ManifestTmpDir, ManifestsFromGCS)
+	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
 		return "", fmt.Errorf("failed to create the tmp directory: %w", err)
 	}
 	for _, manifest := range manifests {
@@ -36,7 +40,7 @@ func DownloadFromGCS(manifests []string) (string, error) {
 			return "", fmt.Errorf("%v is not a valid GCS path", manifest)
 		}
 		gcs := util.Gsutil{}
-		if err := gcs.Copy(context.Background(), manifest, ManifestTmpDir, true); err != nil {
+		if err := gcs.Copy(context.Background(), manifest, dir, true); err != nil {
 			return "", fmt.Errorf("failed to download manifests fom GCS: %w", err)
 		}
 	}
diff --git a/pkg/skaffold/kubernetes/manifest/url.go b/pkg/skaffold/kubernetes/manifest/url.go
new file mode 100644
index 000000000..798da1893
--- /dev/null
+++ b/pkg/skaffold/kubernetes/manifest/url.go
@@ -0,0 +1,72 @@
+/*
+Copyright 2022 The Skaffold Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package manifest
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"path/filepath"
+
+	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
+)
+
+var ManifestsFromURL = "manifests_from_url"
+
+// DownloadFromURL downloads the provided manifests from their URLs,
+// and returns a slice containing downloaded file destinations.
+func DownloadFromURL(manifests []string) ([]string, error) {
+	dir := filepath.Join(ManifestTmpDir, ManifestsFromURL)
+	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
+		return nil, fmt.Errorf("failed to create the tmp directory: %w", err)
+	}
+	var paths []string
+	for _, manifest := range manifests {
+		out, err := downloadFromURL(dir, manifest)
+
+		if err != nil {
+			return nil, err
+		}
+		paths = append(paths, out)
+	}
+	return paths, nil
+}
+
+func downloadFromURL(destDir string, manifest string) (string, error) {
+	if manifest == "" || !util.IsURL(manifest) {
+		return "", fmt.Errorf("%s is not a valid URL", manifest)
+	}
+
+	resp, err := http.Get(manifest)
+	if err != nil {
+		return "", fmt.Errorf("failed to download manifest from %s, err : %w", manifest, err)
+	}
+	defer resp.Body.Close()
+
+	f, err := os.CreateTemp(destDir, "*.yaml")
+	if err != nil {
+		return "", fmt.Errorf("failed to create manifest file: %w", err)
+	}
+	defer f.Close()
+	_, err = io.Copy(f, resp.Body)
+	if err != nil {
+		return "", fmt.Errorf("failed to write manifest to file, err: %w", err)
+	}
+
+	return f.Name(), nil
+}
diff --git a/pkg/skaffold/render/generate/generate.go b/pkg/skaffold/render/generate/generate.go
index 838286431..6c93dd05a 100644
--- a/pkg/skaffold/render/generate/generate.go
+++ b/pkg/skaffold/render/generate/generate.go
@@ -57,9 +57,11 @@ type Generator struct {
 func resolveRemoteAndLocal(paths []string, workdir string) ([]string, error) {
 	var localPaths []string
 	var gcsManifests []string
+	var urlManifests []string
 	for _, path := range paths {
 		switch {
 		case util.IsURL(path):
+			urlManifests = append(urlManifests, path)
 		case strings.HasPrefix(path, "gs://"):
 			gcsManifests = append(gcsManifests, path)
 		default:
@@ -82,6 +84,13 @@ func resolveRemoteAndLocal(paths []string, workdir string) ([]string, error) {
 		}
 		list = append(list, l...)
 	}
+	if len(urlManifests) != 0 {
+		paths, err := manifest.DownloadFromURL(urlManifests)
+		if err != nil {
+			return nil, err
+		}
+		list = append(list, paths...)
+	}
 	return list, nil
 }
 


Test patch:
diff --git a/pkg/skaffold/deploy/deploy_mux_test.go b/pkg/skaffold/deploy/deploy_mux_test.go
index ce8de0d1a..94dec9d39 100644
--- a/pkg/skaffold/deploy/deploy_mux_test.go
+++ b/pkg/skaffold/deploy/deploy_mux_test.go
@@ -42,8 +42,6 @@ type MockDeployer struct {
 	dependencies    []string
 	dependenciesErr error
 	cleanupErr      error
-	renderResult    string
-	renderErr       error
 }
 
 func (m *MockDeployer) HasRunnableHooks() bool {
@@ -110,30 +108,15 @@ func (m *MockDeployer) WithCleanupErr(err error) *MockDeployer {
 	return m
 }
 
-func (m *MockDeployer) WithRenderErr(err error) *MockDeployer {
-	m.renderErr = err
-	return m
-}
-
 func (m *MockDeployer) Deploy(context.Context, io.Writer, []graph.Artifact, manifest.ManifestList) error {
 	return m.deployErr
 }
 
-func (m *MockDeployer) Render(_ context.Context, w io.Writer, _ []graph.Artifact, _ bool, _ string) error {
-	w.Write([]byte(m.renderResult))
-	return m.renderErr
-}
-
 func (m *MockDeployer) WithDependencies(dependencies []string) *MockDeployer {
 	m.dependencies = dependencies
 	return m
 }
 
-func (m *MockDeployer) WithRenderResult(renderResult string) *MockDeployer {
-	m.renderResult = renderResult
-	return m
-}
-
 func TestDeployerMux_Deploy(t *testing.T) {
 	tests := []struct {
 		name        string
diff --git a/pkg/skaffold/deploy/helm/helm_test.go b/pkg/skaffold/deploy/helm/helm_test.go
index d57afc088..e086754a2 100644
--- a/pkg/skaffold/deploy/helm/helm_test.go
+++ b/pkg/skaffold/deploy/helm/helm_test.go
@@ -39,6 +39,7 @@ import (
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/client"
 	kubectx "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/context"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/logger"
+	rhelm "github.com/GoogleContainerTools/skaffold/pkg/skaffold/render/renderer/helm"
 	runcontext "github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext/v2"
 	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
 	schemautil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util"
@@ -1224,8 +1225,6 @@ func TestHelmRender(t *testing.T) {
 		commands    util.Command
 		helm        latest.LegacyHelmDeploy
 		env         []string
-		outputFile  string
-		expected    string
 		builds      []graph.Artifact
 		namespace   string
 	}{
@@ -1233,8 +1232,7 @@ func TestHelmRender(t *testing.T) {
 			description: "normal render v3",
 			shouldErr:   false,
 			commands: testutil.
-				CmdRunWithOutput("helm version --client", version31).
-				AndRun("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set some.key=somevalue --kubeconfig kubeconfig"),
+				CmdRun("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set some.key=somevalue --kubeconfig kubeconfig"),
 			helm: testDeployConfig,
 			builds: []graph.Artifact{
 				{
@@ -1242,28 +1240,11 @@ func TestHelmRender(t *testing.T) {
 					Tag:       "skaffold-helm:tag1",
 				}},
 		},
-		{
-			description: "render to a file",
-			shouldErr:   false,
-			commands: testutil.
-				CmdRunWithOutput("helm version --client", version31).
-				AndRunWithOutput("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set some.key=somevalue --kubeconfig kubeconfig",
-					"Dummy Output"),
-			helm:       testDeployConfig,
-			outputFile: "dummy.yaml",
-			expected:   "Dummy Output\n",
-			builds: []graph.Artifact{
-				{
-					ImageName: "skaffold-helm",
-					Tag:       "skaffold-helm:tag1",
-				}},
-		},
 		{
 			description: "render with templated config",
 			shouldErr:   false,
 			commands: testutil.
-				CmdRunWithOutput("helm version --client", version31).
-				AndRun("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set image.name=skaffold-helm --set image.tag=skaffold-helm:tag1 --set missing.key=<MISSING> --set other.key=FOOBAR --set some.key=somevalue --set FOOBAR=somevalue --kubeconfig kubeconfig"),
+				CmdRun("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set image.name=skaffold-helm --set image.tag=skaffold-helm:tag1 --set missing.key=<MISSING> --set other.key=FOOBAR --set some.key=somevalue --set FOOBAR=somevalue --kubeconfig kubeconfig"),
 			helm: testDeployConfigTemplated,
 			builds: []graph.Artifact{
 				{
@@ -1274,10 +1255,8 @@ func TestHelmRender(t *testing.T) {
 		{
 			description: "render with templated values file",
 			shouldErr:   false,
-			commands: testutil.
-				CmdRunWithOutput("helm version --client", version31).
-				AndRun("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY -f /some/file-FOOBAR.yaml --kubeconfig kubeconfig"),
-			helm: testDeployConfigValuesFilesTemplated,
+			commands:    testutil.CmdRun("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY -f /some/file-FOOBAR.yaml --kubeconfig kubeconfig"),
+			helm:        testDeployConfigValuesFilesTemplated,
 			builds: []graph.Artifact{
 				{
 					ImageName: "skaffold-helm",
@@ -1287,7 +1266,6 @@ func TestHelmRender(t *testing.T) {
 		{
 			description: "render with missing templated release name should fail",
 			shouldErr:   true,
-			commands:    testutil.CmdRunWithOutput("helm version --client", version31),
 			helm:        testDeployWithTemplatedName,
 			builds:      testBuilds,
 		},
@@ -1295,17 +1273,15 @@ func TestHelmRender(t *testing.T) {
 			description: "render with templated release name",
 			env:         []string{"USER=user"},
 			commands: testutil.
-				CmdRunWithOutput("helm version --client", version31).
-				AndRun("helm --kube-context kubecontext template user-skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set some.key=somevalue --kubeconfig kubeconfig"),
+				CmdRun("helm --kube-context kubecontext template user-skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set some.key=somevalue --kubeconfig kubeconfig"),
 			helm:   testDeployWithTemplatedName,
 			builds: testBuilds,
 		},
 		{
 			description: "render with namespace",
 			shouldErr:   false,
-			commands: testutil.CmdRunWithOutput("helm version --client", version31).
-				AndRun("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set some.key=somevalue --namespace testReleaseNamespace --kubeconfig kubeconfig"),
-			helm: testDeployNamespacedConfig,
+			commands:    testutil.CmdRun("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set some.key=somevalue --namespace testReleaseNamespace --kubeconfig kubeconfig"),
+			helm:        testDeployNamespacedConfig,
 			builds: []graph.Artifact{
 				{
 					ImageName: "skaffold-helm",
@@ -1315,9 +1291,8 @@ func TestHelmRender(t *testing.T) {
 		{
 			description: "render with namespace",
 			shouldErr:   false,
-			commands: testutil.CmdRunWithOutput("helm version --client", version31).
-				AndRun("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set some.key=somevalue --namespace testReleaseFOOBARNamespace --kubeconfig kubeconfig"),
-			helm: testDeployEnvTemplateNamespacedConfig,
+			commands:    testutil.CmdRun("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set some.key=somevalue --namespace testReleaseFOOBARNamespace --kubeconfig kubeconfig"),
+			helm:        testDeployEnvTemplateNamespacedConfig,
 			builds: []graph.Artifact{
 				{
 					ImageName: "skaffold-helm",
@@ -1327,9 +1302,8 @@ func TestHelmRender(t *testing.T) {
 		{
 			description: "render with remote repo",
 			shouldErr:   false,
-			commands: testutil.CmdRunWithOutput("helm version --client", version31).
-				AndRun("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set some.key=somevalue --repo https://charts.helm.sh/stable --kubeconfig kubeconfig"),
-			helm: testDeployConfigRemoteRepo,
+			commands:    testutil.CmdRun("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set some.key=somevalue --repo https://charts.helm.sh/stable --kubeconfig kubeconfig"),
+			helm:        testDeployConfigRemoteRepo,
 			builds: []graph.Artifact{
 				{
 					ImageName: "skaffold-helm",
@@ -1339,67 +1313,56 @@ func TestHelmRender(t *testing.T) {
 		{
 			description: "render with remote chart",
 			shouldErr:   false,
-			commands: testutil.CmdRunWithOutput("helm version --client", version31).
-				AndRun("helm --kube-context kubecontext template skaffold-helm-remote stable/chartmuseum --repo https://charts.helm.sh/stable --kubeconfig kubeconfig"),
-			helm: testDeployRemoteChart,
+			commands:    testutil.CmdRun("helm --kube-context kubecontext template skaffold-helm-remote stable/chartmuseum --repo https://charts.helm.sh/stable --kubeconfig kubeconfig"),
+			helm:        testDeployRemoteChart,
 		},
 		{
 			description: "render with remote chart with version",
 			shouldErr:   false,
-			commands: testutil.CmdRunWithOutput("helm version --client", version31).
-				AndRun("helm --kube-context kubecontext template skaffold-helm-remote stable/chartmuseum --version 1.0.0 --repo https://charts.helm.sh/stable --kubeconfig kubeconfig"),
-			helm: testDeployRemoteChartVersion,
-		},
-		{
-			description: "render with cli namespace",
-			shouldErr:   false,
-			namespace:   "clinamespace",
-			commands: testutil.
-				CmdRunWithOutput("helm version --client", version31).
-				AndRun("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set some.key=somevalue --namespace clinamespace --kubeconfig kubeconfig"),
-			helm: testDeployConfig,
-			builds: []graph.Artifact{
-				{
-					ImageName: "skaffold-helm",
-					Tag:       "skaffold-helm:tag1",
-				}},
-		},
-		{
-			description: "render with HelmRelease.Namespace and cli namespace",
-			shouldErr:   false,
-			namespace:   "clinamespace",
-			commands: testutil.
-				CmdRunWithOutput("helm version --client", version31).
-				AndRun("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set some.key=somevalue --namespace clinamespace --kubeconfig kubeconfig"),
-			helm: testDeployNamespacedConfig,
-			builds: []graph.Artifact{
-				{
-					ImageName: "skaffold-helm",
-					Tag:       "skaffold-helm:tag1",
-				}},
-		},
+			commands:    testutil.CmdRun("helm --kube-context kubecontext template skaffold-helm-remote stable/chartmuseum --version 1.0.0 --repo https://charts.helm.sh/stable --kubeconfig kubeconfig"),
+			helm:        testDeployRemoteChartVersion,
+		},
+		// https://github.com/GoogleContainerTools/skaffold/issues/7595
+		// {
+		//	description: "render with cli namespace",
+		//	shouldErr:   false,
+		//	namespace:   "clinamespace",
+		//	commands:    testutil.CmdRun("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set some.key=somevalue --namespace clinamespace --kubeconfig kubeconfig"),
+		//	helm:        testDeployConfig,
+		//	builds: []graph.Artifact{
+		//		{
+		//			ImageName: "skaffold-helm",
+		//			Tag:       "skaffold-helm:tag1",
+		//		}},
+		// },
+		// {
+		//	description: "render with HelmRelease.Namespace and cli namespace",
+		//	shouldErr:   false,
+		//	namespace:   "clinamespace",
+		//	commands:    testutil.CmdRun("helm --kube-context kubecontext template skaffold-helm examples/test --post-renderer SKAFFOLD-BINARY --set some.key=somevalue --namespace clinamespace --kubeconfig kubeconfig"),
+		//	helm:        testDeployNamespacedConfig,
+		//	builds: []graph.Artifact{
+		//		{
+		//			ImageName: "skaffold-helm",
+		//			Tag:       "skaffold-helm:tag1",
+		//		}},
+		// },
 	}
+	labeller := label.DefaultLabeller{}
+	labels := labeller.Labels()
 	for _, test := range tests {
 		testutil.Run(t, test.description, func(t *testutil.T) {
-			file := ""
-			if test.outputFile != "" {
-				file = t.NewTempDir().Path(test.outputFile)
-			}
-
 			t.Override(&util.OSEnviron, func() []string { return append([]string{"FOO=FOOBAR"}, test.env...) })
 			t.Override(&helm.OSExecutable, func() (string, error) { return "SKAFFOLD-BINARY", nil })
 			t.Override(&util.DefaultExecCommand, test.commands)
-			deployer, err := NewDeployer(context.Background(), &helmConfig{
+			helmRenderer, err := rhelm.New(&helmConfig{
 				namespace: test.namespace,
-			}, &label.DefaultLabeller{}, &test.helm, nil)
+			}, latest.RenderConfig{
+				Generate: latest.Generate{Helm: &latest.Helm{Flags: test.helm.Flags, Releases: test.helm.Releases}},
+			}, labels)
 			t.RequireNoError(err)
-			err = deployer.Render(context.Background(), ioutil.Discard, test.builds, true, file)
+			_, err = helmRenderer.Render(context.Background(), ioutil.Discard, test.builds, true)
 			t.CheckError(test.shouldErr, err)
-
-			if file != "" {
-				dat, _ := ioutil.ReadFile(file)
-				t.CheckDeepEqual(test.expected, string(dat))
-			}
 		})
 	}
 }
diff --git a/pkg/skaffold/deploy/kubectl/kubectl_test.go b/pkg/skaffold/deploy/kubectl/kubectl_test.go
index d624e67cc..7d2f352c0 100644
--- a/pkg/skaffold/deploy/kubectl/kubectl_test.go
+++ b/pkg/skaffold/deploy/kubectl/kubectl_test.go
@@ -157,20 +157,6 @@ func TestKubectlV1RenderDeploy(t *testing.T) {
 				"MYENV": "Namesp",
 			},
 		},
-		{
-			description: "http manifest",
-			generate: latest.Generate{
-				RawK8s: []string{"deployment.yaml", "http://remote.yaml"},
-			},
-			commands: testutil.
-				CmdRunOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", "").
-				AndRun("kubectl --context kubecontext --namespace testNamespace apply -f -"),
-			builds: []graph.Artifact{{
-				ImageName: "leeroy-web",
-				Tag:       "leeroy-web:v1",
-			}},
-			waitForDeletions: true,
-		},
 		{
 			description: "deploy command error",
 			generate: latest.Generate{
@@ -609,7 +595,7 @@ func TestGCSManifests(t *testing.T) {
 				RawK8s: []string{"gs://dev/deployment.yaml"},
 			},
 			commands: testutil.
-				CmdRunOut(fmt.Sprintf("gsutil cp -r %s %s", "gs://dev/deployment.yaml", manifest.ManifestTmpDir), "log").
+				CmdRunOut(fmt.Sprintf("gsutil cp -r %s %s", "gs://dev/deployment.yaml", filepath.Join(manifest.ManifestTmpDir, manifest.ManifestsFromGCS)), "log").
 				AndRun("kubectl --context kubecontext --namespace testNamespace apply -f -"),
 		}}
 	for _, test := range tests {
diff --git a/pkg/skaffold/deploy/kustomize/kustomize_test.go b/pkg/skaffold/deploy/kustomize/kustomize_test.go
index 614a54644..14922b985 100644
--- a/pkg/skaffold/deploy/kustomize/kustomize_test.go
+++ b/pkg/skaffold/deploy/kustomize/kustomize_test.go
@@ -17,7 +17,6 @@ limitations under the License.
 package kustomize
 
 import (
-	"bytes"
 	"context"
 	"errors"
 	"io"
@@ -619,190 +618,6 @@ func TestKustomizeBuildCommandArgs(t *testing.T) {
 	}
 }
 
-func TestKustomizeRender(t *testing.T) {
-	type kustomizationCall struct {
-		folder      string
-		buildResult string
-	}
-	tests := []struct {
-		description    string
-		builds         []graph.Artifact
-		labels         []string
-		kustomizations []kustomizationCall
-		expected       string
-		shouldErr      bool
-	}{
-		{
-			description: "single kustomization",
-			builds: []graph.Artifact{
-				{
-					ImageName: "gcr.io/project/image1",
-					Tag:       "gcr.io/project/image1:tag1",
-				},
-				{
-					ImageName: "gcr.io/project/image2",
-					Tag:       "gcr.io/project/image2:tag2",
-				},
-			},
-			kustomizations: []kustomizationCall{
-				{
-					folder: ".",
-					buildResult: `apiVersion: v1
-kind: Pod
-metadata:
-  namespace: default
-spec:
-  containers:
-  - image: gcr.io/project/image1
-    name: image1
-  - image: gcr.io/project/image2
-    name: image2
-`,
-				},
-			},
-			expected: `apiVersion: v1
-kind: Pod
-metadata:
-  namespace: default
-spec:
-  containers:
-  - image: gcr.io/project/image1:tag1
-    name: image1
-  - image: gcr.io/project/image2:tag2
-    name: image2
-`,
-		},
-		{
-			description: "single kustomization with user labels",
-			builds: []graph.Artifact{
-				{
-					ImageName: "gcr.io/project/image1",
-					Tag:       "gcr.io/project/image1:tag1",
-				},
-				{
-					ImageName: "gcr.io/project/image2",
-					Tag:       "gcr.io/project/image2:tag2",
-				},
-			},
-			labels: []string{"user/label=test"},
-			kustomizations: []kustomizationCall{
-				{
-					folder: ".",
-					buildResult: `apiVersion: v1
-kind: Pod
-metadata:
-  namespace: default
-spec:
-  containers:
-  - image: gcr.io/project/image1
-    name: image1
-  - image: gcr.io/project/image2
-    name: image2
-`,
-				},
-			},
-			expected: `apiVersion: v1
-kind: Pod
-metadata:
-  labels:
-    user/label: test
-  namespace: default
-spec:
-  containers:
-  - image: gcr.io/project/image1:tag1
-    name: image1
-  - image: gcr.io/project/image2:tag2
-    name: image2
-`,
-		},
-		{
-			description: "multiple kustomizations",
-			builds: []graph.Artifact{
-				{
-					ImageName: "gcr.io/project/image1",
-					Tag:       "gcr.io/project/image1:tag1",
-				},
-				{
-					ImageName: "gcr.io/project/image2",
-					Tag:       "gcr.io/project/image2:tag2",
-				},
-			},
-			kustomizations: []kustomizationCall{
-				{
-					folder: "a",
-					buildResult: `apiVersion: v1
-kind: Pod
-metadata:
-  namespace: default
-spec:
-  containers:
-  - image: gcr.io/project/image1
-    name: image1
-`,
-				},
-				{
-					folder: "b",
-					buildResult: `apiVersion: v1
-kind: Pod
-metadata:
-  namespace: default
-spec:
-  containers:
-  - image: gcr.io/project/image2
-    name: image2
-`,
-				},
-			},
-			expected: `apiVersion: v1
-kind: Pod
-metadata:
-  namespace: default
-spec:
-  containers:
-  - image: gcr.io/project/image1:tag1
-    name: image1
----
-apiVersion: v1
-kind: Pod
-metadata:
-  namespace: default
-spec:
-  containers:
-  - image: gcr.io/project/image2:tag2
-    name: image2
-`,
-		},
-	}
-	for _, test := range tests {
-		testutil.Run(t, test.description, func(t *testutil.T) {
-			var kustomizationPaths []string
-			fakeCmd := testutil.
-				CmdRunOut("kubectl version --client -ojson", kubectl.KubectlVersion112)
-			for _, kustomizationCall := range test.kustomizations {
-				fakeCmd.AndRunOut("kustomize build "+kustomizationCall.folder, kustomizationCall.buildResult)
-				kustomizationPaths = append(kustomizationPaths, kustomizationCall.folder)
-			}
-			t.Override(&util.DefaultExecCommand, fakeCmd)
-			t.NewTempDir().Chdir()
-
-			labeller := label.NewLabeller(false, test.labels, "")
-
-			k, err := NewDeployer(&kustomizeConfig{
-				workingDir: ".",
-				RunContext: runcontext.RunContext{Opts: config.SkaffoldOptions{Namespace: kubectl.TestNamespace}},
-			}, labeller, &latest.KustomizeDeploy{
-				KustomizePaths: kustomizationPaths,
-			})
-			t.RequireNoError(err)
-
-			var b bytes.Buffer
-			err = k.Render(context.Background(), &b, test.builds, true, "")
-			t.CheckError(test.shouldErr, err)
-			t.CheckDeepEqual(test.expected, b.String())
-		})
-	}
-}
-
 func TestHasRunnableHooks(t *testing.T) {
 	tests := []struct {
 		description string
diff --git a/pkg/skaffold/render/generate/generate_test.go b/pkg/skaffold/render/generate/generate_test.go
index bd89bc4cf..f15e39b2a 100644
--- a/pkg/skaffold/render/generate/generate_test.go
+++ b/pkg/skaffold/render/generate/generate_test.go
@@ -18,6 +18,9 @@ package generate
 import (
 	"bytes"
 	"context"
+	"fmt"
+	"net/http"
+	"net/http/httptest"
 	"os"
 	"path/filepath"
 	"testing"
@@ -178,6 +181,25 @@ func TestGenerate(t *testing.T) {
 	}
 }
 
+func TestGenerateFromURLManifest(t *testing.T) {
+	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprintln(w, podYaml)
+	}))
+
+	defer ts.Close()
+	defer os.RemoveAll(manifest.ManifestTmpDir)
+	g := NewGenerator(".", latest.Generate{
+		RawK8s: []string{ts.URL},
+	})
+	var output bytes.Buffer
+	actual, err := g.Generate(context.Background(), &output)
+	testutil.Run(t, "", func(t *testutil.T) {
+		t.CheckNoError(err)
+		manifestList := manifest.ManifestList{[]byte(podYaml)}
+		t.CheckDeepEqual(actual.String(), manifestList.String())
+	})
+}
+
 func TestManifestDeps(t *testing.T) {
 	tests := []struct {
 		description    string