Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support command mod init #945

Merged
merged 1 commit into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
SparkYuan marked this conversation as resolved.
Show resolved Hide resolved

# 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)
})
}
Loading