Repository: grafana/tanka
Pull request #883 (2023-06-19 09:12:27)

Bump k8s.io/apimachinery from 0.27.2 to 0.27.3
Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.27.2 to 0.27.3.
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/kubernetes/apimachinery/commit/38152d47d7866fcd1c1287f34cdc2f92fd78a5f2"><code>38152d4</code></a> Merge pull request <a href="https://redirect.github.com/kubernetes/apimachinery/issues/118553">#118553</a> from puerco/bump-1.27-go1.20.5</li>
<li><a href="https://github.com/kubernetes/apimachinery/commit/2bd872f9613b7fe77e000706a4875ca13afd4a75"><code>2bd872f</code></a> update-vendor: update vendored go.sums</li>
<li>See full diff in <a href="https://github.com/kubernetes/apimachinery/compare/v0.27.2...v0.27.3">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=k8s.io/apimachinery&package-manager=go_modules&previous-version=0.27.2&new-version=0.27.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

github-actions[bot]: [PR Preview Action](https://github.com/rossjrw/pr-preview-action) v1.4.4
:---:
:rocket: Deployed preview to https://grafana.github.io/tanka/pr-preview/pr-883/
on branch [`gh-pages`](https://github.com/grafana/tanka/tree/gh-pages) at 2023-06-19 09:16 UTC
<!-- Sticky Pull Request Commentpr-preview -->

Patch:
diff --git a/cmd/tk/tool.go b/cmd/tk/tool.go
index 1e15e8d7..9ed56e65 100644
--- a/cmd/tk/tool.go
+++ b/cmd/tk/tool.go
@@ -137,9 +137,10 @@ func importsCmd() *cli.Command {
 
 func importersCmd() *cli.Command {
 	cmd := &cli.Command{
-		Use:   "importers <file> <file...>",
+		Use:   "importers <file> <file...> <deleted:file...>",
 		Short: "list all environments that either directly or transitively import the given files",
 		Long: `list all environments that either directly or transitively import the given files
+If the file being looked up was deleted, it should be prefixed with "deleted:".
 
 As optimization,
 if the file is not a vendored (located at <tk-root>/vendor/) or a lib file (located at <tk-root>/lib/), we assume:
@@ -162,6 +163,9 @@ if the file is not a vendored (located at <tk-root>/vendor/) or a lib file (loca
 		}
 
 		for _, f := range args {
+			if strings.HasPrefix(f, "deleted:") {
+				continue
+			}
 			if _, err := os.Stat(f); os.IsNotExist(err) {
 				return fmt.Errorf("file %q does not exist", f)
 			}
diff --git a/go.mod b/go.mod
index 38c980e2..6a4fdecd 100644
--- a/go.mod
+++ b/go.mod
@@ -23,7 +23,7 @@ require (
 	golang.org/x/text v0.9.0
 	gopkg.in/yaml.v2 v2.4.0
 	gopkg.in/yaml.v3 v3.0.1
-	k8s.io/apimachinery v0.27.2
+	k8s.io/apimachinery v0.27.3
 	k8s.io/utils v0.0.0-20230209194617-a36077c30491
 	sigs.k8s.io/yaml v1.3.0
 )
diff --git a/go.sum b/go.sum
index 873aa2c5..d7160b89 100644
--- a/go.sum
+++ b/go.sum
@@ -133,8 +133,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg=
-k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=
+k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM=
+k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E=
 k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
 k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
 k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY=
diff --git a/pkg/jsonnet/find_importers.go b/pkg/jsonnet/find_importers.go
index 0ca17101..14e41d96 100644
--- a/pkg/jsonnet/find_importers.go
+++ b/pkg/jsonnet/find_importers.go
@@ -1,6 +1,7 @@
 package jsonnet
 
 import (
+	"fmt"
 	"os"
 	"path/filepath"
 	"sort"
@@ -34,12 +35,33 @@ func FindImporterForFiles(root string, files []string) ([]string, error) {
 
 	importers := map[string]struct{}{}
 
-	if files, err = expandSymlinksInFiles(root, files); err != nil {
+	// Handle files prefixed with `deleted:`. They need to be made absolute and we shouldn't try to find symlinks for them
+	var filesToCheck, existingFiles []string
+	for _, file := range files {
+		if strings.HasPrefix(file, "deleted:") {
+			deletedFile := strings.TrimPrefix(file, "deleted:")
+			// Try with both the absolute path and the path relative to the root
+			if !filepath.IsAbs(deletedFile) {
+				absFilePath, err := filepath.Abs(deletedFile)
+				if err != nil {
+					return nil, err
+				}
+				filesToCheck = append(filesToCheck, absFilePath)
+				filesToCheck = append(filesToCheck, filepath.Clean(filepath.Join(root, deletedFile)))
+			}
+			continue
+		}
+
+		existingFiles = append(existingFiles, file)
+	}
+
+	if existingFiles, err = expandSymlinksInFiles(root, existingFiles); err != nil {
 		return nil, err
 	}
+	filesToCheck = append(filesToCheck, existingFiles...)
 
 	// Loop through all given files and add their importers to the list
-	for _, file := range files {
+	for _, file := range filesToCheck {
 		if filepath.Base(file) == jpath.DefaultEntrypoint {
 			importers[file] = struct{}{}
 		}
@@ -171,15 +193,14 @@ func findImporters(root string, searchForFile string, chain map[string]struct{})
 	searchedFileIsLibOrVendored := isFileLibOrVendored(searchForFile)
 	if !searchedFileIsLibOrVendored {
 		searchedDir := filepath.Dir(searchForFile)
-		searchedFileEntrypoint, err := jpath.Entrypoint(searchedDir)
-		if err == nil && searchedFileEntrypoint != "" {
+		if entrypoint := findEntrypoint(searchedDir); entrypoint != "" {
 			// Found the main file for the searched file, add it as an importer
-			importers = append(importers, searchedFileEntrypoint)
-		} else {
+			importers = append(importers, entrypoint)
+		} else if _, err := os.Stat(searchedDir); err == nil {
 			// No main file found, add all main files in child dirs as importers
 			files, err := FindFiles(searchedDir, nil)
 			if err != nil {
-				return nil, err
+				return nil, fmt.Errorf("failed to find files in %s: %w", searchedDir, err)
 			}
 			for _, file := range files {
 				if filepath.Base(file) == jpath.DefaultEntrypoint {
@@ -245,9 +266,8 @@ func findImporters(root string, searchForFile string, chain map[string]struct{})
 			if isImporter {
 				if jsonnetFileContent.IsMainFile {
 					importers = append(importers, jsonnetFilePath)
-				} else {
-					intermediateImporters = append(intermediateImporters, jsonnetFilePath)
 				}
+				intermediateImporters = append(intermediateImporters, jsonnetFilePath)
 				break
 			}
 		}
@@ -317,6 +337,21 @@ func createJsonnetFileCache(root string) (map[string]*cachedJsonnetFile, error)
 	return jsonnetFilesCache[root], nil
 }
 
+// findEntrypoint finds the nearest main.jsonnet file in the given file's directory or parent directories
+func findEntrypoint(searchedDir string) string {
+	for {
+		if _, err := os.Stat(searchedDir); err == nil {
+			break
+		}
+		searchedDir = filepath.Dir(searchedDir)
+	}
+	searchedFileEntrypoint, err := jpath.Entrypoint(searchedDir)
+	if err != nil {
+		return ""
+	}
+	return searchedFileEntrypoint
+}
+
 func pathMatches(path1, path2 string) bool {
 	if path1 == path2 {
 		return true


Test patch:
diff --git a/pkg/jsonnet/find_importers_test.go b/pkg/jsonnet/find_importers_test.go
index 4ba636ad..8f376505 100644
--- a/pkg/jsonnet/find_importers_test.go
+++ b/pkg/jsonnet/find_importers_test.go
@@ -3,6 +3,7 @@ package jsonnet
 import (
 	"fmt"
 	"path/filepath"
+	"strings"
 	"testing"
 
 	"github.com/grafana/tanka/pkg/jsonnet/jpath"
@@ -143,10 +144,74 @@ func findImportersTestCases(t testing.TB) []findImportersTestCase {
 				absPath(t, "testdata/findImporters/environments/lib-import-relative-to-env/folder1/folder2/main.jsonnet"),
 			},
 		},
+		{
+			name: "unused deleted file",
+			files: []string{
+				"deleted:testdata/findImporters/vendor/deleted-vendor/main.libsonnet",
+			},
+			expectedImporters: nil,
+		},
+		{
+			name: "deleted local path that is still potentially imported",
+			files: []string{
+				"deleted:testdata/findImporters/environments/using-deleted-stuff/my-import-dir/main.libsonnet",
+			},
+			expectedImporters: []string{
+				absPath(t, "testdata/findImporters/environments/using-deleted-stuff/main.jsonnet"),
+			},
+		},
+		{
+			name: "deleted lib that is still potentially imported",
+			files: []string{
+				"deleted:testdata/findImporters/lib/my-import-dir/main.libsonnet",
+			},
+			expectedImporters: []string{
+				absPath(t, "testdata/findImporters/environments/using-deleted-stuff/main.jsonnet"),
+			},
+		},
+		{
+			name: "deleted vendor that is still potentially imported",
+			files: []string{
+				"deleted:testdata/findImporters/vendor/my-import-dir/main.libsonnet",
+			},
+			expectedImporters: []string{
+				absPath(t, "testdata/findImporters/environments/using-deleted-stuff/main.jsonnet"),
+			},
+		},
+		{
+			name: "deleted lib that is still potentially imported, relative path from root",
+			files: []string{
+				"deleted:lib/my-import-dir/main.libsonnet",
+			},
+			expectedImporters: []string{
+				absPath(t, "testdata/findImporters/environments/using-deleted-stuff/main.jsonnet"),
+			},
+		},
+		{
+			// All files in an environment are considered to be imported by the main file, so the same should apply for deleted files
+			name: "deleted dir in environment",
+			files: []string{
+				"deleted:testdata/findImporters/environments/no-imports/deleted-dir/deleted-file.libsonnet",
+			},
+			expectedImporters: []string{
+				absPath(t, "testdata/findImporters/environments/no-imports/main.jsonnet"),
+			},
+		},
+		{
+			name: "imports through a main file are followed",
+			files: []string{
+				"testdata/findImporters/environments/import-other-main-file/env2/file.libsonnet",
+			},
+			expectedImporters: []string{
+				absPath(t, "testdata/findImporters/environments/import-other-main-file/env1/main.jsonnet"),
+				absPath(t, "testdata/findImporters/environments/import-other-main-file/env2/main.jsonnet"),
+			},
+		},
 	}
 }
 
 func TestFindImportersForFiles(t *testing.T) {
+	// Sanity check
 	// Make sure the main files all eval correctly
 	// We want to make sure that the importers command works correctly,
 	// but there's no point in testing on invalid jsonnet files
@@ -154,6 +219,11 @@ func TestFindImportersForFiles(t *testing.T) {
 	require.NoError(t, err)
 	require.NotEmpty(t, files)
 	for _, file := range files {
+		// This project is known to be invalid (as the name suggests)
+		if strings.Contains(file, "using-deleted-stuff") {
+			continue
+		}
+
 		// Skip non-main files
 		if filepath.Base(file) != jpath.DefaultEntrypoint {
 			continue
diff --git a/pkg/jsonnet/testdata/findImporters/environments/import-other-main-file/env1/main.jsonnet b/pkg/jsonnet/testdata/findImporters/environments/import-other-main-file/env1/main.jsonnet
new file mode 100644
index 00000000..ad4c2df9
--- /dev/null
+++ b/pkg/jsonnet/testdata/findImporters/environments/import-other-main-file/env1/main.jsonnet
@@ -0,0 +1,3 @@
+{
+  otherMain: import '../env2/main.jsonnet',
+}
diff --git a/pkg/jsonnet/testdata/findImporters/environments/import-other-main-file/env2/file.libsonnet b/pkg/jsonnet/testdata/findImporters/environments/import-other-main-file/env2/file.libsonnet
new file mode 100644
index 00000000..0967ef42
--- /dev/null
+++ b/pkg/jsonnet/testdata/findImporters/environments/import-other-main-file/env2/file.libsonnet
@@ -0,0 +1,1 @@
+{}
diff --git a/pkg/jsonnet/testdata/findImporters/environments/import-other-main-file/env2/main.jsonnet b/pkg/jsonnet/testdata/findImporters/environments/import-other-main-file/env2/main.jsonnet
new file mode 100644
index 00000000..d5f00fa5
--- /dev/null
+++ b/pkg/jsonnet/testdata/findImporters/environments/import-other-main-file/env2/main.jsonnet
@@ -0,0 +1,3 @@
+{
+  myfile: import 'file.libsonnet',
+}
diff --git a/pkg/jsonnet/testdata/findImporters/environments/using-deleted-stuff/main.jsonnet b/pkg/jsonnet/testdata/findImporters/environments/using-deleted-stuff/main.jsonnet
new file mode 100644
index 00000000..96567dc3
--- /dev/null
+++ b/pkg/jsonnet/testdata/findImporters/environments/using-deleted-stuff/main.jsonnet
@@ -0,0 +1,3 @@
+{
+  myimport: import 'my-import-dir/main.libsonnet',
+}