Repository: kubernetes-sigs/krew
Pull request #540 (2020-03-08 18:06:39)

site: Alternative layout
Instead of a shadowed box in the middle of the screen, sticking navbar to the
top and floating content in the middle. This is a bit more consistent with
current design patterns and more mobile-friendly.

If we like the preview, we can consider incorporating.

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

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/krew/pull/540#" 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/cmd/generate-plugin-overview/generate-plugin-overview.go b/cmd/generate-plugin-overview/generate-plugin-overview.go
index 409e864..96c94a0 100644
--- a/cmd/generate-plugin-overview/generate-plugin-overview.go
+++ b/cmd/generate-plugin-overview/generate-plugin-overview.go
@@ -123,6 +123,7 @@ func findRepo(homePage string) string {
 		`https://kubernetes.github.io/ingress-nginx/kubectl-plugin/`: "kubernetes/ingress-nginx",
 		`https://kudo.dev/`:                                          "kudobuilder/kudo",
 		`https://kubevirt.io`:                                        "kubevirt/kubectl-virt-plugin",
+		`https://popeyecli.io`:                                       "derailed/popeye",
 	}
 	return knownHomePages[homePage]
 }
diff --git a/cmd/krew/cmd/index.go b/cmd/krew/cmd/index.go
new file mode 100644
index 0000000..fe70f30
--- /dev/null
+++ b/cmd/krew/cmd/index.go
@@ -0,0 +1,62 @@
+// Copyright 2019 The Kubernetes 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 cmd
+
+import (
+	"os"
+
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+
+	"sigs.k8s.io/krew/internal/index/indexoperations"
+	"sigs.k8s.io/krew/pkg/constants"
+)
+
+// indexCmd represents the index command
+var indexCmd = &cobra.Command{
+	Use:    "index",
+	Short:  "Manage custom plugin indexes",
+	Long:   "Manage which repositories are used to discover and install plugins from.",
+	Args:   cobra.NoArgs,
+	Hidden: true, // TODO(chriskim06) remove this once multi-index is enabled
+}
+
+var indexListCmd = &cobra.Command{
+	Use:   "list",
+	Short: "List configured indexes",
+	Long: `Print a list of configured indexes.
+
+This command prints a list of indexes. It shows the name and the remote URL for
+each configured index in table format.`,
+	Args: cobra.NoArgs,
+	RunE: func(_ *cobra.Command, _ []string) error {
+		indexes, err := indexoperations.ListIndexes(paths.IndexBase())
+		if err != nil {
+			return errors.Wrap(err, "failed to list indexes")
+		}
+		var rows [][]string
+		for _, index := range indexes {
+			rows = append(rows, []string{index.Name, index.URL})
+		}
+		return printTable(os.Stdout, []string{"INDEX", "URL"}, rows)
+	},
+}
+
+func init() {
+	if _, ok := os.LookupEnv(constants.EnableMultiIndexSwitch); ok {
+		indexCmd.AddCommand(indexListCmd)
+		rootCmd.AddCommand(indexCmd)
+	}
+}
diff --git a/internal/gitutil/git.go b/internal/gitutil/git.go
index 6a496dc..73a6b9d 100644
--- a/internal/gitutil/git.go
+++ b/internal/gitutil/git.go
@@ -31,7 +31,8 @@ func EnsureCloned(uri, destinationPath string) error {
 	if ok, err := IsGitCloned(destinationPath); err != nil {
 		return err
 	} else if !ok {
-		return exec("", "clone", "-v", uri, destinationPath)
+		_, err = Exec("", "clone", "-v", uri, destinationPath)
+		return err
 	}
 	return nil
 }
@@ -49,15 +50,15 @@ func IsGitCloned(gitPath string) (bool, error) {
 // and also will create a pristine working directory by removing
 // untracked files and directories.
 func updateAndCleanUntracked(destinationPath string) error {
-	if err := exec(destinationPath, "fetch", "-v"); err != nil {
+	if _, err := Exec(destinationPath, "fetch", "-v"); err != nil {
 		return errors.Wrapf(err, "fetch index at %q failed", destinationPath)
 	}
 
-	if err := exec(destinationPath, "reset", "--hard", "@{upstream}"); err != nil {
+	if _, err := Exec(destinationPath, "reset", "--hard", "@{upstream}"); err != nil {
 		return errors.Wrapf(err, "reset index at %q failed", destinationPath)
 	}
 
-	err := exec(destinationPath, "clean", "-xfd")
+	_, err := Exec(destinationPath, "clean", "-xfd")
 	return errors.Wrapf(err, "clean index at %q failed", destinationPath)
 }
 
@@ -69,7 +70,12 @@ func EnsureUpdated(uri, destinationPath string) error {
 	return updateAndCleanUntracked(destinationPath)
 }
 
-func exec(pwd string, args ...string) error {
+// GetRemoteURL returns the url of the remote origin
+func GetRemoteURL(dir string) (string, error) {
+	return Exec(dir, "config", "--get", "remote.origin.url")
+}
+
+func Exec(pwd string, args ...string) (string, error) {
 	klog.V(4).Infof("Going to run git %s", strings.Join(args, " "))
 	cmd := osexec.Command("git", args...)
 	cmd.Dir = pwd
@@ -80,7 +86,7 @@ func exec(pwd string, args ...string) error {
 	}
 	cmd.Stdout, cmd.Stderr = w, w
 	if err := cmd.Run(); err != nil {
-		return errors.Wrapf(err, "command execution failure, output=%q", buf.String())
+		return "", errors.Wrapf(err, "command execution failure, output=%q", buf.String())
 	}
-	return nil
+	return strings.TrimSpace(buf.String()), nil
 }
diff --git a/internal/index/indexoperations/index.go b/internal/index/indexoperations/index.go
new file mode 100644
index 0000000..c66b988
--- /dev/null
+++ b/internal/index/indexoperations/index.go
@@ -0,0 +1,54 @@
+// Copyright 2019 The Kubernetes 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 indexoperations
+
+import (
+	"io/ioutil"
+	"path/filepath"
+
+	"github.com/pkg/errors"
+
+	"sigs.k8s.io/krew/internal/gitutil"
+)
+
+// Index describes the name and URL of a configured index.
+type Index struct {
+	Name string
+	URL  string
+}
+
+// ListIndexes returns a slice of Index objects. The path argument is used as
+// the base path of the index.
+func ListIndexes(path string) ([]Index, error) {
+	dirs, err := ioutil.ReadDir(path)
+	if err != nil {
+		return nil, errors.Wrapf(err, "failed to read directory %s", path)
+	}
+
+	indexes := []Index{}
+	for _, dir := range dirs {
+		indexName := dir.Name()
+		remote, err := gitutil.GetRemoteURL(filepath.Join(path, indexName))
+		if err != nil {
+			return nil, errors.Wrapf(err, "failed to list the remote URL for index %s", indexName)
+		}
+
+		indexes = append(indexes, Index{
+			Name: indexName,
+			URL:  remote,
+		})
+	}
+	return indexes, nil
+}
diff --git a/site/layouts/partials/footer.html b/site/layouts/partials/footer.html
index 022e61d..fda5921 100644
--- a/site/layouts/partials/footer.html
+++ b/site/layouts/partials/footer.html
@@ -5,7 +5,7 @@
             Krew is a Kubernetes
                 <a href="https://github.com/kubernetes/community/blob/master/sig-cli/README.md#cli-special-interest-group" target="_blank" class="text-info">
                     SIG CLI</a> project.
-                <p>
+                <p class="d-none d-md-block">
                     {{- with .File -}}
                         <a href="http://sigs.k8s.io/krew/site/content/{{- .File.Path -}}" target="_blank" class="text-info">
                         Edit Page</a>
diff --git a/site/layouts/partials/header.html b/site/layouts/partials/header.html
index 554a4b4..989534a 100644
--- a/site/layouts/partials/header.html
+++ b/site/layouts/partials/header.html
@@ -3,8 +3,7 @@
 <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, maximum-scale=1.0, minimum-scale=1.0">
-
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
     <title>
         {{ if .IsHome }}
             {{ .Site.Title }}
@@ -21,10 +20,11 @@
 
 <body>
 
-    <div class="container top-level-container shadow-lg rounded
+    {{ partial "navbar.html" . }}
+
+    <div class="container top-level-container
         col-12 col-lg-8 col-xl-6
-        my-0 my-md-5
+        my-1 my-md-3
         p-0">
-        {{ partial "navbar.html" . }}
 
-        <div class="py-md-4 pl-2 px-md-5">
+        <div class="py-1 pl-2 px-md-5">
diff --git a/site/layouts/partials/navbar.html b/site/layouts/partials/navbar.html
index 63327cd..a3115b9 100644
--- a/site/layouts/partials/navbar.html
+++ b/site/layouts/partials/navbar.html
@@ -1,29 +1,31 @@
 <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}}
+    <div class="container top-level-container col-12">
+        <a class="navbar-brand" href="{{"/" | relURL }}">
+            <img src="{{ "/img/krew-logo.svg" }}" height="40vw"/>
         </a>
 
-        {{ $isQuickstart := (eq .Slug "quickstart") }}
-        {{ $isDocs := (and (eq .Section "docs") (ne .Slug "quickstart")) }}
+        <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>
 
-        <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>
+            {{ $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>
     </div>
 </nav>
diff --git a/site/static/css/style.css b/site/static/css/style.css
index fbd1fd2..503c3ae 100644
--- a/site/static/css/style.css
+++ b/site/static/css/style.css
@@ -16,7 +16,7 @@ article h1, article h2, article h3, article h4 {
 
 pre {
     padding: 12px;
-    overflow-y: scroll;
+    overflow: auto
 }
 
 .noselect {


Test patch:
diff --git a/internal/index/indexoperations/index_test.go b/internal/index/indexoperations/index_test.go
new file mode 100644
index 0000000..7fd07cd
--- /dev/null
+++ b/internal/index/indexoperations/index_test.go
@@ -0,0 +1,66 @@
+// Copyright 2019 The Kubernetes 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 indexoperations
+
+import (
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+
+	"sigs.k8s.io/krew/internal/environment"
+	"sigs.k8s.io/krew/internal/gitutil"
+	"sigs.k8s.io/krew/internal/testutil"
+)
+
+func TestListIndexes(t *testing.T) {
+	tmpDir, cleanup := testutil.NewTempDir(t)
+	defer cleanup()
+
+	wantIndexes := []Index{
+		{
+			Name: "custom",
+			URL:  "https://github.com/custom/index.git",
+		},
+		{
+			Name: "default",
+			URL:  "https://github.com/default/index.git",
+		},
+	}
+
+	for _, index := range wantIndexes {
+		path := tmpDir.Path("index/" + index.Name)
+		if err := os.MkdirAll(path, os.ModePerm); err != nil {
+			t.Fatalf("cannot create directory %q: %s", filepath.Dir(path), err)
+		}
+		_, err := gitutil.Exec(path, "init")
+		if err != nil {
+			t.Fatalf("error initializing git repo: %s", err)
+		}
+		_, err = gitutil.Exec(path, "remote", "add", "origin", index.URL)
+		if err != nil {
+			t.Fatalf("error setting remote origin: %s", err)
+		}
+	}
+
+	gotIndexes, err := ListIndexes(environment.NewPaths(tmpDir.Root()).IndexBase())
+	if err != nil {
+		t.Errorf("error listing indexes: %v", err)
+	}
+	if diff := cmp.Diff(wantIndexes, gotIndexes); diff != "" {
+		t.Errorf("output does not match: %s", diff)
+	}
+}