Repository: kubernetes-sigs/krew
Pull request #603 (2020-05-30 01:24:28)

Remove receipts migration
This removes the receiptsmigration code like we discussed in the related issue. I'm leaving the `system.go` file for now and leaving the integration test with the same name, but I might end up changing it in a follow up PR when I switch the index migration to be automatic.

Related issue: #600 
<!-- For proposed features, make sure there's an issue it's discussed first -->


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

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/krew/pull/603#" title="Author self-approved">chriskim06</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **corneliusweig**
You can assign the PR to them by writing `/assign @corneliusweig` in a comment when ready.

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

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

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

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":["corneliusweig"]} -->
ahmetb: /lgtm
/approve
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/krew/pull/603#issuecomment-638505143" title="Approved">ahmetb</a>*, *<a href="https://github.com/kubernetes-sigs/krew/pull/603#" title="Author self-approved">chriskim06</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/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 76ff283..d77df9a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -60,9 +60,6 @@ jobs:
     - name: Run integration tests
       run: hack/run-integration-tests.sh
 
-    - name: Verify receipt migration from v0.2.x to v0.3.x
-      run: hack/verify-receipts-upgrade-migration.sh
-
     - name: Create a new release
       if: contains(github.ref, 'tags')
       id: create_release
diff --git a/cmd/krew/cmd/root.go b/cmd/krew/cmd/root.go
index 925c5cc..4151d03 100644
--- a/cmd/krew/cmd/root.go
+++ b/cmd/krew/cmd/root.go
@@ -141,8 +141,11 @@ func preRun(cmd *cobra.Command, _ []string) error {
 	if err != nil {
 		return err
 	}
-	if !isMigrated && cmd.Use != "receipts-upgrade" {
-		fmt.Fprintln(os.Stderr, "You need to perform a migration to continue using krew.\nPlease run `kubectl krew system receipts-upgrade`")
+	if !isMigrated {
+		fmt.Fprintln(os.Stderr, `This version of Krew is not supported anymore. Please manually migrate:
+1. Uninstall Krew: https://krew.sigs.k8s.io/docs/user-guide/setup/uninstall/
+2. Install latest Krew: https://krew.sigs.k8s.io/docs/user-guide/setup/install/
+3. Install the plugins you used`)
 		return errors.New("krew home outdated")
 	}
 
diff --git a/cmd/krew/cmd/system.go b/cmd/krew/cmd/system.go
index dc6b5c2..31e8160 100644
--- a/cmd/krew/cmd/system.go
+++ b/cmd/krew/cmd/system.go
@@ -20,7 +20,6 @@ import (
 	"github.com/spf13/cobra"
 
 	"sigs.k8s.io/krew/internal/indexmigration"
-	"sigs.k8s.io/krew/internal/receiptsmigration"
 	"sigs.k8s.io/krew/pkg/constants"
 )
 
@@ -37,23 +36,6 @@ This command will be removed without further notice from future versions of krew
 	Hidden: true,
 }
 
-// TODO(corneliusweig) remove migration code with v0.4
-// systemCmd represents the system command
-var receiptsUpgradeCmd = &cobra.Command{
-	Use:   "receipts-upgrade",
-	Short: "Perform a migration of the krew home",
-	Long: `Krew became more awesome! To use the new features, you need to run this
-one-time migration, which will reinstall all current plugins.
-
-This command will be removed without further notice from future versions of krew.
-`,
-	Args: cobra.NoArgs,
-	RunE: func(cmd *cobra.Command, args []string) error {
-		return receiptsmigration.Migrate(paths)
-	},
-	PreRunE: func(_ *cobra.Command, _ []string) error { return ensureIndexesUpdated() },
-}
-
 var indexUpgradeCmd = &cobra.Command{
 	Use:   "index-upgrade",
 	Short: "Perform a migration of the krew index",
@@ -72,6 +54,5 @@ func init() {
 	if _, ok := os.LookupEnv(constants.EnableMultiIndexSwitch); ok {
 		systemCmd.AddCommand(indexUpgradeCmd)
 	}
-	systemCmd.AddCommand(receiptsUpgradeCmd)
 	rootCmd.AddCommand(systemCmd)
 }
diff --git a/cmd/krew/main.go b/cmd/krew/main.go
index 15e2ca8..6ccb16a 100644
--- a/cmd/krew/main.go
+++ b/cmd/krew/main.go
@@ -21,6 +21,6 @@ import (
 )
 
 func main() {
-	cmd.Execute()
 	defer klog.Flush()
+	cmd.Execute()
 }
diff --git a/hack/verify-receipts-upgrade-migration.sh b/hack/verify-receipts-upgrade-migration.sh
deleted file mode 100755
index f3eb68c..0000000
--- a/hack/verify-receipts-upgrade-migration.sh
+++ /dev/null
@@ -1,147 +0,0 @@
-#!/usr/bin/env bash
-
-# 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.
-
-# This script tests "krew system receipts-migration" which was introduced for
-# migrating krew 0.2.x to krew 0.3.x.
-#
-# TODO(ahmetb,corneliusweig) remove at/after krew 0.4.x when receipts-migration
-# is no longer supported.
-
-set -euo pipefail
-
-[[ -n "${DEBUG:-}" ]] && set -x
-
-SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-BINDIR="${SCRIPTDIR}/../out/bin"
-goos="$(go env GOOS)"
-goarch="$(go env GOARCH)"
-
-install_krew_0_2_1() {
-  # from https://github.com/kubernetes-sigs/krew/blob/v0.2.1/README.md
-  krew_root="${1}"
-  temp_dir="$(mktemp -d)"
-  trap 'rm -rf "${temp_dir}"' RETURN
-  (
-    cd "${temp_dir}"
-    curl -fsSLO "https://storage.googleapis.com/krew/v0.2.1/krew.{tar.gz,yaml}"
-    tar zxf krew.tar.gz
-    env KREW_ROOT="${krew_root}" ./krew-"${goos}_amd64" install \
-      --manifest="./krew.yaml" --archive="./krew.tar.gz"
-  )
-}
-
-install_plugin() {
-  krew_root="${1}"
-  plugin="${2}"
-
-  run_krew "${1}" install "${plugin}" 1>/dev/null
-}
-
-# patch_krew_bin replaces the installed krew 0.2.x binary with the specified
-patch_krew_bin() {
-  krew_root="${1}"
-  new_binary="${2}"
-
-  local old_binary
-  old_binary="$(readlink -f "${krew_root}/bin/kubectl-krew")"
-  cp -f "${new_binary}" "${old_binary}"
-}
-
-# run_krew runs 'krew' with the specified KREW_ROOT and arguments.
-run_krew() {
-  krew_root="${1}"
-  shift
-
-  env KREW_ROOT="${krew_root}" \
-    PATH="${krew_root}/bin:$PATH" \
-    kubectl krew "$@"
-}
-
-verify_plugin_installed() {
-  krew_root="${1}"
-  plugin="${2}"
-  local plugin_bin_name
-  plugin_bin_name="kubectl-${plugin//-/_}"
-
-  [[ -x "${krew_root}/bin/${plugin_bin_name}" ]]
-}
-
-verify_plugin_receipt() {
-  krew_root="${1}"
-  plugin="${2}"
-  [[ -f "${krew_root}/receipts/${plugin}.yaml" ]]
-}
-
-main() {
-  new_krew="${BINDIR}/krew-${goos}_${goarch}"
-  if [[ ! -e "${new_krew}" ]]; then
-    echo >&2 "Could not find ${new_krew}."
-    exit 1
-  fi
-
-  krew_root="$(mktemp -d)"
-  trap 'rm -rf "${krew_root}"' RETURN
-
-  echo >&2 "Test directory: ${krew_root}"
-  install_krew_0_2_1 "${krew_root}"
-  install_plugin "${krew_root}" "get-all"
-  echo >&2 "Swapping krew binary."
-  patch_krew_bin "${krew_root}" "${new_krew}"
-  run_krew "${krew_root}" version && (
-    echo >&2 "[FAIL] expected 'krew version' to fail with upgrade message"
-    exit 1
-  )
-  echo >&2 "Performing migration."
-  run_krew "${krew_root}" system receipts-upgrade
-  verify_plugin_installed "${krew_root}" get-all || (
-    echo >&2 "get-all plugin is not linked"
-    exit 1
-  )
-  verify_plugin_receipt "${krew_root}" get-all || (
-    echo >&2 "get-all plugin receipt missing"
-    exit 1
-  )
-  run_krew "${krew_root}" list || (
-    echo >&2 "krew list is failing"
-    exit 1
-  )
-  run_krew "${krew_root}" search || (
-    echo >&2 "krew search is failing"
-    exit 1
-  )
-  run_krew "${krew_root}" uninstall get-all || (
-    echo >&2 "get-all cannot be uninstalled"
-  )
-  verify_plugin_installed "${krew_root}" krew || (
-    echo >&2 "krew binary is missing"
-    exit 1
-  )
-  verify_plugin_receipt "${krew_root}" krew || (
-    echo >&2 "krew plugin receipt is not copied over"
-    exit 1
-  )
-  install_plugin "${krew_root}" "who-can" # install still works after migration
-  verify_plugin_installed "${krew_root}" who-can || (
-    echo >&2 "who-can plugin is not linked"
-    exit 1
-  )
-  verify_plugin_receipt "${krew_root}" who-can || (
-    echo >&2 "who-can plugin receipt missing"
-    exit 1
-  )
-}
-
-main
diff --git a/internal/receiptsmigration/migration.go b/internal/receiptsmigration/migration.go
index 9adddd7..5ba95ee 100644
--- a/internal/receiptsmigration/migration.go
+++ b/internal/receiptsmigration/migration.go
@@ -16,25 +16,9 @@
 package receiptsmigration
 
 import (
-	"io"
 	"io/ioutil"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"runtime"
-	"strings"
-
-	"github.com/pkg/errors"
-	"k8s.io/klog"
 
 	"sigs.k8s.io/krew/internal/environment"
-	"sigs.k8s.io/krew/internal/index/validation"
-	"sigs.k8s.io/krew/internal/receiptsmigration/oldenvironment"
-	"sigs.k8s.io/krew/pkg/constants"
-)
-
-const (
-	krewPluginName = "krew"
 )
 
 // Done checks if the krew installation requires a migration.
@@ -54,162 +38,3 @@ func Done(newPaths environment.Paths) (bool, error) {
 
 	return !(hasInstalledPlugins && hasNoReceipts), nil
 }
-
-// Migrate searches for installed plugins, removes each plugin and reinstalls afterwards.
-// Once started, it keeps going even if there are errors.
-func Migrate(newPaths environment.Paths) error {
-	isMigrated, err := Done(newPaths)
-	if err != nil {
-		return err
-	}
-	if isMigrated {
-		klog.Infoln("Already migrated")
-		return nil
-	}
-
-	oldPaths := oldenvironment.MustGetKrewPaths()
-	installed, err := getPluginsToReinstall(oldPaths, newPaths)
-	if err != nil {
-		return errors.Wrapf(err, "failed to build list of plugins")
-	}
-
-	klog.Infoln("These plugins will be reinstalled: ", installed)
-
-	// krew must be skipped by the normal migration logic
-	if err := copyKrewManifest(newPaths.IndexPluginsPath(constants.DefaultIndexName), newPaths.InstallReceiptsPath()); err != nil {
-		return errors.Wrapf(err, "failed to copy krew manifest")
-	}
-
-	// point of no return: keep on going when encountering errors
-	for _, plugin := range installed {
-		if err := uninstall(oldPaths, plugin); err != nil {
-			klog.Infof("Uninstalling of %s failed, skipping reinstall", plugin)
-			continue
-		}
-
-		if err := reinstall(plugin); err != nil {
-			klog.Infof("Reinstalling %s failed", plugin)
-		}
-	}
-
-	return nil
-}
-
-func copyKrewManifest(srcFolder, dstFolder string) error {
-	manifestName := "krew" + constants.ManifestExtension
-	src, err := os.Open(filepath.Join(srcFolder, manifestName))
-	if err != nil {
-		return err
-	}
-	defer src.Close()
-
-	dst, err := os.Create(filepath.Join(dstFolder, manifestName))
-	if err != nil {
-		return err
-	}
-	defer dst.Close()
-
-	_, err = io.Copy(dst, src)
-	return err
-}
-
-// getPluginsToReinstall collects a list of installed plugins which can be reinstalled.
-func getPluginsToReinstall(oldPaths oldenvironment.Paths, newPaths environment.Paths) ([]string, error) {
-	store := oldPaths.InstallPath()
-	fileInfos, err := ioutil.ReadDir(store)
-	if err != nil {
-		return nil, err
-	}
-
-	renewable := []string{}
-	for _, fileInfo := range fileInfos {
-		plugin := fileInfo.Name()
-		if !fileInfo.IsDir() || !validation.IsSafePluginName(plugin) || plugin == krewPluginName {
-			continue
-		}
-		if !isAvailableInIndex(newPaths, plugin) {
-			klog.Infof("Skipping plugin %s, because it is missing in the index", plugin)
-			continue
-		}
-		renewable = append(renewable, plugin)
-	}
-	return renewable, nil
-}
-
-// isAvailableInIndex checks that the given plugin is available in the index
-func isAvailableInIndex(paths environment.Paths, plugin string) bool {
-	pluginYaml := filepath.Join(paths.IndexPluginsPath(constants.DefaultIndexName), plugin+constants.ManifestExtension)
-	_, err := os.Lstat(pluginYaml)
-	return err == nil
-}
-
-// uninstall will uninstall a plugin in the old krew home layout.
-func uninstall(p oldenvironment.Paths, name string) error {
-	if name == krewPluginName {
-		return errors.Errorf("removing krew is not allowed through krew. Please run:\n\t rm -r %s", p.BasePath())
-	}
-	klog.Infof("Uninstalling %s", name)
-
-	symlinkPath := filepath.Join(p.BinPath(), pluginNameToBin(name, isWindows()))
-	klog.V(3).Infof("Unlink %q", symlinkPath)
-	if err := removeLink(symlinkPath); err != nil {
-		return errors.Wrap(err, "could not uninstall symlink of plugin")
-	}
-
-	pluginInstallPath := p.PluginInstallPath(name)
-	klog.V(3).Infof("Deleting path %q", pluginInstallPath)
-	return errors.Wrapf(os.RemoveAll(pluginInstallPath), "could not remove plugin directory %q", pluginInstallPath)
-}
-
-// reinstall shells out to `krew` to install the given plugin.
-func reinstall(plugin string) error {
-	klog.Infoln("Re-installing", plugin)
-	cmd := exec.Command("kubectl", "krew", "install", plugin)
-	output, err := cmd.CombinedOutput()
-	if err != nil {
-		klog.Info(string(output))
-	}
-	return err
-}
-
-// removeLink removes a symlink reference if exists.
-// same as pkg/installation/install.go:167
-func removeLink(path string) error {
-	fi, err := os.Lstat(path)
-	if os.IsNotExist(err) {
-		klog.V(3).Infof("No file found at %q", path)
-		return nil
-	} else if err != nil {
-		return errors.Wrapf(err, "failed to read the symlink in %q", path)
-	}
-
-	if fi.Mode()&os.ModeSymlink == 0 {
-		return errors.Errorf("file %q is not a symlink (mode=%s)", path, fi.Mode())
-	}
-	if err := os.Remove(path); err != nil {
-		return errors.Wrapf(err, "failed to remove the symlink in %q", path)
-	}
-	klog.V(3).Infof("Removed symlink from %q", path)
-	return nil
-}
-
-// same as pkg/installation/install.go:186
-func isWindows() bool {
-	goos := runtime.GOOS
-	if env := os.Getenv("KREW_OS"); env != "" {
-		goos = env
-	}
-	return goos == "windows"
-}
-
-// pluginNameToBin creates the name of the symlink file for the plugin name.
-// It converts dashes to underscores.
-// same as pkg/installation/install.go:196
-func pluginNameToBin(name string, isWindows bool) string {
-	name = strings.ReplaceAll(name, "-", "_")
-	name = "kubectl-" + name
-	if isWindows {
-		name += ".exe"
-	}
-	return name
-}
diff --git a/internal/receiptsmigration/oldenvironment/environment.go b/internal/receiptsmigration/oldenvironment/environment.go
deleted file mode 100644
index c0b8a5a..0000000
--- a/internal/receiptsmigration/oldenvironment/environment.go
+++ /dev/null
@@ -1,91 +0,0 @@
-// 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.
-
-// todo(corneliusweig) remove migration code with v0.4
-// oldenvironment is a copy of the relevant function in environment before the index migration.
-package oldenvironment
-
-import (
-	"os"
-	"path/filepath"
-
-	"github.com/pkg/errors"
-	"k8s.io/client-go/util/homedir"
-	"k8s.io/klog"
-)
-
-// Paths contains all important environment paths
-type Paths struct {
-	base string
-	tmp  string
-}
-
-// MustGetKrewPaths returns the inferred paths for krew. By default, it assumes
-// $HOME/.krew as the base path, but can be overridden via KREW_ROOT environment
-// variable.
-func MustGetKrewPaths() Paths {
-	base := filepath.Join(homedir.HomeDir(), ".krew")
-	if fromEnv := os.Getenv("KREW_ROOT"); fromEnv != "" {
-		base = fromEnv
-		klog.V(4).Infof("using environment override KREW_ROOT=%s", fromEnv)
-	}
-	base, err := filepath.Abs(base)
-	if err != nil {
-		panic(errors.Wrap(err, "cannot get absolute path"))
-	}
-	return NewPaths(base)
-}
-
-func NewPaths(base string) Paths {
-	return Paths{base: base, tmp: os.TempDir()}
-}
-
-// BasePath returns krew base directory.
-func (p Paths) BasePath() string { return p.base }
-
-// IndexPath returns the base directory where plugin index repository is cloned.
-//
-// e.g. {BasePath}/index/
-func (p Paths) IndexPath() string { return filepath.Join(p.base, "index") }
-
-// IndexPluginsPath returns the plugins directory of the index repository.
-//
-// e.g. {BasePath}/index/plugins/
-func (p Paths) IndexPluginsPath() string { return filepath.Join(p.base, "index", "plugins") }
-
-// BinPath returns the path where plugin executable symbolic links are found.
-// This path should be added to $PATH in client machine.
-//
-// e.g. {BasePath}/bin
-func (p Paths) BinPath() string { return filepath.Join(p.base, "bin") }
-
-// InstallPath returns the base directory for plugin installations.
-//
-// e.g. {BasePath}/store
-func (p Paths) InstallPath() string { return filepath.Join(p.base, "store") }
-
-// PluginInstallPath returns the path to install the plugin.
-//
-// e.g. {InstallPath}/{version}/{..files..}
-func (p Paths) PluginInstallPath(plugin string) string {
-	return filepath.Join(p.InstallPath(), plugin)
-}
-
-// PluginVersionInstallPath returns the path to the specified version of specified
-// plugin.
-//
-// e.g. {PluginInstallPath}/{plugin}/{version}
-func (p Paths) PluginVersionInstallPath(plugin, version string) string {
-	return filepath.Join(p.InstallPath(), plugin, version)
-}


Test patch:
diff --git a/integration_test/system_test.go b/integration_test/system_test.go
index cc03889..25e8fcf 100644
--- a/integration_test/system_test.go
+++ b/integration_test/system_test.go
@@ -16,15 +16,12 @@
 package integrationtest
 
 import (
-	"bytes"
 	"os"
-	"path/filepath"
+	"strings"
 	"testing"
-
-	"sigs.k8s.io/krew/internal/testutil"
 )
 
-func TestKrewSystem(t *testing.T) {
+func TestKrewUnsupportedVersion(t *testing.T) {
 	skipShort(t)
 
 	test, cleanup := NewTest(t)
@@ -35,61 +32,13 @@ func TestKrewSystem(t *testing.T) {
 	// needs to be after initial installation
 	prepareOldKrewRoot(test)
 
-	test.Krew("system", "receipts-upgrade").RunOrFailOutput()
-	test.AssertExecutableInPATH("kubectl-" + validPlugin)
-
-	assertReceiptExistsFor(test, validPlugin)
-}
-
-func TestKrewSystem_ReceiptForKrew(t *testing.T) {
-	skipShort(t)
-
-	test, cleanup := NewTest(t)
-	defer cleanup()
-
-	prepareOldKrewRoot(test)
-	touch(test.tempDir, "store/krew/ensure-folder-exists")
-
-	test.WithDefaultIndex().Krew("system", "receipts-upgrade").RunOrFailOutput()
-
-	assertReceiptExistsFor(test, "krew")
-}
-
-func TestKrewSystem_IgnoreAdditionalFolders(t *testing.T) {
-	skipShort(t)
-
-	test, cleanup := NewTest(t)
-	defer cleanup()
-
-	prepareOldKrewRoot(test)
-
-	touch(test.tempDir, "store/not-a-plugin/ensure-folder-exists")
-	out := test.WithDefaultIndex().Krew("system", "receipts-upgrade").RunOrFailOutput()
-
-	if !bytes.Contains(out, []byte("Skipping plugin not-a-plugin")) {
-		t.Errorf("Expected a message that 'not-a-plugin' is skipped, but output was:")
-		t.Log(string(out))
+	// any command should fail here
+	out, err := test.Krew("list").Run()
+	if err == nil {
+		t.Error("krew should fail when old receipts structure is detected")
 	}
-}
-
-func TestKrewSystem_IgnoreUnknownPlugins(t *testing.T) {
-	skipShort(t)
-
-	test, cleanup := NewTest(t)
-	defer cleanup()
-
-	test.Krew("install",
-		"--manifest", filepath.Join("testdata", "foo.yaml"),
-		"--archive", filepath.Join("testdata", "foo.tar.gz")).
-		RunOrFail()
-
-	prepareOldKrewRoot(test)
-
-	out := test.WithDefaultIndex().Krew("system", "receipts-upgrade").RunOrFailOutput()
-
-	if !bytes.Contains(out, []byte("Skipping plugin foo")) {
-		t.Errorf("Expected a message that 'foo' is skipped, but output was:")
-		t.Log(string(out))
+	if !strings.Contains(string(out), "Uninstall Krew") {
+		t.Errorf("output should contain instructions on upgrading: %s", string(out))
 	}
 }
 
@@ -99,16 +48,3 @@ func prepareOldKrewRoot(test *ITest) {
 		test.t.Fatal(err)
 	}
 }
-
-func assertReceiptExistsFor(it *ITest, plugin string) {
-	receipt := "receipts/" + plugin + ".yaml"
-	_, err := os.Lstat(it.tempDir.Path(receipt))
-	if err != nil {
-		it.t.Errorf("Expected plugin receipt %q but found none.", receipt)
-	}
-}
-
-// touch creates a file without content in the temporary directory.
-func touch(td *testutil.TempDir, file string) {
-	td.Write(file, nil)
-}
diff --git a/internal/receiptsmigration/migration_test.go b/internal/receiptsmigration/migration_test.go
index 3af831c..71b873c 100644
--- a/internal/receiptsmigration/migration_test.go
+++ b/internal/receiptsmigration/migration_test.go
@@ -16,18 +16,10 @@
 package receiptsmigration
 
 import (
-	"io/ioutil"
 	"os"
-	"path/filepath"
-	"runtime"
-	"sort"
-	"strings"
 	"testing"
 
-	"github.com/google/go-cmp/cmp"
-
 	"sigs.k8s.io/krew/internal/environment"
-	"sigs.k8s.io/krew/internal/receiptsmigration/oldenvironment"
 	"sigs.k8s.io/krew/internal/testutil"
 )
 
@@ -82,186 +74,6 @@ func TestIsMigrated(t *testing.T) {
 	}
 }
 
-func Test_getPluginsToReinstall(t *testing.T) {
-	tests := []struct {
-		name         string
-		filesPresent []string
-		expected     []string
-	}{
-		{
-			name:         "single plugin",
-			filesPresent: []string{"store/konfig/konfig.sh", "index/plugins/konfig.yaml"},
-			expected:     []string{"konfig"},
-		},
-		{
-			name: "multiple plugins",
-			filesPresent: []string{
-				"store/konfig/foo", "index/plugins/konfig.yaml",
-				"store/plug-in/foo", "index/plugins/plug-in.yaml",
-			},
-			expected: []string{"konfig", "plug-in"},
-		},
-		{
-			name: "skip unsafe name",
-			filesPresent: []string{
-				"store/LPT6/foo", "index/plugins/LPT6.yaml",
-			},
-			expected: []string{},
-		},
-		{
-			name: "skip krew",
-			filesPresent: []string{
-				"store/krew/foo", "index/plugins/krew.yaml",
-			},
-			expected: []string{},
-		},
-		{
-			name: "skip if missing in index",
-			filesPresent: []string{
-				"store/missing/foo",
-			},
-			expected: []string{},
-		},
-		{
-			name:     "check multiple conditions",
-			expected: []string{"konfig", "plug-in"},
-			filesPresent: []string{
-				"store/konfig/foo", "index/plugins/konfig.yaml",
-				"store/plug-in/foo", "index/plugins/plug-in.yaml",
-				"store/LPT6/foo", "index/plugins/LPT6.yaml",
-				"store/krew/foo", "index/plugins/krew.yaml",
-				"store/missing/foo",
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			tmpDir, cleanup := testutil.NewTempDir(t)
-			defer cleanup()
-
-			oldPaths := oldenvironment.NewPaths(tmpDir.Root())
-			newPaths := environment.NewPaths(tmpDir.Root())
-
-			for _, name := range test.filesPresent {
-				touch(tmpDir, name)
-			}
-
-			actual, err := getPluginsToReinstall(oldPaths, newPaths)
-			if err != nil {
-				t.Fatal(err)
-			}
-			sort.Strings(actual)
-			sort.Strings(test.expected)
-
-			if diff := cmp.Diff(actual, test.expected); diff != "" {
-				t.Errorf(diff)
-			}
-		})
-	}
-}
-
-func Test_pluginNameToBin(t *testing.T) {
-	tests := []struct {
-		name      string
-		isWindows 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"},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			if got := pluginNameToBin(tt.name, tt.isWindows); got != tt.want {
-				t.Errorf("pluginNameToBin(%v, %v) = %v; want %v", tt.name, tt.isWindows, got, tt.want)
-			}
-		})
-	}
-}
-
-func Test_removeLink_notExists(t *testing.T) {
-	if err := removeLink("/non/existing/path"); err != nil {
-		t.Fatalf("removeLink failed with non-existing path: %+v", err)
-	}
-}
-
-func TestUninstall_cantUninstallItself(t *testing.T) {
-	envPath := oldenvironment.MustGetKrewPaths()
-	expectedErrorMessagePart := "not allowed"
-	if err := uninstall(envPath, "krew"); !strings.Contains(err.Error(), expectedErrorMessagePart) {
-		t.Fatalf("wrong error message for 'uninstall krew' action, expected message contains %q; got %q",
-			expectedErrorMessagePart, err.Error())
-	}
-}
-
-func Test_removeLink_linkExists(t *testing.T) {
-	tmpDir, cleanup := testutil.NewTempDir(t)
-	defer cleanup()
-
-	link := filepath.Join(tmpDir.Root(), "some-symlink")
-	if err := os.Symlink(os.TempDir(), link); err != nil {
-		t.Fatal(err)
-	}
-
-	if err := removeLink(link); err != nil {
-		t.Fatalf("removeLink(%s) failed: %+v", link, err)
-	}
-}
-
-func Test_removeLink_fails(t *testing.T) {
-	tmpDir, cleanup := testutil.NewTempDir(t)
-	defer cleanup()
-
-	// create an unreadable directory and trigger "permission denied" error
-	unreadableDir := filepath.Join(tmpDir.Root(), "unreadable")
-	if err := os.MkdirAll(unreadableDir, 0); err != nil {
-		t.Fatal(err)
-	}
-	unreadableFile := tmpDir.Path("unreadable/mysterious-file")
-
-	if err := removeLink(unreadableFile); err == nil {
-		t.Fatalf("removeLink(%s) with unreadable file returned err==nil", unreadableFile)
-	}
-}
-
-func Test_removeLink_regularFileExists(t *testing.T) {
-	f, err := ioutil.TempFile("", "some-regular-file")
-	if err != nil {
-		t.Fatal(err)
-	}
-	path := f.Name()
-	f.Close()
-	defer os.Remove(path)
-
-	if err := removeLink(path); err == nil {
-		t.Fatalf("removeLink(%s) with regular file was expected to fail; got: err=nil", path)
-	}
-}
-
-func Test_isWindows(t *testing.T) {
-	expected := runtime.GOOS == "windows"
-	got := isWindows()
-	if expected != got {
-		t.Fatalf("isWindows()=%v; expected=%v (on %s)", got, expected, runtime.GOOS)
-	}
-}
-
-func Test_isWindows_envOverride(t *testing.T) {
-	defer os.Unsetenv("KREW_OS")
-
-	os.Setenv("KREW_OS", "windows")
-	if !isWindows() {
-		t.Fatalf("isWindows()=false when KREW_OS=windows")
-	}
-
-	os.Setenv("KREW_OS", "not-windows")
-	if isWindows() {
-		t.Fatalf("isWindows()=true when KREW_OS != windows")
-	}
-}
-
 // touch creates a file without content in the temporary directory.
 func touch(td *testutil.TempDir, file string) {
 	td.Write(file, nil)
diff --git a/internal/receiptsmigration/oldenvironment/environment_test.go b/internal/receiptsmigration/oldenvironment/environment_test.go
deleted file mode 100644
index 7237cc5..0000000
--- a/internal/receiptsmigration/oldenvironment/environment_test.go
+++ /dev/null
@@ -1,70 +0,0 @@
-// 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.
-
-// todo(corneliusweig) remove migration code with v0.4
-package oldenvironment
-
-import (
-	"os"
-	"path/filepath"
-	"testing"
-
-	"k8s.io/client-go/util/homedir"
-)
-
-func TestMustGetKrewPaths_resolvesToHomeDir(t *testing.T) {
-	home := homedir.HomeDir()
-	expectedBase := filepath.Join(home, ".krew")
-	p := MustGetKrewPaths()
-	if got := p.BasePath(); got != expectedBase {
-		t.Fatalf("MustGetKrewPaths()=%s; expected=%s", got, expectedBase)
-	}
-}
-
-func TestMustGetKrewPaths_envOverride(t *testing.T) {
-	custom := filepath.FromSlash("/custom/krew/path")
-	os.Setenv("KREW_ROOT", custom)
-	defer os.Unsetenv("KREW_ROOT")
-
-	p := MustGetKrewPaths()
-	if expected, got := custom, p.BasePath(); got != expected {
-		t.Fatalf("MustGetKrewPaths()=%s; expected=%s", got, expected)
-	}
-}
-
-func TestPaths(t *testing.T) {
-	base := filepath.FromSlash("/foo")
-	p := NewPaths(base)
-	if got := p.BasePath(); got != base {
-		t.Fatalf("BasePath()=%s; expected=%s", got, base)
-	}
-	if got, expected := p.BinPath(), filepath.FromSlash("/foo/bin"); got != expected {
-		t.Fatalf("BinPath()=%s; expected=%s", got, expected)
-	}
-	if got, expected := p.IndexPath(), filepath.FromSlash("/foo/index"); got != expected {
-		t.Fatalf("IndexPath()=%s; expected=%s", got, expected)
-	}
-	if got, expected := p.IndexPluginsPath(), filepath.FromSlash("/foo/index/plugins"); got != expected {
-		t.Fatalf("IndexPluginsPath()=%s; expected=%s", got, expected)
-	}
-	if got, expected := p.InstallPath(), filepath.FromSlash("/foo/store"); got != expected {
-		t.Fatalf("InstallPath()=%s; expected=%s", got, expected)
-	}
-	if got, expected := p.PluginInstallPath("my-plugin"), filepath.FromSlash("/foo/store/my-plugin"); got != expected {
-		t.Fatalf("PluginInstallPath()=%s; expected=%s", got, expected)
-	}
-	if got, expected := p.PluginVersionInstallPath("my-plugin", "v1"), filepath.FromSlash("/foo/store/my-plugin/v1"); got != expected {
-		t.Fatalf("PluginVersionInstallPath()=%s; expected=%s", got, expected)
-	}
-}