diff --git a/README.md b/README.md index 8f4cc15d..234c9db6 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ After the plugin is installed, you can use `kn quickstart` to run its related su ## Usage ``` -Get up and running with a local Knative environment running on KinD. +Get up and running with a local Knative environment Usage: kn-quickstart [command] @@ -30,6 +30,7 @@ Available Commands: completion generate the autocompletion script for the specified shell help Help about any command kind Quickstart with Kind + minikube Quickstart with Minikube version Prints the plugin version Flags: @@ -46,6 +47,14 @@ Set up a local Knative cluster using [KinD](https://kind.sigs.k8s.io/): kn quickstart kind ``` +### Quickstart with Minikube + +Set up a local Knative cluster using [Minikube](https://minikube.sigs.k8s.io/): + +```bash +kn quickstart minikube +``` + ## Building from Source You must [set up your development environment](https://github.com/knative/client/blob/master/docs/DEVELOPMENT.md#prerequisites) before you build `kn-plugin-quickstart`. @@ -57,3 +66,4 @@ git clone git@github.com:knative-sandbox/kn-plugin-quickstart.git cd kn-plugin-quickstart ./hack/build.sh ``` + diff --git a/internal/command/kind.go b/internal/command/kind.go index bef49ea2..6de6dd22 100644 --- a/internal/command/kind.go +++ b/internal/command/kind.go @@ -21,7 +21,7 @@ import ( "knative.dev/kn-plugin-quickstart/pkg/kind" ) -// NewKindCommand implements 'kn kind' command +// NewKindCommand implements 'kn quickstart kind' command func NewKindCommand() *cobra.Command { return &cobra.Command{ Use: "kind", diff --git a/internal/command/minikube.go b/internal/command/minikube.go new file mode 100644 index 00000000..834773e9 --- /dev/null +++ b/internal/command/minikube.go @@ -0,0 +1,35 @@ +// Copyright © 2021 The Knative 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 command + +import ( + "fmt" + + "knative.dev/kn-plugin-quickstart/pkg/minikube" + + "github.com/spf13/cobra" +) + +// NewMinikubeCommand implements 'kn quickstart minikube' command +func NewMinikubeCommand() *cobra.Command { + return &cobra.Command{ + Use: "minikube", + Short: "Quickstart with Minikube", + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Println("Running Knative Quickstart using Minikube") + return minikube.SetUp() + }, + } +} diff --git a/internal/root/root.go b/internal/root/root.go index fcd1d808..e40481b5 100644 --- a/internal/root/root.go +++ b/internal/root/root.go @@ -19,16 +19,17 @@ import ( "knative.dev/kn-plugin-quickstart/internal/command" ) -// NewSourceKafkaCommand represents the plugin's entrypoint +// NewRootCommand represents the plugin's entrypoint func NewRootCommand() *cobra.Command { var rootCmd = &cobra.Command{ Use: "kn-quickstart", Short: "Get started quickly with Knative", - Long: `Get up and running with a local Knative environment running on KinD.`, + Long: `Get up and running with a local Knative environment`, } rootCmd.AddCommand(command.NewKindCommand()) + rootCmd.AddCommand(command.NewMinikubeCommand()) rootCmd.AddCommand(command.NewVersionCommand()) return rootCmd diff --git a/pkg/install/install.go b/pkg/install/install.go index 99921f25..665bf557 100644 --- a/pkg/install/install.go +++ b/pkg/install/install.go @@ -46,6 +46,15 @@ func Kourier() error { } fmt.Println(" Ingress patched...") + fmt.Println(" Finished installing Kourier Networking layer") + + return nil +} + +// KourierKind runs the kind-specific setup for Kourier +func KourierKind() error { + fmt.Println("🕸 Configuring Kourier for Kind...") + config := `apiVersion: v1 kind: Service metadata: @@ -76,9 +85,30 @@ spec: return fmt.Errorf("domain dns: %w", err) } fmt.Println(" Domain DNS set up...") + fmt.Println(" Finished configuring Kourier") - fmt.Println(" Finished installing Kourier Networking layer") + return nil +} +// KourierMinikube runs the minikube-specific setup for Kourier +func KourierMinikube() error { + fmt.Println("🕸 Configuring Kourier for Minikube...") + + if err := retryingApply("https://github.com/knative/serving/releases/download/v" + servingVersion + "/serving-default-domain.yaml"); err != nil { + return fmt.Errorf("default domain: %w", err) + } + if err := waitForPodsReady("knative-serving"); err != nil { + return fmt.Errorf("core: %w", err) + } + + fmt.Println(" Domain DNS set up...") + + tunnel := exec.Command("minikube", "tunnel", "--profile", "minikube-knative") + if err := tunnel.Start(); err != nil { + return fmt.Errorf("tunnel: %w", err) + } + fmt.Println(" Minikube tunnel...") + fmt.Println(" Finished configuring Kourier") return nil } diff --git a/pkg/kind/kind.go b/pkg/kind/kind.go index 757e7792..a9364d00 100644 --- a/pkg/kind/kind.go +++ b/pkg/kind/kind.go @@ -41,6 +41,9 @@ func SetUp() error { if err := install.Kourier(); err != nil { return fmt.Errorf("install kourier: %w", err) } + if err := install.KourierKind(); err != nil { + return fmt.Errorf("configure kourier: %w", err) + } if err := install.Eventing(); err != nil { return fmt.Errorf("install eventing: %w", err) } diff --git a/pkg/minikube/minikube.go b/pkg/minikube/minikube.go new file mode 100644 index 00000000..ec214076 --- /dev/null +++ b/pkg/minikube/minikube.go @@ -0,0 +1,186 @@ +// Copyright © 2021 The Knative 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 minikube + +import ( + "fmt" + "os" + "os/exec" + "regexp" + "runtime" + "strconv" + "strings" + "time" + + "knative.dev/kn-plugin-quickstart/pkg/install" +) + +var clusterName = "minikube-knative" +var minikubeVersion = 1.23 + +// SetUp creates a local Minikube cluster and installs all the relevant Knative components +func SetUp() error { + start := time.Now() + + if runtime.GOOS == "darwin" { + fmt.Println("NOTE: Using Minikube on Mac may require entering your password to enable networking.") + } + + if err := createMinikubeCluster(); err != nil { + return fmt.Errorf("creating cluster: %w", err) + } + if err := install.Serving(); err != nil { + return fmt.Errorf("install serving: %w", err) + } + if err := install.Kourier(); err != nil { + return fmt.Errorf("install kourier: %w", err) + } + if err := install.KourierMinikube(); err != nil { + return fmt.Errorf("configure kourier: %w", err) + } + if err := install.Eventing(); err != nil { + return fmt.Errorf("install eventing: %w", err) + } + finish := time.Since(start).Round(time.Second) + fmt.Printf("🚀 Knative install took: %s \n", finish) + fmt.Println("🎉 Now have some fun with Serverless and Event Driven Apps!") + return nil +} + +func createMinikubeCluster() error { + if err := checkMinikubeVersion(); err != nil { + return fmt.Errorf("minikube version: %w", err) + } + if err := checkForExistingCluster(); err != nil { + return fmt.Errorf("existing cluster: %w", err) + } + return nil +} + +// checkMinikubeVersion validates that the user has the correct version of Minikube installed. +// If not, it prompts the user to download a newer version before continuing. +func checkMinikubeVersion() error { + versionCheck := exec.Command("minikube", "version", "--short") + out, err := versionCheck.CombinedOutput() + if err != nil { + return fmt.Errorf("minikube version: %w", err) + } + fmt.Printf("Minikube version is: %s\n", string(out)) + + userMinikubeVersion, err := parseMinikubeVersion(string(out)) + if err != nil { + return fmt.Errorf("parsing minikube version: %w", err) + } + if userMinikubeVersion < minikubeVersion { + var resp string + fmt.Printf("WARNING: We require at least Minikube v%.2f, while you are using v%.2f\n", minikubeVersion, userMinikubeVersion) + fmt.Println("You can download a newer version from https://github.com/kubernetes/minikube/releases/") + fmt.Print("Continue anyway? (not recommended) [y/N]: ") + fmt.Scanf("%s", &resp) + if strings.ToLower(resp) != "y" { + fmt.Println("Installation stopped. Please upgrade minikube and run again") + os.Exit(0) + } + } + + return nil +} + +// checkForExistingCluster checks if the user already has a Minikube cluster. If so, it provides +// the option of deleting the existing cluster and recreating it. If not, it proceeds to +// creating a new cluster +func checkForExistingCluster() error { + + getClusters := exec.Command("minikube", "profile", "list") + out, err := getClusters.CombinedOutput() + if err != nil { + // there are no existing minikube profiles, the listing profiles command will error + // if there were no profiles, we simply want to create a new one and not stop the install + // so if the error is the "MK_USAGE_NO_PROFILE" error, we ignore it and continue onwards + if !strings.Contains(string(out), "MK_USAGE_NO_PROFILE") { + return fmt.Errorf("check cluster: %w", err) + } + } + // TODO Add tests for regex + r := regexp.MustCompile(clusterName) + matches := r.Match(out) + if matches { + var resp string + fmt.Print("Knative Cluster " + clusterName + " already installed.\nDelete and recreate [y/N]: ") + fmt.Scanf("%s", &resp) + if strings.ToLower(resp) != "y" { + fmt.Println("Installation skipped") + return nil + } + fmt.Println("deleting cluster...") + deleteCluster := exec.Command("minikube", "delete", "--profile", clusterName) + if err := deleteCluster.Run(); err != nil { + return fmt.Errorf("delete cluster: %w", err) + } + if err := createNewCluster(); err != nil { + return fmt.Errorf("new cluster: %w", err) + } + } + + if err := createNewCluster(); err != nil { + return fmt.Errorf("new cluster: %w", err) + } + + return nil +} + +// createNewCluster creates a new Minikube cluster +func createNewCluster() error { + + fmt.Println("☸ Creating Minikube cluster...") + fmt.Println("\n By default, using the standard minikube driver for your system") + fmt.Println("If you wish to use a different driver, please configure minikube using") + fmt.Println(" minikube config set --driver ") + + // create cluster and wait until ready + createCluster := exec.Command("minikube", "start", "--cpus", "3", "--profile", clusterName, "--wait", "all") + if err := runCommand(createCluster); err != nil { + return fmt.Errorf("minikube create: %w", err) + } + + // minikube tunnel + tunnel := exec.Command("minikube", "tunnel", "--profile", "minikube-knative") + if err := tunnel.Start(); err != nil { + return fmt.Errorf("tunnel: %w", err) + } + fmt.Println(" Minikube tunnel...") + + fmt.Println(" Cluster ready") + return nil +} + +func runCommand(c *exec.Cmd) error { + if out, err := c.CombinedOutput(); err != nil { + fmt.Println(string(out)) + return err + } + return nil +} + +func parseMinikubeVersion(v string) (float64, error) { + strippedVersion := strings.TrimLeft(strings.TrimRight(v, "\n"), "v") + dotVersion := strings.Split(strippedVersion, ".") + floatVersion, err := strconv.ParseFloat(dotVersion[0]+"."+dotVersion[1], 64) + if err != nil { + return 0, err + } + + return floatVersion, nil +}