Skip to content

Commit

Permalink
🏃 structure and expose cobra cmds
Browse files Browse the repository at this point in the history
This restructures the cmd directory into pkgs that follows the
hierarchy of CLI subcommands. This helps to expose the New***Command() functions
that return the cobra subcommands so they can be consumed by downstream
projects that need to reuse Kubebuilder CLI for scaffolding projects.
  • Loading branch information
hasbro17 committed Nov 4, 2019
1 parent 746316d commit 02fc1d1
Show file tree
Hide file tree
Showing 15 changed files with 184 additions and 164 deletions.
9 changes: 5 additions & 4 deletions cmd/alpha.go → cmd/alpha/alpha.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package main
package alpha

import (
"github.com/spf13/cobra"
"sigs.k8s.io/kubebuilder/cmd/alpha/webhookv1"
)

// newAlphaCommand returns alpha subcommand which will be mounted
// NewAlphaCommand returns alpha subcommand which will be mounted
// at the root command by the caller.
func newAlphaCommand() *cobra.Command {
func NewAlphaCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "alpha",
Short: "Expose commands which are in experimental or early stages of development",
Expand All @@ -34,7 +35,7 @@ kubebuilder alpha webhook <params>
}

cmd.AddCommand(
newWebhookCmd(),
webhookv1.NewWebhookCmd(),
)
return cmd
}
23 changes: 7 additions & 16 deletions cmd/webhook_v1.go → cmd/alpha/webhookv1/webhook_v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package main
package webhookv1

import (
"fmt"
Expand All @@ -25,18 +25,19 @@ import (

"github.com/gobuffalo/flect"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"

"sigs.k8s.io/kubebuilder/cmd/util"
"sigs.k8s.io/kubebuilder/pkg/model"
"sigs.k8s.io/kubebuilder/pkg/scaffold"
"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
"sigs.k8s.io/kubebuilder/pkg/scaffold/project"
sutil "sigs.k8s.io/kubebuilder/pkg/scaffold/util"
"sigs.k8s.io/kubebuilder/pkg/scaffold/v1/manager"
"sigs.k8s.io/kubebuilder/pkg/scaffold/v1/resource"
"sigs.k8s.io/kubebuilder/pkg/scaffold/v1/webhook"
)

func newWebhookCmd() *cobra.Command {
func NewWebhookCmd() *cobra.Command {
o := webhookOptions{}

cmd := &cobra.Command{
Expand All @@ -51,9 +52,9 @@ This command is only available for v1 scaffolding project.
kubebuilder alpha webhook --group crew --version v1 --kind FirstMate --type=mutating --operations=create,update
`,
Run: func(cmd *cobra.Command, args []string) {
dieIfNoProject()
util.DieIfNoProject()

projectInfo, err := scaffold.LoadProjectFile("PROJECT")
projectInfo, err := sutil.LoadProjectFile("PROJECT")
if err != nil {
log.Fatalf("failed to read the PROJECT file: %v", err)
}
Expand Down Expand Up @@ -103,7 +104,7 @@ This command is only available for v1 scaffolding project.
"the operations that the webhook will intercept, e.g. create, update, delete and connect")
cmd.Flags().BoolVar(&o.doMake, "make", true,
"if true, run make after generating files")
o.res = gvkForFlags(cmd.Flags())
o.res = util.GVKForFlags(cmd.Flags())
return cmd
}

Expand All @@ -115,13 +116,3 @@ type webhookOptions struct {
webhookType string
doMake bool
}

// gvkForFlags registers flags for Resource fields and returns the Resource
func gvkForFlags(f *flag.FlagSet) *resource.Resource {
r := &resource.Resource{}
f.StringVar(&r.Group, "group", "", "resource Group")
f.StringVar(&r.Version, "version", "", "resource Version")
f.StringVar(&r.Kind, "kind", "", "resource Kind")
f.StringVar(&r.Resource, "resource", "", "resource Resource")
return r
}
13 changes: 3 additions & 10 deletions cmd/api.go → cmd/create/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package main
package api

import (
"bufio"
Expand Down Expand Up @@ -74,7 +74,7 @@ func resourceForFlags(f *flag.FlagSet) *resource.Resource {

// APICmd represents the resource command
func (o *apiOptions) runAddAPI() {
dieIfNoProject()
util.DieIfNoProject()

switch strings.ToLower(o.pattern) {
case "":
Expand Down Expand Up @@ -126,7 +126,7 @@ func (o *apiOptions) postScaffold() error {
return nil
}

func newAPICommand() *cobra.Command {
func NewAPICommand() *cobra.Command {
options := apiOptions{
apiScaffolder: scaffold.API{},
}
Expand Down Expand Up @@ -169,10 +169,3 @@ After the scaffold is written, api will run make on the project.

return apiCmd
}

// dieIfNoProject checks to make sure the command is run from a directory containing a project file.
func dieIfNoProject() {
if _, err := os.Stat("PROJECT"); os.IsNotExist(err) {
log.Fatalf("Command must be run from a directory containing %s", "PROJECT")
}
}
13 changes: 8 additions & 5 deletions cmd/create.go → cmd/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package main
package create

import (
"fmt"

"github.com/spf13/cobra"
"sigs.k8s.io/kubebuilder/cmd/create/api"
"sigs.k8s.io/kubebuilder/cmd/create/webhookv2"
"sigs.k8s.io/kubebuilder/cmd/util"
)

func newCreateCmd() *cobra.Command {
func NewCreateCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "Scaffold a Kubernetes API or webhook.",
Expand All @@ -32,16 +35,16 @@ func newCreateCmd() *cobra.Command {
},
}
cmd.AddCommand(
newAPICommand(),
api.NewAPICommand(),
)

foundProject, version := getProjectVersion()
foundProject, version := util.GetProjectVersion()
// It add webhook v2 command in the following 2 cases:
// - There are no PROJECT file found.
// - version == 2 is found in the PROJECT file.
if !foundProject || version == "2" {
cmd.AddCommand(
newWebhookV2Cmd(),
webhookv2.NewWebhookV2Cmd(),
)
}

Expand Down
12 changes: 7 additions & 5 deletions cmd/webhook_v2.go → cmd/create/webhookv2/webhook_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package main
package webhookv2

import (
"fmt"
Expand All @@ -26,16 +26,18 @@ import (
"github.com/gobuffalo/flect"
"github.com/spf13/cobra"

"sigs.k8s.io/kubebuilder/cmd/util"
"sigs.k8s.io/kubebuilder/pkg/model"
"sigs.k8s.io/kubebuilder/pkg/scaffold"
"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
"sigs.k8s.io/kubebuilder/pkg/scaffold/project"
sutil "sigs.k8s.io/kubebuilder/pkg/scaffold/util"
"sigs.k8s.io/kubebuilder/pkg/scaffold/v1/resource"
resourcev2 "sigs.k8s.io/kubebuilder/pkg/scaffold/v2"
"sigs.k8s.io/kubebuilder/pkg/scaffold/v2/webhook"
)

func newWebhookV2Cmd() *cobra.Command {
func NewWebhookV2Cmd() *cobra.Command {
o := webhookV2Options{}

cmd := &cobra.Command{
Expand All @@ -49,9 +51,9 @@ func newWebhookV2Cmd() *cobra.Command {
kubebuilder create webhook --group crew --version v1 --kind FirstMate --conversion
`,
Run: func(cmd *cobra.Command, args []string) {
dieIfNoProject()
util.DieIfNoProject()

projectInfo, err := scaffold.LoadProjectFile("PROJECT")
projectInfo, err := sutil.LoadProjectFile("PROJECT")
if err != nil {
log.Fatalf("failed to read the PROJECT file: %v", err)
}
Expand Down Expand Up @@ -107,7 +109,7 @@ You need to implement the conversion.Hub and conversion.Convertible interfaces f

},
}
o.res = gvkForFlags(cmd.Flags())
o.res = util.GVKForFlags(cmd.Flags())
cmd.Flags().BoolVar(&o.defaulting, "defaulting", false,
"if set, scaffold the defaulting webhook")
cmd.Flags().BoolVar(&o.validation, "programmatic-validation", false,
Expand Down
2 changes: 1 addition & 1 deletion cmd/go_version_test.go → cmd/init/go_version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package main
package init

import "testing"

Expand Down
79 changes: 77 additions & 2 deletions cmd/init_project.go → cmd/init/init_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package main
package init

import (
"encoding/json"
"fmt"
"log"
"os"
Expand All @@ -27,13 +28,15 @@ import (

"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"golang.org/x/tools/go/packages"

"sigs.k8s.io/kubebuilder/cmd/util"
"sigs.k8s.io/kubebuilder/pkg/scaffold"
"sigs.k8s.io/kubebuilder/pkg/scaffold/project"
sutil "sigs.k8s.io/kubebuilder/pkg/scaffold/util"
)

func newInitProjectCmd() *cobra.Command {
func NewInitProjectCmd() *cobra.Command {
o := projectOptions{}

initCmd := &cobra.Command{
Expand Down Expand Up @@ -66,6 +69,14 @@ kubebuilder init --domain example.org --license apache2 --owner "The Kubernetes
return initCmd
}

// module and goMod arg just enough of the output of `go mod edit -json` for our purposes
type goMod struct {
Module module
}
type module struct {
Path string
}

type projectOptions struct {

// flags
Expand Down Expand Up @@ -224,6 +235,70 @@ func checkGoVersion(verStr string) error {
return nil
}

// findCurrentRepo attempts to determine the current repository
// though a combination of go/packages and `go mod` commands/tricks.
func findCurrentRepo() (string, error) {
// easiest case: project file already exists
projFile, err := sutil.LoadProjectFile("PROJECT")
if err == nil {
return projFile.Repo, nil
}

// next easy case: existing go module
path, err := findGoModulePath(false)
if err == nil {
return path, nil
}

// next, check if we've got a package in the current directory
pkgCfg := &packages.Config{
Mode: packages.NeedName, // name gives us path as well
}
pkgs, err := packages.Load(pkgCfg, ".")
// NB(directxman12): when go modules are off and we're outside GOPATH and
// we don't otherwise have a good guess packages.Load will fabricate a path
// that consists of `_/absolute/path/to/current/directory`. We shouldn't
// use that when it happens.
if err == nil && len(pkgs) > 0 && len(pkgs[0].PkgPath) > 0 && pkgs[0].PkgPath[0] != '_' {
return pkgs[0].PkgPath, nil
}

// otherwise, try to get `go mod init` to guess for us -- it's pretty good
cmd := exec.Command("go", "mod", "init")
cmd.Env = append(cmd.Env, os.Environ()...)
cmd.Env = append(cmd.Env, "GO111MODULE=on" /* turn on modules just for these commands */)
if _, err := cmd.Output(); err != nil {
if exitErr, isExitErr := err.(*exec.ExitError); isExitErr {
err = fmt.Errorf("%s", string(exitErr.Stderr))
}
// give up, let the user figure it out
return "", fmt.Errorf("could not determine repository path from module data, package data, or by initializing a module: %v", err)
}
defer os.Remove("go.mod") // clean up after ourselves
return findGoModulePath(true)
}

// findGoModulePath finds the path of the current module, if present.
func findGoModulePath(forceModules bool) (string, error) {
cmd := exec.Command("go", "mod", "edit", "-json")
cmd.Env = append(cmd.Env, os.Environ()...)
if forceModules {
cmd.Env = append(cmd.Env, "GO111MODULE=on" /* turn on modules just for these commands */)
}
out, err := cmd.Output()
if err != nil {
if exitErr, isExitErr := err.(*exec.ExitError); isExitErr {
err = fmt.Errorf("%s", string(exitErr.Stderr))
}
return "", err
}
mod := goMod{}
if err := json.Unmarshal(out, &mod); err != nil {
return "", err
}
return mod.Module.Path, nil
}

func (o *projectOptions) postScaffold() error {
// preserve old "ask if not explicitly set" behavior for the `--dep` flag
// (asking is handled by the v1 scaffolder)
Expand Down
Loading

0 comments on commit 02fc1d1

Please sign in to comment.