Skip to content

Commit

Permalink
feat: support command mod init
Browse files Browse the repository at this point in the history
  • Loading branch information
SparkYuan committed Mar 19, 2024
1 parent 9ad97e4 commit e9590e1
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 13 deletions.
2 changes: 1 addition & 1 deletion pkg/cmd/mod/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func NewCmdMod(streams genericclioptions.IOStreams) *cobra.Command {
}

// add subcommands
cmd.AddCommand(NewCmdInit(streams))
cmd.AddCommand(NewCmdInit())
cmd.AddCommand(NewCmdPush(streams))

return cmd
Expand Down
105 changes: 93 additions & 12 deletions pkg/cmd/mod/mod_init.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,109 @@
package mod

import (
"fmt"
"os"
"path"

"github.com/go-git/go-git/v5/plumbing"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/gitutil"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/kubectl/pkg/util/templates"

"kusionstack.io/kusion/pkg/cmd/util"
"kusionstack.io/kusion/pkg/util/i18n"
)

type InitModOptions struct{}
type InitOptions struct {
Name string
Path string
TemplateURL string
}

var example = i18n.T(`# Create a kusion module template in the current directory
kusion mod init my-app
# Init a kusion module at the specified Path
kusion mod init my-app ./modules
# Init a module from a remote git template repository
kusion mod init my-app --template https://github.com/<user>/<repo>`)
var short = i18n.T("Create a kusion module along with common files and directories in the current directory")

var (
initLong = ``
initExample = ``
const (
defaultTemplateURL = "https://github.com/KusionStack/kusion-module-scaffolding.git"
defaultBranch = "main"
)

// NewCmdInit returns an initialized Command instance for the 'mod init' sub command
func NewCmdInit(streams genericclioptions.IOStreams) *cobra.Command {
func NewCmdInit() *cobra.Command {
o := &InitOptions{}

cmd := &cobra.Command{
Use: "",
DisableFlagsInUseLine: true,
Short: "",
Long: initLong,
Example: initExample,
Run: func(cmd *cobra.Command, args []string) {
Use: "init [MODULE NAME] [PATH]",
Short: short,
Example: templates.Examples(example),
RunE: func(cmd *cobra.Command, args []string) (err error) {
defer util.RecoverErr(&err)
util.CheckErr(o.Validate(args))
util.CheckErr(o.Run())
return
},
}
cmd.Flags().StringVar(&o.TemplateURL, "template", "", i18n.T("Initialize with specified template"))

return cmd
}

func (o *InitOptions) Validate(args []string) error {
// get the module Name
if len(args) < 1 {
return fmt.Errorf("module Name is empty")
}
o.Name = args[0]

// get the Path
if len(args) == 2 {
o.Path = args[1]
} else {
// default to the current directory
o.Path, _ = os.Getwd()
}

// create the module directory if not exists
fs, err := os.Stat(o.Path)
if err != nil {
return fmt.Errorf("failed to create module directory: %w", err)
} else if !fs.IsDir() {
return fmt.Errorf("path is not a directory")
} else if os.IsNotExist(err) {
if err = os.MkdirAll(o.Path, os.ModePerm); err != nil {
return fmt.Errorf("failed to create module directory: %v", err)
}
}
return nil
}

func (o *InitOptions) Run() error {
if o.TemplateURL == "" {
o.TemplateURL = defaultTemplateURL
}

// remove existing directory
dir := path.Join(o.Path, o.Name)
if err := os.RemoveAll(dir); err != nil {
return fmt.Errorf("failed to remove existing directory: %v", err)
}
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
return err
}

// clone templates repo
branch := plumbing.NewBranchReferenceName(defaultBranch)
err := gitutil.GitCloneOrPull(o.TemplateURL, branch, dir, false)
if err != nil {
return fmt.Errorf("failed to clone git repo:%s, %w", o.TemplateURL, err)
}
fmt.Printf("initialized module %s successfully\n", o.Name)
return nil
}
60 changes: 60 additions & 0 deletions pkg/cmd/mod/mod_init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package mod

import (
"os"
"testing"

"github.com/bytedance/mockey"
"github.com/stretchr/testify/assert"

"github.com/pulumi/pulumi/sdk/v3/go/common/util/gitutil"
)

func TestValidateWithEmptyModuleName(t *testing.T) {
o := &InitOptions{}
err := o.Validate([]string{})
assert.Error(t, err)
assert.Equal(t, "module Name is empty", err.Error())
}

func TestValidateWithNonExistentPath(t *testing.T) {
o := &InitOptions{}
err := o.Validate([]string{"my-app", "/non/existent/Path"})
assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to create module directory")
}

func TestValidateWithExistingPath(t *testing.T) {
o := &InitOptions{}
err := o.Validate([]string{"my-app", os.TempDir()})
assert.NoError(t, err)
}

func TestRunWithEmptyTemplateURL(t *testing.T) {
o := &InitOptions{}
o.Name = "my-app"
o.Path = os.TempDir()
err := o.Run()
assert.NoError(t, err)
}

func TestRunWithInvalidTemplateURL(t *testing.T) {
o := &InitOptions{}
o.Name = "my-app"
o.Path = os.TempDir()
o.TemplateURL = "invalid-url"
err := o.Run()
assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to clone git repo")
}

func TestNewCmdInit(t *testing.T) {
t.Run("validate error", func(t *testing.T) {
// mock git clone
mockey.Mock(gitutil.GitCloneOrPull).Return(nil).Build()
cmd := NewCmdInit()
cmd.SetArgs([]string{"fakeName"})
err := cmd.Execute()
assert.Nil(t, err)
})
}

0 comments on commit e9590e1

Please sign in to comment.