Repository: kubernetes-sigs/krew
Pull request #517 (2020-02-27 20:23:03)

site: Advanced website navigation
> Temporary staging at `http://staging.krew.dev/`

- added responsive collapsible navbar (+requires bootstrap.js, jquery)
- added a homepage (previously was docs ToC)
- added ToC pages for sub-sections (e.g. /docs/user-guide)
- add 404 page
- limit the content to 840px to avoid long lines on large screens
- move logo to /static/img

Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>

Related issue: #509 #514 

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/krew/pull/517#" title="Author self-approved">ahmetb</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fkrew).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/krew/blob/master/OWNERS)~~ [ahmetb]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/internal/index/indexscanner/scanner.go b/internal/index/indexscanner/scanner.go
index e8cec59..58684e4 100644
--- a/internal/index/indexscanner/scanner.go
+++ b/internal/index/indexscanner/scanner.go
@@ -86,31 +86,47 @@ func LoadPluginByName(pluginsDir, pluginName string) (index.Plugin, error) {
 // ReadPluginFromFile loads a file from the FS. When plugin file not found, it
 // returns an error that can be checked with os.IsNotExist.
 func ReadPluginFromFile(path string) (index.Plugin, error) {
-	f, err := os.Open(path)
-	if os.IsNotExist(err) {
-		// TODO(ahmetb): we should use go1.13+ errors.Is construct at call sites to evaluate if an error is os.IsNotExist
-		return index.Plugin{}, err
-	} else if err != nil {
-		return index.Plugin{}, errors.Wrap(err, "failed to open index file")
+	var plugin index.Plugin
+	err := readFromFile(path, &plugin)
+	if err != nil {
+		return plugin, err
 	}
-	return ReadPlugin(f)
+	return plugin, errors.Wrap(validation.ValidatePlugin(plugin.Name, plugin), "plugin manifest validation error")
 }
 
 func ReadPlugin(f io.ReadCloser) (index.Plugin, error) {
-	defer f.Close()
-	p, err := DecodePluginFile(f)
+	var plugin index.Plugin
+	err := decodeFile(f, &plugin)
 	if err != nil {
-		return p, errors.Wrap(err, "failed to decode plugin manifest")
+		return plugin, errors.Wrap(err, "failed to decode plugin manifest")
 	}
-	return p, errors.Wrap(validation.ValidatePlugin(p.Name, p), "plugin manifest validation error")
+	return plugin, errors.Wrap(validation.ValidatePlugin(plugin.Name, plugin), "plugin manifest validation error")
 }
 
-// DecodePluginFile tries to decodes a plugin manifest from r.
-func DecodePluginFile(r io.Reader) (index.Plugin, error) {
-	var plugin index.Plugin
+// ReadReceiptFromFile loads a file from the FS. When receipt file not found, it
+// returns an error that can be checked with os.IsNotExist.
+func ReadReceiptFromFile(path string) (index.Receipt, error) {
+	var receipt index.Receipt
+	err := readFromFile(path, &receipt)
+	return receipt, err
+}
+
+// readFromFile loads a file from the FS into the provided object.
+func readFromFile(path string, as interface{}) error {
+	f, err := os.Open(path)
+	if err != nil {
+		return err
+	}
+	err = decodeFile(f, &as)
+	return errors.Wrap(err, "failed to parse yaml file")
+}
+
+// decodeFile tries to decode a plugin/receipt
+func decodeFile(r io.ReadCloser, as interface{}) error {
+	defer r.Close()
 	b, err := ioutil.ReadAll(r)
 	if err != nil {
-		return plugin, err
+		return err
 	}
 
 	// TODO(ahmetb): when we have a stable API that won't add new fields,
@@ -118,6 +134,5 @@ func DecodePluginFile(r io.Reader) (index.Plugin, error) {
 	// incremental field additions to plugin manifests independently from the
 	// installed version of krew.
 	// yaml.UnmarshalStrict()
-	err = yaml.Unmarshal(b, &plugin)
-	return plugin, err
+	return yaml.Unmarshal(b, &as)
 }
diff --git a/internal/installation/receipt/receipt.go b/internal/installation/receipt/receipt.go
index e4495cf..6ebd5a3 100644
--- a/internal/installation/receipt/receipt.go
+++ b/internal/installation/receipt/receipt.go
@@ -24,7 +24,7 @@ import (
 	"sigs.k8s.io/krew/pkg/index"
 )
 
-// Store saves the given plugin at the destination.
+// Store saves the given plugin receipt at the destination.
 // The caller has to ensure that the destination directory exists.
 func Store(plugin index.Plugin, dest string) error {
 	yamlBytes, err := yaml.Marshal(plugin)
@@ -38,6 +38,6 @@ func Store(plugin index.Plugin, dest string) error {
 
 // Load reads the plugin receipt at the specified destination.
 // If not found, it returns os.IsNotExist error.
-func Load(path string) (index.Plugin, error) {
-	return indexscanner.ReadPluginFromFile(path)
+func Load(path string) (index.Receipt, error) {
+	return indexscanner.ReadReceiptFromFile(path)
 }
diff --git a/pkg/index/types.go b/pkg/index/types.go
index b326c17..791d946 100644
--- a/pkg/index/types.go
+++ b/pkg/index/types.go
@@ -58,3 +58,8 @@ type FileOperation struct {
 	From string `json:"from,omitempty"`
 	To   string `json:"to,omitempty"`
 }
+
+// Receipt describes a plugin receipt file.
+type Receipt struct {
+	Plugin `json:",inline" yaml:",inline"`
+}
diff --git a/site/config.yaml b/site/config.yaml
index 4e31865..369c843 100644
--- a/site/config.yaml
+++ b/site/config.yaml
@@ -1,7 +1,9 @@
+title: "Krew – kubectl plugin manager"
 baseURL: "https://krew.sigs.k8s.io/"
 languageCode: "en-us"
-title: "Krew – kubectl plugin manager"
-
+disableKinds:
+- taxonomy
+- taxonomyTerm
 markup:
-    highlight:
-      style: dracula
+  highlight:
+    style: dracula
diff --git a/site/content/_index.md b/site/content/_index.md
new file mode 100644
index 0000000..cd4305e
--- /dev/null
+++ b/site/content/_index.md
@@ -0,0 +1,22 @@
+---
+title: Home
+---
+
+## Krew: plugin manager for kubectl
+
+Krew helps you discover [kubectl plugins][kpl], install and manage them on your
+machine.
+
+It is similar to package managers like apt, brew or dnf. Krew works acrosss all
+major platforms and [over 80 kubectl][list] plugins are currently available on
+Krew.
+
+- For **kubectl users**: Krew helps you discover and install kubectl plugins,
+  and keep them up to date.
+
+- For **kubectl plugin developers**: Krew helps you package and distribute your
+  plugins on multiple platforms easily and makes them discoverable through a
+  centralized plugin repository.
+
+[kpl]: https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/
+[list]: http://sigs.k8s.io/krew-index/plugins.md
diff --git a/site/content/docs/_index.md b/site/content/docs/_index.md
index e6329a4..aea34a8 100644
--- a/site/content/docs/_index.md
+++ b/site/content/docs/_index.md
@@ -1,3 +1,4 @@
 ---
-title: Krew
+title: Documentation
+slug: docs
 ---
diff --git a/site/content/docs/developer-guide/_index.md b/site/content/docs/developer-guide/_index.md
index 8496b64..92bbd48 100644
--- a/site/content/docs/developer-guide/_index.md
+++ b/site/content/docs/developer-guide/_index.md
@@ -2,3 +2,6 @@
 title: Developer Guide
 weight: 300
 ---
+
+This guide is for `kubectl` plugin developers looking to distribute their
+plugins with Krew.
diff --git a/site/content/docs/developer-guide/release/submitting-to-krew.md b/site/content/docs/developer-guide/release/submitting-to-krew.md
index a49880b..6f30267 100644
--- a/site/content/docs/developer-guide/release/submitting-to-krew.md
+++ b/site/content/docs/developer-guide/release/submitting-to-krew.md
@@ -13,7 +13,7 @@ existing plugins.
 1. Familiarize yourself with [Krew plugin naming guide]({{< ref "../develop/naming-guide.md" >}}).
 1. Read the [plugin development best practices]({{< ref
    "../develop/best-practices.md" >}}).
-1. Develop a [plugin manifest](../plugin-manifest.md).
+1. Develop a [plugin manifest]({{< ref "../plugin-manifest.md" >}})
 1. Make sure your plugin’s source code is available as open source.
 1. Adopt an open source license, and add it to your plugin archive file.
 1. Tag a git release with a [semantic
diff --git a/site/content/docs/user-guide/_index.md b/site/content/docs/user-guide/_index.md
index 6a4bff3..abfe1b2 100644
--- a/site/content/docs/user-guide/_index.md
+++ b/site/content/docs/user-guide/_index.md
@@ -2,3 +2,5 @@
 title: User Guide
 weight: 200
 ---
+
+Learn how to use Krew to its full extent.
diff --git a/site/content/docs/user-guide/quickstart.md b/site/content/docs/user-guide/quickstart.md
index 38e8bda..4c3f571 100644
--- a/site/content/docs/user-guide/quickstart.md
+++ b/site/content/docs/user-guide/quickstart.md
@@ -5,12 +5,14 @@ weight: 100
 ---
 
 Krew helps you discover and install [`kubectl` plugins][kpl] on your machine.
+
 There are [a lot of][list] `kubectl` plugins you can install and use to enhance
 your Kubernetes experience.
 
-Let's get started.
+Let's get started:
+
+1. [Install and set up]({{<ref "setup/install.md">}}) Krew on your machine.
 
-1. [Install and set up]({{<ref "setup/install.md">}}) Krew.
 1. Download the plugin list:
 
     ```sh
@@ -21,16 +23,14 @@ Let's get started.
 
     ```sh
     {{<prompt>}}kubectl krew search
-    {{<output>}}
-NAME                            DESCRIPTION                                         INSTALLED
+    {{<output>}}NAME                            DESCRIPTION                                         INSTALLED
 access-matrix                   Show an RBAC access matrix for server resources     no
 advise-psp                      Suggests PodSecurityPolicies for cluster.           no
 auth-proxy                      Authentication proxy to a pod or service            no
-bulk-action                     Do bulk actions on Kubernetes resources.            no
-ca-cert                         Print the PEM CA certificate of the current clu...  no{{</output>}}
+[...]{{</output>}}
     ```
 
-1. Install a plugin:
+1. Choose a plugin from the list and install it:
 
     ```sh
     {{<prompt>}}kubectl krew install access-matrix
diff --git a/site/content/docs/user-guide/search.md b/site/content/docs/user-guide/search.md
index cc42745..4416894 100644
--- a/site/content/docs/user-guide/search.md
+++ b/site/content/docs/user-guide/search.md
@@ -26,12 +26,9 @@ advise-psp       Suggests PodSecurityPolicies for cluster.           no
 auth-proxy       Authentication proxy to a pod or service            no
 bulk-action      Do bulk actions on Kubernetes resources.            no
 ca-cert          Print the PEM CA certificate of the current clu...  no
-capture          Triggers a Sysdig capture to troubleshoot the r...  no
-change-ns        View or change the current namespace via kubectl.   no
-cluster-group    Exec commands across a group of contexts.           no
-config-cleanup   Automatically clean up your kubeconfig              no
 ...{{</output>}}
 ```
+
 You can specify search keywords as arguments:
 
 ```sh
diff --git a/site/layouts/404.html b/site/layouts/404.html
new file mode 100644
index 0000000..1c1204a
--- /dev/null
+++ b/site/layouts/404.html
@@ -0,0 +1,11 @@
+{{ partial "header.html" . }}
+
+<h1 class="display-3 text-danger">Not found</h1>
+
+<p class="lead">
+    Sorry, this URL cannot be found. Maybe it’s moved.
+</p>
+
+<a href=" {{ "/" }}  " class="btn btn-lg btn-primary">&larr; Home</a>
+
+{{ partial "footer.html" }}
diff --git a/site/layouts/docs/section.html b/site/layouts/docs/section.html
new file mode 100644
index 0000000..d7326ce
--- /dev/null
+++ b/site/layouts/docs/section.html
@@ -0,0 +1,22 @@
+{{ $isDocsHome := (eq .Slug "docs") }}
+{{ partial "header.html" . }}
+<article>
+    <h1>
+        {{ if $isDocsHome }}
+            Documentation
+        {{else}}
+            {{ .Title}}
+        {{end}}
+    </h1>
+
+    {{ with .Content }}
+        <aside class="lead">{{.}}</aside>
+    {{ end }}
+
+    {{ partial "toc.html" . }}
+
+    {{ if not $isDocsHome }}
+        {{ partial "backbutton.html" .}}
+    {{ end }}
+</article>
+{{ partial "footer.html" }}
diff --git a/site/layouts/docs/single.html b/site/layouts/docs/single.html
index 5fdc9e1..9c4a5aa 100644
--- a/site/layouts/docs/single.html
+++ b/site/layouts/docs/single.html
@@ -1,5 +1,5 @@
 {{ partial "header.html" . }}
-    <article>
+    <article class="py-lg-3">
         <h1>{{.Title}}</h1>
         {{ with .Content }}
             {{.}}
@@ -8,8 +8,5 @@
         {{ end }}
     </article>
 
-    <p>
-        <a href="{{ "/" | relURL }}" rel="home">
-        &larr; Table of Contents</a>
-    </p>
+{{ partial "backbutton.html" .}}
 {{ partial "footer.html" }}
diff --git a/site/layouts/index.html b/site/layouts/index.html
index 19fab26..0342073 100644
--- a/site/layouts/index.html
+++ b/site/layouts/index.html
@@ -1,7 +1,14 @@
 {{ partial "header.html" . }}
 
+<article>
+    {{.Content}}
 
-    <h1 class="display-4">Documentation</h1>
-    {{ partial "toc.html" (.Site.GetPage "section" "docs") }}
+    <a href="{{ "/docs/user-guide/quickstart" | relURL }} " class="btn btn-primary text-nowrap">Quickstart</a>
+
+    <a href="{{ "/docs/user-guide" | relURL }} " class="btn btn-light text-nowrap">User Guide</a>
+
+    <a href="{{ "/docs/developer-guide" | relURL }} " class="btn btn-light text-nowrap">Developer Guide</a>
+
+</article>
 
 {{ partial "footer.html" }}
diff --git a/site/layouts/partials/backbutton.html b/site/layouts/partials/backbutton.html
new file mode 100644
index 0000000..3466805
--- /dev/null
+++ b/site/layouts/partials/backbutton.html
@@ -0,0 +1,21 @@
+{{ $docsHome := "/docs" }}
+{{ $userGuide := "/docs/user-guide" }}
+{{ $devGuide := "/docs/developer-guide" }}
+
+{{ $isUserGuide := .Parent.IsDescendant (.Site.GetPage "section" $userGuide) }}
+{{ $isDevGuide := .Parent.IsDescendant (.Site.GetPage "section" $devGuide) }}
+
+{{ $parent := "" }}
+{{ if $isUserGuide }}
+    {{ $parent = $userGuide }}
+{{ else if $isDevGuide }}
+    {{ $parent = $devGuide }}
+{{ else }}
+    {{ $parent = $docsHome }}
+{{ end }}
+
+{{ $parent := .Site.GetPage "section" $parent }}
+
+<a href="{{  $parent.RelPermalink}}" class="btn btn-link btn-sm rounded">
+    &larr; {{ $parent.Title }}
+</a>
diff --git a/site/layouts/partials/footer.html b/site/layouts/partials/footer.html
index bc560fb..ee2a635 100644
--- a/site/layouts/partials/footer.html
+++ b/site/layouts/partials/footer.html
@@ -4,9 +4,11 @@
             <small>
                 &copy; {{ now.Format "2006"}} The Kubernetes Authors.
                 Krew documentation is distributed under
-                <a href='https://creativecommons.org/licenses/by/4.0/' class='text-muted'>CC-BY-4.0</a>.
+                <a target="_blank" href='https://creativecommons.org/licenses/by/4.0/' class='text-muted'>CC-BY-4.0</a>.<br/>
+                Krew is a Kubernetes <a href="https://github.com/kubernetes/community/blob/master/sig-cli/README.md#cli-special-interest-group" target="_blank">SIG CLI</a> project.
             </small>
         </footer>
-    </div>
+        </div>
+    </div><!-- .container -->
 </body>
 </html>
diff --git a/site/layouts/partials/header.html b/site/layouts/partials/header.html
index 1ddaad6..496b54f 100644
--- a/site/layouts/partials/header.html
+++ b/site/layouts/partials/header.html
@@ -3,13 +3,28 @@
 <head>
     <meta charset="utf-8">
     <meta http-equiv="content-type" content="text/html; charset=utf-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0, max-scale=1.0, min-scale=1.0">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 
-    <title>{{.Title}}</title>
+    <title>
+        {{ if .IsHome }}
+            {{ .Site.Title }}
+        {{ else }}
+            {{ .Title }} &#183 Krew
+        {{ end }}
+
+    </title>
     <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
     <link rel="stylesheet" href="{{"/css/style.css" | relURL}}" />
+    <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
+    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
 </head>
 
 <body>
-    <div class="container shadow-lg col-xl-6 rounded py-md-4 px-md-5 my-md-5">
-        <a href="{{"/" | relURL }}"><img src="{{ "/krew-logo.svg" }}" height="100vw"/></a>
+
+    <div class="container shadow-lg rounded
+        col-12 col-lg-8 col-xl-6
+        my-0 my-md-5
+        p-0">
+        {{ partial "navbar.html" . }}
+
+        <div class="py-md-4 pl-2 px-md-5">
diff --git a/site/layouts/partials/navbar.html b/site/layouts/partials/navbar.html
new file mode 100644
index 0000000..63327cd
--- /dev/null
+++ b/site/layouts/partials/navbar.html
@@ -0,0 +1,29 @@
+<nav class="navbar navbar-expand-md navbar-light bg-light">
+    <a class="navbar-brand" href="{{"/" | relURL }}">
+        <img src="{{ "/img/krew-logo.svg" }}" height="40vw"/>
+    </a>
+
+    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
+        <span class="navbar-toggler-icon"></span>
+    </button>
+    <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
+        <div class="navbar-nav">
+          <a class="nav-item nav-link {{if .IsHome}}active{{end}}" href="{{"/"|relURL}}">
+            Home
+            {{if .IsHome}}<span class="sr-only">(current)</span>{{end}}
+        </a>
+
+        {{ $isQuickstart := (eq .Slug "quickstart") }}
+        {{ $isDocs := (and (eq .Section "docs") (ne .Slug "quickstart")) }}
+
+        <a class="nav-item nav-link {{if $isQuickstart}}active{{end}}" href="{{ "/docs/user-guide/quickstart/" | relURL}}">
+            Quickstart
+            {{if $isQuickstart}}<span class="sr-only">(current)</span>{{end}}
+        </a>
+        <a class="nav-item nav-link {{if $isDocs}}active{{end}}" href="{{ "/docs/" | relURL}}">
+            Documentation
+            {{if $isDocs}}<span class="sr-only">(current)</span>{{end}}
+        </a>
+        <a class="nav-item nav-link" href="https://sigs.k8s.io/krew">GitHub</a>
+    </div>
+</nav>
diff --git a/site/static/css/style.css b/site/static/css/style.css
index e8bb923..063de44 100644
--- a/site/static/css/style.css
+++ b/site/static/css/style.css
@@ -1,3 +1,7 @@
+div.top-level-container {
+    max-width: 840px;
+}
+
 blockquote {
     margin-left: 12px;
     color: #555;
@@ -7,7 +11,7 @@ blockquote {
 }
 
 article h1, article h2, article h3, article h4 {
-    font-weight: 200;
+    font-weight: 300;
 }
 
 pre {
@@ -30,3 +34,7 @@ pre {
 pre .cmdoutput {
     color: #aaa;
 }
+
+.lead {
+    letter-spacing: -1px;
+}
diff --git a/site/static/img/krew-logo.svg b/site/static/img/krew-logo.svg
new file mode 120000
index 0000000..1ea4b3d
--- /dev/null
+++ b/site/static/img/krew-logo.svg
@@ -0,0 +1,1 @@
+../../../assets/logo/horizontal/color/krew-horizontal-color.svg
\ No newline at end of file
diff --git a/site/static/krew-logo.svg b/site/static/krew-logo.svg
deleted file mode 120000
index 2e1b9c6..0000000
--- a/site/static/krew-logo.svg
+++ /dev/null
@@ -1,1 +0,0 @@
-../../assets/logo/horizontal/color/krew-horizontal-color.svg
\ No newline at end of file


Test patch:
diff --git a/internal/installation/receipt/receipt_test.go b/internal/installation/receipt/receipt_test.go
index f0adddc..658ebf1 100644
--- a/internal/installation/receipt/receipt_test.go
+++ b/internal/installation/receipt/receipt_test.go
@@ -22,6 +22,7 @@ import (
 
 	"sigs.k8s.io/krew/internal/index/indexscanner"
 	"sigs.k8s.io/krew/internal/testutil"
+	"sigs.k8s.io/krew/pkg/index"
 )
 
 func TestStore(t *testing.T) {
@@ -58,7 +59,8 @@ func TestLoad(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	if diff := cmp.Diff(&gotPlugin, &testPlugin); diff != "" {
+	testPluginReceipt := index.Receipt{Plugin: testPlugin}
+	if diff := cmp.Diff(&gotPlugin, &testPluginReceipt); diff != "" {
 		t.Fatal(diff)
 	}
 }
diff --git a/internal/installation/util_test.go b/internal/installation/util_test.go
index 4b3d490..7d6bd4d 100644
--- a/internal/installation/util_test.go
+++ b/internal/installation/util_test.go
@@ -65,7 +65,7 @@ func TestListInstalledPlugins(t *testing.T) {
 			defer cleanup()
 
 			for _, plugin := range test.plugins {
-				tempDir.WritePlugin(plugin.Name+constants.ManifestExtension, plugin)
+				tempDir.WriteYAML(plugin.Name+constants.ManifestExtension, plugin)
 			}
 
 			actual, err := ListInstalledPlugins(tempDir.Root())
diff --git a/internal/testutil/tempdir.go b/internal/testutil/tempdir.go
index 92ef417..9b28fee 100644
--- a/internal/testutil/tempdir.go
+++ b/internal/testutil/tempdir.go
@@ -23,8 +23,6 @@ import (
 	"testing"
 
 	"sigs.k8s.io/yaml"
-
-	"sigs.k8s.io/krew/pkg/index"
 )
 
 type TempDir struct {
@@ -77,11 +75,11 @@ func (td *TempDir) Write(file string, content []byte) *TempDir {
 	return td
 }
 
-func (td *TempDir) WritePlugin(file string, plugin index.Plugin) *TempDir {
+func (td *TempDir) WriteYAML(file string, obj interface{}) *TempDir {
 	td.t.Helper()
-	content, err := yaml.Marshal(plugin)
+	content, err := yaml.Marshal(obj)
 	if err != nil {
-		td.t.Fatalf("cannot marshal plugin: %s", err)
+		td.t.Fatalf("cannot marshal obj: %s", err)
 	}
 	return td.Write(file, content)
 }