diff --git a/internal/installation/install.go b/internal/installation/install.go index 03b6b68b..fef9e125 100644 --- a/internal/installation/install.go +++ b/internal/installation/install.go @@ -41,6 +41,8 @@ type installOperation struct { pluginName string platform index.Platform + createSubCommand bool + installDir string binDir string } @@ -79,8 +81,9 @@ func Install(p environment.Paths, plugin index.Plugin, indexName string, opts In pluginName: plugin.Name, platform: candidate, - binDir: p.BinPath(), - installDir: p.PluginVersionInstallPath(plugin.Name, plugin.Spec.Version), + createSubCommand: plugin.Spec.CreateSubCommand, + binDir: p.BinPath(), + installDir: p.PluginVersionInstallPath(plugin.Name, plugin.Spec.Version), }, opts); err != nil { return errors.Wrap(err, "install failed") } @@ -125,7 +128,7 @@ func install(op installOperation, opts InstallOpts) error { if _, ok := pathutil.IsSubPath(subPathAbs, pathAbs); !ok { return errors.Wrapf(err, "the fullPath %q does not extend the sub-fullPath %q", fullPath, op.installDir) } - err = createOrUpdateLink(op.binDir, fullPath, op.pluginName) + err = createOrUpdateLink(op.binDir, fullPath, op.pluginName, op.createSubCommand) return errors.Wrap(err, "failed to link installed plugin") } @@ -170,6 +173,8 @@ func Uninstall(p environment.Paths, name string) error { klog.V(1).Infof("Deleting plugin %s", name) + // Clarify: How can we handle this case? uninstalling a plugin does not have manifest provided. + // Maybe should store manifest used in binpath? symlinkPath := filepath.Join(p.BinPath(), pluginNameToBin(name, IsWindows())) klog.V(3).Infof("Unlink %q", symlinkPath) if err := removeLink(symlinkPath); err != nil { @@ -187,8 +192,8 @@ func Uninstall(p environment.Paths, name string) error { return errors.Wrapf(err, "could not remove plugin receipt %q", pluginReceiptPath) } -func createOrUpdateLink(binDir, binary, plugin string) error { - dst := filepath.Join(binDir, pluginNameToBin(plugin, IsWindows())) +func createOrUpdateLink(binDir, binary, plugin string, createSubCommand bool) error { + dst := filepath.Join(binDir, pluginNameToBin(plugin, IsWindows(), createSubCommand)) if err := removeLink(dst); err != nil { return errors.Wrap(err, "failed to remove old symlink") @@ -238,7 +243,10 @@ func IsWindows() bool { // pluginNameToBin creates the name of the symlink file for the plugin name. // It converts dashes to underscores. -func pluginNameToBin(name string, isWindows bool) string { +func pluginNameToBin(name string, isWindows bool, createSubCommand bool) string { + if createSubCommand && !isWindows { + return name + } name = strings.ReplaceAll(name, "-", "_") name = "kubectl-" + name if isWindows { diff --git a/internal/installation/install_test.go b/internal/installation/install_test.go index 6306befb..a138abc4 100644 --- a/internal/installation/install_test.go +++ b/internal/installation/install_test.go @@ -79,10 +79,11 @@ func Test_moveTargets(t *testing.T) { func Test_createOrUpdateLink(t *testing.T) { tests := []struct { - name string - pluginName string - binary string - wantErr bool + name string + pluginName string + binary string + createSubCommand bool + wantErr bool }{ { name: "normal link", @@ -107,7 +108,7 @@ func Test_createOrUpdateLink(t *testing.T) { t.Run(tt.name, func(t *testing.T) { tmpDir := testutil.NewTempDir(t) - if err := createOrUpdateLink(tmpDir.Root(), tt.binary, tt.pluginName); (err != nil) != tt.wantErr { + if err := createOrUpdateLink(tmpDir.Root(), tt.binary, tt.pluginName, tt.createSubCommand); (err != nil) != tt.wantErr { t.Errorf("createOrUpdateLink() error = %v, wantErr %v", err, tt.wantErr) } }) @@ -116,18 +117,19 @@ func Test_createOrUpdateLink(t *testing.T) { func Test_pluginNameToBin(t *testing.T) { tests := []struct { - name string - isWindows bool - want string + name string + isWindows bool + createSubCommand bool + want string }{ - {"foo", false, "kubectl-foo"}, - {"foo-bar", false, "kubectl-foo_bar"}, - {"foo", true, "kubectl-foo.exe"}, - {"foo-bar", true, "kubectl-foo_bar.exe"}, + {"foo", false, false, "kubectl-foo"}, + {"foo-bar", false, false, "kubectl-foo_bar"}, + {"foo", true, false, "kubectl-foo.exe"}, + {"foo-bar", true, false, "kubectl-foo_bar.exe"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := pluginNameToBin(tt.name, tt.isWindows); got != tt.want { + if got := pluginNameToBin(tt.name, tt.isWindows, tt.createSubCommand); got != tt.want { t.Errorf("pluginNameToBin(%v, %v) = %v; want %v", tt.name, tt.isWindows, got, tt.want) } }) diff --git a/pkg/index/types.go b/pkg/index/types.go index e2b19325..2abb41a9 100644 --- a/pkg/index/types.go +++ b/pkg/index/types.go @@ -34,6 +34,11 @@ type PluginSpec struct { Caveats string `json:"caveats,omitempty"` Homepage string `json:"homepage,omitempty"` + // CreateSubCommand specifies whether the plugin should be installed as a + // create subcommand. If true, the plugin will preserve first 'create' word + // in the plugin name. + CreateSubCommand bool `json:"createSubCommand,omitempty"` + Platforms []Platform `json:"platforms,omitempty"` }