Skip to content

Commit 7ec26f6

Browse files
Check whether PATH has been set up correctly (#489)
* Add util to assert correct krew setup Homebrew refuses to add a post-install message for setting up the PATH variable with krew's plugin store. Therefore we need to check this on our side to warn users about an incomplete setup. The goal is to - print specific instructions for each shell/OS. - not print the instructions when krew installs itself. * Comments
1 parent 4303b6a commit 7ec26f6

File tree

3 files changed

+197
-2
lines changed

3 files changed

+197
-2
lines changed

cmd/krew/cmd/internal/setup_check.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2020 The Kubernetes Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package internal
16+
17+
import (
18+
"fmt"
19+
"os"
20+
"path/filepath"
21+
"strings"
22+
23+
"k8s.io/klog"
24+
25+
"sigs.k8s.io/krew/internal/environment"
26+
"sigs.k8s.io/krew/internal/installation"
27+
)
28+
29+
const (
30+
instructionWindows = `To be able to run kubectl plugins, you need to add the
31+
"%USERPROFILE%\.krew\bin" directory to your PATH environment variable
32+
and restart your shell.`
33+
instructionUnixTemplate = `To be able to run kubectl plugins, you need to add
34+
the following to your %s
35+
36+
and restart your shell.`
37+
instructionZsh = `~/.zshrc:
38+
39+
export PATH="${PATH}:${HOME}/.krew/bin"`
40+
instructionBash = `~/.bash_profile or ~/.bashrc:
41+
42+
export PATH="${PATH}:${HOME}/.krew/bin"`
43+
instructionFish = `config.fish:
44+
45+
set -gx PATH $PATH $HOME/.krew/bin`
46+
instructionGeneric = `~/.bash_profile, ~/.bashrc, or ~/.zshrc:
47+
48+
export PATH="${PATH}:${HOME}/.krew/bin"`
49+
)
50+
51+
func IsBinDirInPATH(paths environment.Paths) bool {
52+
// For the first run we don't want to show a warning.
53+
_, err := os.Stat(paths.BasePath())
54+
if err != nil {
55+
klog.V(4).Info("Assuming this is the first run")
56+
return os.IsNotExist(err)
57+
}
58+
59+
binPath := paths.BinPath()
60+
for _, dirInPATH := range filepath.SplitList(os.Getenv("PATH")) {
61+
if dirInPATH == binPath {
62+
return true
63+
}
64+
}
65+
return false
66+
}
67+
68+
func SetupInstructions() string {
69+
if installation.IsWindows() {
70+
return instructionWindows
71+
}
72+
73+
var instruction string
74+
switch shell := os.Getenv("SHELL"); {
75+
case strings.HasSuffix(shell, "/zsh"):
76+
instruction = instructionZsh
77+
case strings.HasSuffix(shell, "/bash"):
78+
instruction = instructionBash
79+
case strings.HasSuffix(shell, "/fish"):
80+
instruction = instructionFish
81+
default:
82+
instruction = instructionGeneric
83+
}
84+
return fmt.Sprintf(instructionUnixTemplate, instruction)
85+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright 2020 The Kubernetes Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package internal
16+
17+
import (
18+
"os"
19+
"strings"
20+
"testing"
21+
22+
"sigs.k8s.io/krew/internal/environment"
23+
"sigs.k8s.io/krew/internal/testutil"
24+
)
25+
26+
func TestIsBinDirInPATH_firstRun(t *testing.T) {
27+
tempDir, cleanup := testutil.NewTempDir(t)
28+
defer cleanup()
29+
30+
paths := environment.NewPaths(tempDir.Path("does-not-exist"))
31+
res := IsBinDirInPATH(paths)
32+
if res == false {
33+
t.Errorf("expected positive result on first run")
34+
}
35+
}
36+
37+
func TestIsBinDirInPATH_secondRun(t *testing.T) {
38+
tempDir, cleanup := testutil.NewTempDir(t)
39+
defer cleanup()
40+
paths := environment.NewPaths(tempDir.Root())
41+
res := IsBinDirInPATH(paths)
42+
if res == true {
43+
t.Errorf("expected negative result on second run")
44+
}
45+
}
46+
47+
func TestSetupInstructions_windows(t *testing.T) {
48+
const instructionsContain = "add the\n\"%USERPROFILE%\\.krew\\bin\" directory to your PATH environment variable"
49+
os.Setenv("KREW_OS", "windows")
50+
defer func() { os.Unsetenv("KREW_OS") }()
51+
instructions := SetupInstructions()
52+
if !strings.Contains(instructions, instructionsContain) {
53+
t.Errorf("expected %q\nto contain %q", instructions, instructionsContain)
54+
}
55+
}
56+
57+
func TestSetupInstructions_unix(t *testing.T) {
58+
tests := []struct {
59+
name string
60+
shell string
61+
instructionsContain string
62+
}{
63+
{
64+
name: "When the shell is zsh",
65+
shell: "/bin/zsh",
66+
instructionsContain: "~/.zshrc",
67+
},
68+
{
69+
name: "When the shell is bash",
70+
shell: "/bin/bash",
71+
instructionsContain: "~/.bash_profile or ~/.bashrc",
72+
},
73+
{
74+
name: "When the shell is fish",
75+
shell: "/bin/fish",
76+
instructionsContain: "config.fish",
77+
},
78+
{
79+
name: "When the shell is unknown",
80+
shell: "other",
81+
instructionsContain: "~/.bash_profile, ~/.bashrc, or ~/.zshrc",
82+
},
83+
}
84+
85+
// always set KREW_OS, so that tests succeed on windows
86+
os.Setenv("KREW_OS", "linux")
87+
defer func(origShell string) {
88+
os.Unsetenv("KREW_OS")
89+
os.Setenv("SHELL", origShell)
90+
}(os.Getenv("SHELL"))
91+
92+
for _, test := range tests {
93+
t.Run(test.name, func(tt *testing.T) {
94+
os.Setenv("SHELL", test.shell)
95+
instructions := SetupInstructions()
96+
if !strings.Contains(instructions, test.instructionsContain) {
97+
tt.Errorf("expected %q\nto contain %q", instructions, test.instructionsContain)
98+
}
99+
})
100+
}
101+
}

cmd/krew/cmd/root.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ import (
1919
"fmt"
2020
"os"
2121

22+
"github.com/fatih/color"
2223
"github.com/mattn/go-isatty"
2324
"github.com/pkg/errors"
2425
"github.com/spf13/cobra"
2526
"github.com/spf13/pflag"
2627
"k8s.io/klog"
2728

29+
"sigs.k8s.io/krew/cmd/krew/cmd/internal"
2830
"sigs.k8s.io/krew/internal/environment"
2931
"sigs.k8s.io/krew/internal/gitutil"
3032
"sigs.k8s.io/krew/internal/installation"
@@ -77,15 +79,22 @@ func init() {
7779
}
7880

7981
paths = environment.MustGetKrewPaths()
82+
}
83+
84+
func preRun(cmd *cobra.Command, _ []string) error {
85+
// check must be done before ensureDirs, to detect krew's self-installation
86+
if !internal.IsBinDirInPATH(paths) {
87+
boldRed := color.New(color.FgRed, color.Bold).SprintfFunc()
88+
fmt.Fprintf(os.Stderr, "%s: %s\n\n", boldRed("WARNING"), internal.SetupInstructions())
89+
}
90+
8091
if err := ensureDirs(paths.BasePath(),
8192
paths.InstallPath(),
8293
paths.BinPath(),
8394
paths.InstallReceiptsPath()); err != nil {
8495
klog.Fatal(err)
8596
}
86-
}
8797

88-
func preRun(cmd *cobra.Command, _ []string) error {
8998
// detect if receipts migration (v0.2.x->v0.3.x) is complete
9099
isMigrated, err := receiptsmigration.Done(paths)
91100
if err != nil {

0 commit comments

Comments
 (0)