diff --git a/commands/operator-sdk/cmd/error.go b/commands/operator-sdk/cmd/error.go index d648ccacd7..c79c430bdf 100644 --- a/commands/operator-sdk/cmd/error.go +++ b/commands/operator-sdk/cmd/error.go @@ -6,6 +6,9 @@ import ( ) const ( + // http://tldp.org/LDP/abs/html/exitcodes.html + ExitSuccess = iota + ExitError // ExitBadArgs is the exit error code for bad arguments. ExitBadArgs = 128 ) diff --git a/commands/operator-sdk/cmd/new.go b/commands/operator-sdk/cmd/new.go index aaf28b3812..34e2fed530 100644 --- a/commands/operator-sdk/cmd/new.go +++ b/commands/operator-sdk/cmd/new.go @@ -3,6 +3,8 @@ package cmd import ( "fmt" + "github.com/coreos/operator-sdk/pkg/generator" + "github.com/spf13/cobra" ) @@ -27,8 +29,9 @@ var newCmd = &cobra.Command{ } var ( - apiGroup string - kind string + apiGroup string + kind string + projectName string ) func init() { @@ -44,8 +47,21 @@ func newFunc(cmd *cobra.Command, args []string) { ExitWithError(ExitBadArgs, fmt.Errorf("new command needs 1 argument.")) } parse(args) - // TODO: add generation logic. + verifyFlags() + g := generator.NewGenerator(apiGroup, kind, projectName) + err := g.Render() + if err != nil { + ExitWithError(ExitError, fmt.Errorf("failed to create project %v: %v", projectName, err)) + } } func parse(args []string) { + projectName = args[0] + if len(projectName) == 0 { + ExitWithError(ExitBadArgs, fmt.Errorf("project-name must not be empty")) + } +} + +func verifyFlags() { + // TODO: verify format of input flags. } diff --git a/pkg/generator/generator.go b/pkg/generator/generator.go index 58ebcec46f..60022f8ef9 100644 --- a/pkg/generator/generator.go +++ b/pkg/generator/generator.go @@ -1,61 +1,128 @@ package generator import ( - "errors" "os" "path/filepath" "strings" ) -const defaultFileMode = 0750 +const ( + defaultFileMode = 0750 + cmdDir = "cmd" + deployDir = "deploy" + configDir = "config" + tmpDir = "tmp" + buildDir = tmpDir + "/build" + codegenDir = tmpDir + "/codegen" + pkgDir = "pkg" + apisDir = pkgDir + "/apis" + stubDir = pkgDir + "/stub" +) type Generator struct { + apiGroup string + kind string + // projectName is name of the new operator application + // and is also the name of the base directory. + projectName string +} + +// NewGenerator creates a new scaffold Generator. +func NewGenerator(apiGroup, kind, projectName string) *Generator { + return &Generator{apiGroup: apiGroup, kind: kind, projectName: projectName} } +// Render generates the default project structure: +// +// ├── +// │ ├── cmd +// │ │ └── +// │ ├── config +// │ ├── deploy +// │ ├── pkg +// │ │ ├── apis +// │ │ │ └── // computed from apiDirName(apiGroup). +// │ │ │ └── // computed from apiVersion(apiGroup). +// │ │ └── stub +// │ └── tmp +// │ ├── build +// │ └── codegen func (g *Generator) Render() error { - gopath, ok := os.LookupEnv("GOPATH") - if !ok { - return errors.New("GOPATH must be set") + if err := g.renderCmd(); err != nil { + return err } - projDir, ok := os.LookupEnv("PROJECT") // github.com/coreos/play - if !ok { - return errors.New("PROJECT must be set") + if err := g.renderConfig(); err != nil { + return err + } + if err := g.renderDeploy(); err != nil { + return err } - apiGroup, ok := os.LookupEnv("APIGROUP") // play.coreos.com/v1alpha1 - if !ok { - return errors.New("PROJECT must be set") + if err := g.renderPkg(); err != nil { + return err } + return g.renderTmp() +} - projDir = filepath.Join(gopath, "src", projDir) - err := os.MkdirAll(projDir, defaultFileMode) - if err != nil { +func (g *Generator) renderCmd() error { + if err := os.MkdirAll(filepath.Join(g.projectName, cmdDir, g.projectName), defaultFileMode); err != nil { return err } + // TODO render files. + return nil +} - programName := func() string { - splits := strings.Split(projDir, "/") - return splits[len(splits)-1] - }() - err = os.MkdirAll(filepath.Join(projDir, "cmd", programName), defaultFileMode) - if err != nil { +func (g *Generator) renderConfig() error { + if err := os.MkdirAll(filepath.Join(g.projectName, configDir), defaultFileMode); err != nil { return err } + // TODO render files. + return nil +} - // pkg/apis/ - groupName, apiVersion := func() (string, string) { - splits := strings.Split(apiGroup, "/") - return strings.Split(splits[0], ".")[0], splits[1] - }() - err = os.MkdirAll(filepath.Join(projDir, "pkg/apis", groupName, apiVersion), defaultFileMode) - if err != nil { +func (g *Generator) renderDeploy() error { + if err := os.MkdirAll(filepath.Join(g.projectName, deployDir), defaultFileMode); err != nil { return err } + // TODO render files. + return nil +} - // pkg/controller/ - err = os.MkdirAll(filepath.Join(projDir, "pkg/controller"), defaultFileMode) - if err != nil { +func (g *Generator) renderTmp() error { + if err := os.MkdirAll(filepath.Join(g.projectName, buildDir), defaultFileMode); err != nil { return err } + if err := os.MkdirAll(filepath.Join(g.projectName, codegenDir), defaultFileMode); err != nil { + return err + } + // TODO render files. + return nil +} +func (g *Generator) renderPkg() error { + if err := os.MkdirAll(filepath.Join(g.projectName, apisDir, apiDirName(g.apiGroup), apiVersion(g.apiGroup)), defaultFileMode); err != nil { + return err + } + if err := os.MkdirAll(filepath.Join(g.projectName, stubDir), defaultFileMode); err != nil { + return err + } + // TODO render files. return nil } + +// apiVersion extracts api version from the given apiGroup. +func apiVersion(apiGroup string) string { + return strings.Split(apiGroup, "/")[1] +} + +// groupName extracts the group name from the givem apiGroup. +func groupName(apiGroup string) string { + return strings.Split(apiGroup, "/")[0] +} + +// apiDirName extracts the name of api directory under ../apis/ from the apiGroup. +// it uses the first word separated with "." of the groupName as the api directory name. +// for example, +// apiDirName("app.example.com/v1alpha1") => "app". +func apiDirName(apiGroup string) string { + return strings.Split(groupName(apiGroup), ".")[0] +}