Repository: kubernetes/minikube
Pull request #11709 (2021-06-21 04:15:00)

Add method for customized box output
Part of #11207

This gives developers the ability to print out customized box output.

For example:

```go
boxCfg := box.Config{
	Py:       1,
	Px:       2,
	TitlePos: "Inside",
	Type:     "Double",
	Color:    "Yellow",
}

out.BoxedWithConfig(boxCfg, style.Warning, "Warning", "This is a warning")
```
![Screenshot from 2021-06-20 23-37-26](https://user-images.githubusercontent.com/1311594/122703865-7cd43300-d220-11eb-826e-862f2b14f918.png)




k8s-ci-robot: Hi @dinever. Thanks for your PR.

I'm waiting for a [kubernetes](https://github.com/orgs/kubernetes/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fminikube).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

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

This pull-request has been approved by: *<a href="https://github.com/kubernetes/minikube/pull/11709#" title="Author self-approved">dinever</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **ilya-zuyev** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @ilya-zuyev` 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%2Fminikube).

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

- **[OWNERS](https://github.com/kubernetes/minikube/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":["ilya-zuyev"]} -->
minikube-bot: Can one of the admins verify this patch?

Patch:
diff --git a/cmd/minikube/cmd/config/config.go b/cmd/minikube/cmd/config/config.go
index 0751f52e7..8ef363091 100644
--- a/cmd/minikube/cmd/config/config.go
+++ b/cmd/minikube/cmd/config/config.go
@@ -122,6 +122,18 @@ var settings = []Setting{
 		name: config.ReminderWaitPeriodInHours,
 		set:  SetInt,
 	},
+	{
+		name: config.WantReportError,
+		set:  SetBool,
+	},
+	{
+		name: config.WantReportErrorPrompt,
+		set:  SetBool,
+	},
+	{
+		name: config.WantKubectlDownloadMsg,
+		set:  SetBool,
+	},
 	{
 		name: config.WantNoneDriverWarning,
 		set:  SetBool,
@@ -134,6 +146,14 @@ var settings = []Setting{
 		name: Bootstrapper,
 		set:  SetString,
 	},
+	{
+		name: config.ShowDriverDeprecationNotification,
+		set:  SetBool,
+	},
+	{
+		name: config.ShowBootstrapperDeprecationNotification,
+		set:  SetBool,
+	},
 	{
 		name: "insecure-registry",
 		set:  SetString,
diff --git a/cmd/minikube/cmd/root.go b/cmd/minikube/cmd/root.go
index 63753c375..5b81138d8 100644
--- a/cmd/minikube/cmd/root.go
+++ b/cmd/minikube/cmd/root.go
@@ -303,7 +303,12 @@ func setupViper() {
 
 	viper.SetDefault(config.WantUpdateNotification, true)
 	viper.SetDefault(config.ReminderWaitPeriodInHours, 24)
+	viper.SetDefault(config.WantReportError, false)
+	viper.SetDefault(config.WantReportErrorPrompt, true)
+	viper.SetDefault(config.WantKubectlDownloadMsg, true)
 	viper.SetDefault(config.WantNoneDriverWarning, true)
+	viper.SetDefault(config.ShowDriverDeprecationNotification, true)
+	viper.SetDefault(config.ShowBootstrapperDeprecationNotification, true)
 }
 
 func addToPath(dir string) {
diff --git a/pkg/minikube/assets/addons.go b/pkg/minikube/assets/addons.go
index d53626004..f4c4dd5d2 100644
--- a/pkg/minikube/assets/addons.go
+++ b/pkg/minikube/assets/addons.go
@@ -535,7 +535,7 @@ var Addons = map[string]*Addon{
 			"0640"),
 	}, false, "gcp-auth", map[string]string{
 		"KubeWebhookCertgen": "jettech/kube-webhook-certgen:v1.3.0@sha256:ff01fba91131ed260df3f3793009efbf9686f5a5ce78a85f81c386a4403f7689",
-		"GCPAuthWebhook":     "k8s-minikube/gcp-auth-webhook:v0.0.6@sha256:c407ad6ee97d8a0e8a21c713e2d9af66aaf73315e4a123874c00b786f962f3cd",
+		"GCPAuthWebhook":     "k8s-minikube/gcp-auth-webhook:v0.0.5@sha256:4da26a6937e876c80642c98fed9efb2269a5d2cb55029de9e2685c9fd6bc1add",
 	}, map[string]string{
 		"GCPAuthWebhook": "gcr.io",
 	}),
diff --git a/pkg/minikube/config/config.go b/pkg/minikube/config/config.go
index 9b76d8445..f19409926 100644
--- a/pkg/minikube/config/config.go
+++ b/pkg/minikube/config/config.go
@@ -36,10 +36,20 @@ const (
 	WantBetaUpdateNotification = "WantBetaUpdateNotification"
 	// ReminderWaitPeriodInHours is the key for ReminderWaitPeriodInHours
 	ReminderWaitPeriodInHours = "ReminderWaitPeriodInHours"
+	// WantReportError is the key for WantReportError
+	WantReportError = "WantReportError"
+	// WantReportErrorPrompt is the key for WantReportErrorPrompt
+	WantReportErrorPrompt = "WantReportErrorPrompt"
+	// WantKubectlDownloadMsg is the key for WantKubectlDownloadMsg
+	WantKubectlDownloadMsg = "WantKubectlDownloadMsg"
 	// WantNoneDriverWarning is the key for WantNoneDriverWarning
 	WantNoneDriverWarning = "WantNoneDriverWarning"
 	// ProfileName represents the key for the global profile parameter
 	ProfileName = "profile"
+	// ShowDriverDeprecationNotification is the key for ShowDriverDeprecationNotification
+	ShowDriverDeprecationNotification = "ShowDriverDeprecationNotification"
+	// ShowBootstrapperDeprecationNotification is the key for ShowBootstrapperDeprecationNotification
+	ShowBootstrapperDeprecationNotification = "ShowBootstrapperDeprecationNotification"
 	// UserFlag is the key for the global user flag (ex. --user=user1)
 	UserFlag = "user"
 	// AddonImages stores custom addon images config
diff --git a/pkg/minikube/out/out.go b/pkg/minikube/out/out.go
index f2f6f8bc2..738864c06 100644
--- a/pkg/minikube/out/out.go
+++ b/pkg/minikube/out/out.go
@@ -68,6 +68,8 @@ var (
 	JSON = false
 	// spin is spinner showed at starting minikube
 	spin = spinner.New(spinner.CharSets[style.SpinnerCharacter], 100*time.Millisecond)
+	// defaultBoxCfg is the default style config for cli box output
+	defaultBoxCfg = box.Config{Py: 1, Px: 4, Type: "Round", Color: "Red"}
 )
 
 // MaxLogEntries controls the number of log entries to show for each source
@@ -113,23 +115,33 @@ func Styled(st style.Enum, format string, a ...V) {
 	}
 }
 
-func boxedCommon(printFunc func(format string, a ...interface{}), format string, a ...V) {
-	box := box.New(box.Config{Py: 1, Px: 4, Type: "Round"})
-	if useColor {
-		box.Config.Color = "Red"
+func boxedCommon(printFunc func(format string, a ...interface{}), cfg box.Config, title string, format string, a ...V) {
+	box := box.New(cfg)
+	if !useColor {
+		box.Config.Color = ""
 	}
 	str := Sprintf(style.None, format, a...)
-	printFunc(box.String("", strings.TrimSpace(str)))
+	printFunc(box.String(title, strings.TrimSpace(str)))
 }
 
-// Boxed writes a stylized and templated message in a box to stdout
+// Boxed writes a stylized and templated message in a box to stdout using the default style config
 func Boxed(format string, a ...V) {
-	boxedCommon(String, format, a...)
+	boxedCommon(String, defaultBoxCfg, "", format, a...)
 }
 
-// BoxedErr writes a stylized and templated message in a box to stderr
+// BoxedErr writes a stylized and templated message in a box to stderr using the default style config
 func BoxedErr(format string, a ...V) {
-	boxedCommon(Err, format, a...)
+	boxedCommon(Err, defaultBoxCfg, "", format, a...)
+}
+
+// BoxedWithConfig writes a templated message in a box with customized style config to stdout
+func BoxedWithConfig(cfg box.Config, st style.Enum, title string, format string, a ...V) {
+	if st != style.None {
+		title = Sprintf(st, title)
+	}
+	// need to make sure no newlines are in the title otherwise box-cli-maker panics
+	title = strings.ReplaceAll(title, "\n", "")
+	boxedCommon(String, cfg, title, format, a...)
 }
 
 // Sprintf is used for returning the string (doesn't write anything)
diff --git a/site/content/en/docs/commands/config.md b/site/content/en/docs/commands/config.md
index 44b3bb071..51ff431b2 100644
--- a/site/content/en/docs/commands/config.md
+++ b/site/content/en/docs/commands/config.md
@@ -29,9 +29,14 @@ Configurable fields:
  * WantUpdateNotification
  * WantBetaUpdateNotification
  * ReminderWaitPeriodInHours
+ * WantReportError
+ * WantReportErrorPrompt
+ * WantKubectlDownloadMsg
  * WantNoneDriverWarning
  * profile
  * bootstrapper
+ * ShowDriverDeprecationNotification
+ * ShowBootstrapperDeprecationNotification
  * insecure-registry
  * hyperv-virtual-switch
  * disable-driver-mounts


Test patch:
diff --git a/pkg/minikube/out/out_test.go b/pkg/minikube/out/out_test.go
index 593222350..ea0f729c8 100644
--- a/pkg/minikube/out/out_test.go
+++ b/pkg/minikube/out/out_test.go
@@ -23,6 +23,8 @@ import (
 	"strconv"
 	"testing"
 
+	"github.com/Delta456/box-cli-maker/v2"
+
 	"k8s.io/minikube/pkg/minikube/localpath"
 	"k8s.io/minikube/pkg/minikube/style"
 	"k8s.io/minikube/pkg/minikube/tests"
@@ -162,3 +164,142 @@ func TestLatestLogPath(t *testing.T) {
 		}
 	}
 }
+
+func TestBoxed(t *testing.T) {
+	f := tests.NewFakeFile()
+	SetOutFile(f)
+	Boxed(`Running with {{.driver}} driver and port {{.port}}`, V{"driver": "docker", "port": 8000})
+	got := f.String()
+	want :=
+		`╭────────────────────────────────────────────────╮
+│                                                │
+│    Running with docker driver and port 8000    │
+│                                                │
+╰────────────────────────────────────────────────╯
+`
+	if got != want {
+		t.Errorf("Boxed() = %q, want %q", got, want)
+	}
+}
+
+func TestBoxedErr(t *testing.T) {
+	f := tests.NewFakeFile()
+	SetErrFile(f)
+	BoxedErr(`Running with {{.driver}} driver and port {{.port}}`, V{"driver": "docker", "port": 8000})
+	got := f.String()
+	want :=
+		`╭────────────────────────────────────────────────╮
+│                                                │
+│    Running with docker driver and port 8000    │
+│                                                │
+╰────────────────────────────────────────────────╯
+`
+	if got != want {
+		t.Errorf("Boxed() = %q, want %q", got, want)
+	}
+}
+
+func TestBoxedWithConfig(t *testing.T) {
+	testCases := []struct {
+		config box.Config
+		st     style.Enum
+		title  string
+		format string
+		args   []V
+		want   string
+	}{
+		{
+			box.Config{Px: 2, Py: 2},
+			style.None,
+			"",
+			"Boxed content",
+			nil,
+			`┌─────────────────┐
+│                 │
+│                 │
+│  Boxed content  │
+│                 │
+│                 │
+└─────────────────┘
+`,
+		},
+		{
+			box.Config{Px: 0, Py: 0},
+			style.None,
+			"",
+			"Boxed content with 0 padding",
+			nil,
+			`┌────────────────────────────┐
+│Boxed content with 0 padding│
+└────────────────────────────┘
+`,
+		},
+		{
+			box.Config{Px: 1, Py: 1, TitlePos: "Inside"},
+			style.None,
+			"Hello World",
+			"Boxed content with title inside",
+			nil,
+			`┌─────────────────────────────────┐
+│                                 │
+│           Hello World           │
+│                                 │
+│ Boxed content with title inside │
+│                                 │
+└─────────────────────────────────┘
+`,
+		},
+		{
+			box.Config{Px: 1, Py: 1, TitlePos: "Top"},
+			style.None,
+			"Hello World",
+			"Boxed content with title inside",
+			nil,
+			`┌ Hello World ────────────────────┐
+│                                 │
+│ Boxed content with title inside │
+│                                 │
+└─────────────────────────────────┘
+`,
+		},
+		{
+			box.Config{Px: 1, Py: 1, TitlePos: "Top"},
+			style.Tip,
+			"Hello World",
+			"Boxed content with title inside",
+			nil,
+			`┌ * Hello World ──────────────────┐
+│                                 │
+│ Boxed content with title inside │
+│                                 │
+└─────────────────────────────────┘
+`,
+		},
+		{
+			box.Config{Px: 1, Py: 1, TitlePos: "Top"},
+			style.Tip,
+			// This case is to make sure newlines (\n) are removed before printing
+			// Otherwise box-cli-maker panices:
+			// https://github.com/Delta456/box-cli-maker/blob/7b5a1ad8a016ce181e7d8b05e24b54ff60b4b38a/box.go#L69-L71
+			"Hello \nWorld",
+			"Boxed content with title inside",
+			nil,
+			`┌ * Hello World ──────────────────┐
+│                                 │
+│ Boxed content with title inside │
+│                                 │
+└─────────────────────────────────┘
+`,
+		},
+	}
+
+	for _, tc := range testCases {
+		f := tests.NewFakeFile()
+		SetOutFile(f)
+		BoxedWithConfig(tc.config, tc.st, tc.title, tc.format, tc.args...)
+		got := f.String()
+		if tc.want != got {
+			t.Errorf("Expecting BoxedWithConfig(%v, %v, %s, %s, %s) = \n%s, want \n%s", tc.config, tc.st, tc.title, tc.format, tc.args, got, tc.want)
+		}
+	}
+}